0

我目前正在开发一个小的 win32 实用程序来读取配置文件/注册表项,并根据值,使用当前进程命令行参数执行子进程。这听起来很简单,并且已经与许多其他程序一起完成了很多次。在这种情况下,唯一的区别是,我希望能够在等待子进程结束时不让当前进程在后台运行。基本上,我想在当前控制台中启动子进程,传递标准输入等,然后退出当前进程,而不会退出子进程、丢失其控制台或返回当前进程的父进程。

到目前为止,这是我为尝试找出一种方法而写的内容:

#include <tchar.h>
#include <windows.h>
#include <tlhelp32.h>
#include <direct.h>
#include <string.h>
#include <stdio.h>

/* Macros for prettier code. */
#ifndef MAX_PATH
#   define MAX_PATH _MAX_PATH
#endif

#ifndef MAX_ENV
#   define MAX_ENV _MAX_ENV
#endif

/* Function Annotations/Declarations */
// Declare inline for MSVC's C compiler
#ifndef inline
#   define inline __inline
#endif

#ifndef bool
#   define bool BOOL
#   define true TRUE
#   define false FALSE
#endif

/* Utility macros */
#define log(x,...) _tprintf(TEXT("%s\n"), TEXT(x), __VA_ARGS__)
#define fatal(x,...) _tprintf(TEXT("ERROR: %s\n"), TEXT(x), __VA_ARGS__); ExitProcess(1)


// Search each process in the snapshot for id.
bool find_proc_id( HANDLE snap, DWORD id, LPPROCESSENTRY32 ppe )
{
    bool fOk;
    ppe->dwSize = sizeof(PROCESSENTRY32);
    for (fOk = Process32First( snap, ppe ); fOk; fOk = Process32Next( snap, ppe ))
        if (ppe->th32ProcessID == id)
            break;
    return fOk;
}

// Obtain the process and thread identifiers of the parent process.
bool parent_process(LPPROCESS_INFORMATION ppi)
{
    HANDLE hSnap;
    PROCESSENTRY32 pe;
    THREADENTRY32   te;
    DWORD id = GetCurrentProcessId();
    bool fOk;

    hSnap = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS|TH32CS_SNAPTHREAD, id );

    if (hSnap == INVALID_HANDLE_VALUE)
        return FALSE;

    find_proc_id( hSnap, id, &pe );
    if (!find_proc_id( hSnap, pe.th32ParentProcessID, &pe ))
    {
        CloseHandle( hSnap );
        return FALSE;
    }

    te.dwSize = sizeof(te);
    for (fOk = Thread32First( hSnap, &te ); fOk; fOk = Thread32Next( hSnap, &te ))
        if (te.th32OwnerProcessID == pe.th32ProcessID)
            break;

    CloseHandle( hSnap );

    ppi->dwProcessId = pe.th32ProcessID;
    ppi->dwThreadId = te.th32ThreadID;

    return fOk;
}

