我目前正在开发一个小的 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 来处理这个问题。谢谢。