Win32在当前控制台中执行子进程并退出而不返回到父进程

时间:2012-06-10 04:59:03

标签: c multithreading winapi process

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

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

#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来处理这个我找不到的。感谢。

1 个答案:

答案 0 :(得分:0)

如何使用exec family of functions的成员?

调用其中一个函数会将当前进程替换为新创建的进程。