void execute_child(TCHAR *AppName)
{
    BOOL result;
    HANDLE hStdInput, hStdOutput, hStdError, hParent;
    TCHAR tempCmdLine[MAX_PATH * 2];  //Needed since CreateProcessW may change the contents of CmdLine
    PROCESS_INFORMATION processInformation, parentInformation;
    STARTUPINFO startupInfo;

    if (!parent_process( &parentInformation )) {
        fatal("Could not get parent process.");
    }

    if(!(hParent = OpenProcess(
        PROCESS_QUERY_INFORMATION |   
        PROCESS_CREATE_THREAD     | 
        PROCESS_VM_OPERATION |
        PROCESS_VM_WRITE,  // For CreateRemoteThread/WriteVirtualMemory
        FALSE, parentInformation.dwProcessId
    ))) {
        fatal("Could not open a handle to the parent process.");
    }

    memset(&processInformation, 0, sizeof(processInformation));
    memset(&startupInfo, 0, sizeof(startupInfo));

    if((hStdInput = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE || !hStdInput) {
        CloseHandle(hParent);
        fatal("Failed to get stdin.");
    }

    if((hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE)) == INVALID_HANDLE_VALUE || !hStdOutput) {
        CloseHandle(hParent);
        CloseHandle(hStdInput);
        fatal("Failed to get stdout.");
    }

    if((hStdError = GetStdHandle(STD_ERROR_HANDLE)) == INVALID_HANDLE_VALUE || !hStdError) {
        CloseHandle(hParent);
        CloseHandle(hStdInput);
        CloseHandle(hStdOutput);
        fatal("Failed to get stderr.");
    }

    startupInfo.cb = sizeof(startupInfo);
    startupInfo.dwFlags = STARTF_USESTDHANDLES;
    startupInfo.hStdInput = hStdInput;
    startupInfo.hStdOutput = hStdOutput;
    startupInfo.hStdError = hStdError;

    if (!CreateProcess(
            AppName,
            NULL,
            NULL,
            NULL,
            TRUE,
            CREATE_DEFAULT_ERROR_MODE|NORMAL_PRIORITY_CLASS|CREATE_NEW_PROCESS_GROUP|CREATE_SUSPENDED,
            NULL,
            NULL,
            &startupInfo,
            &processInformation
    )){
        CloseHandle(hParent);
        CloseHandle(hStdInput);
        CloseHandle(hStdOutput);
        CloseHandle(hStdError);
        fatal("CreateProcess failed!");
    }

    CloseHandle( hParent );
    CloseHandle( hStdInput );
    CloseHandle( hStdOutput );
    CloseHandle( hStdError );
    FreeConsole();
    ResumeThread(processInformation.hThread);
    CloseHandle( processInformation.hProcess );
    CloseHandle( processInformation.hThread );
    ExitProcess(0);
}

int _tmain(int argc, _TCHAR* argv[])
{
    execute_child(TEXT("python.exe"));

    return 0;
}

现在,当从控制台外部执行时,这正是我想要的。打开一个 python shell 并且从上面的源代码编译的 parent.exe 没有运行。但是,当从现有命令提示符运行时,会发生以下情况:

Microsoft Windows [Version 6.1.7601]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\ShellEnv\j-tree\sbin\python\2.7x86>parent.exe

C:\ShellEnv\j-tree\sbin\python\2.7x86>ActivePython 2.7.2.5 (ActiveState Software Inc.) based on
Python 2.7.2 (default, Jun 24 2011, 12:21:10) [MSC v.1500 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> print 'hi'
Unable to initialize device PRN

C:\ShellEnv\j-tree\sbin\python\2.7x86>

控制台最终陷入了一种奇怪的情况,即 Python 和命令提示符都在当前窗口中执行,并且任何输入都被传递给一个或另一个,或者在两者之间拆分。

我假设命令提示符在 parent.exe 的进程/主线程上使用 WaitForSingleObject,当该进程退出时,继续执行。我想出了一些我可以尝试解决这个问题的理论:

  • 假设从 parent_process 返回的 dwThreadId 是正确的,我可以尝试挂起负责创建当前进程的线程,然后将代码注入我的子进程,以便在退出时恢复线程。这个想法的缺陷是它假设 parent.exe 是从命令提示符运行的,或者类似的东西正在等待 parent.exe 的退出。在 parent.exe 异步运行的情况下(例如,来自资源管理器的 ShellExecute),它将使该应用程序死锁。

  • 同样,假设我有正确的信息,我可以尝试将包含 parent.exe 的 PROCESS_INFORMATION 结构的虚拟内存重写为子进程的虚拟内存。然而,这将需要一种可移植的方式来定位该内存块,并且还需要我检查当前进程和父进程的进程架构,并考虑数据类型大小可能存在的任何差异,等等。我也不是 100% 关于进程的写入限制是否允许我从子进程中执行此操作。(这意味着我可能必须将代码注入父进程中的远程线程来实现这一点,这是一个全新的头痛)

无论如何,我真的希望有一些我无法找到的内置 API 来处理这个问题。谢谢。

4

1 回答 1

0

exec 使用函数家族的成员怎么样?

调用这些函数之一会将当前进程替换为新创建的进程。

于 2012-06-10T14:04:50.200 回答