現在、構成ファイル/レジストリエントリを読み取り、値に応じて、現在のプロセスコマンドライン引数で子プロセスを実行するための小さな 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 シェルが開かれ、上記のソースからコンパイルされた 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 とコマンド プロンプトの両方が現在のウィンドウで実行されているという奇妙な状況でスタックしてしまい、入力はいずれか一方に渡されるか、2 つに分割されます。
コマンドプロンプトがparent.exeのプロセス/メインスレッドでWaitForSingleObjectを使用しており、そのプロセスが終了すると、その実行が続行されると想定しています。私はこれを回避しようとするかもしれないいくつかの理論を思いつきました:
parent_process から返された dwThreadId が正しいと仮定すると、現在のプロセスの作成を担当するスレッドを一時停止してから、子プロセスにコードを挿入して、終了時にスレッドを再開することができます。この考えの欠点は、parent.exe がコマンド プロンプトから実行されているか、parent.exe の終了を待機している同様のものから実行されていると想定していることです。parent.exe が非同期で実行される場合 (たとえば、エクスプローラーからの ShellExecute)、そのアプリケーションはデッドロックします。
ここでも、正しい情報を持っていると仮定すると、parent.exe の PROCESS_INFORMATION 構造体を含む仮想メモリを子プロセスの仮想メモリに書き換えることができます。ただし、これには、そのメモリブロックを見つける移植可能な方法が必要であり、現在のプロセスと親プロセスのプロセスアーキテクチャを確認し、データ型のサイズにある可能性のある違いを説明する必要があります。など。また、プロセスに関する書き込み制限により、子プロセスからこれを実行できるかどうかについても100%ではありません。(これは、これを実現するために親プロセスのリモート スレッドにコードを挿入する必要があることを意味します。これはまったく新しい頭痛の種です)
とにかく、これを処理するための組み込み API が見つからないことを本当に望んでいます。ありがとう。