CreateProcessで子プロセスを起動する方法
Windowsアプリケーションから別の実行ファイルを起動するには、Win32 APIのCreateProcessを使用します。単にプログラムを実行するだけでなく、コマンドライン引数、標準入出力、ウィンドウ表示、継承するハンドル、プロセス終了の監視まで細かく制御できる点が特徴です。
一方で、引数文字列の扱いや構造体の初期化、ハンドルの解放を誤ると、起動失敗やメモリリーク、意図しないファイルハンドルの共有につながります。ここでは、Unicode対応の基本コードから終了待機、エラー処理まで、Windows APIの利用時に必要な要点を整理します。
CreateProcessの基本的な役割
CreateProcessWは、指定した実行ファイルを新しいプロセスとして起動します。成功すると、呼び出し元にはプロセスハンドルとメインスレッドのハンドルが返されます。これらは起動直後の監視や終了待機に利用できます。
実行ファイルのパスは第1引数のlpApplicationNameに渡せます。第2引数のlpCommandLineには、実行時に渡す引数を含むコマンドラインを指定します。パスを明示する場合は、第1引数を使うほうが、空白を含むパスの解釈を安定させやすくなります。
#include <windows.h>
#include <iostream>
int main()
{
STARTUPINFOW si{};
PROCESS_INFORMATION pi{};
si.cb = sizeof(si);
wchar_t commandLine[] = L"\"C:\\Tools\\worker.exe\" --mode batch";
BOOL result = CreateProcessW(
L"C:\\Tools\\worker.exe",
commandLine,
nullptr,
nullptr,
FALSE,
0,
nullptr,
nullptr,
&si,
&pi
);
if (!result) {
std::wcerr << L"CreateProcessW failed: "
<< GetLastError() << std::endl;
return 1;
}
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return 0;
}
Windows APIの関数や構造体を調べる際は、関連する宣言と使用例をまとめたWindows API資料も参照できます。
実行ファイルと引数の指定
lpCommandLineは入力専用の文字列ではありません。CreateProcessWは内部処理で内容を書き換える可能性があるため、書き換え不能な文字列リテラルを直接渡してはいけません。上の例のように、変更可能なwchar_t配列を用意します。
実行ファイルのパスや引数に空白が含まれる場合は、パス全体を二重引用符で囲みます。たとえばC:\Program Files\App\app.exeは、"C:\Program Files\App\app.exe"と記述します。引数自体に引用符を含めたい場合は、Windowsのコマンドライン解析規則に合わせてバックスラッシュを調整する必要があります。
ファイル選択ダイアログで取得したパスを子プロセスの起動に使う場合は、取得した文字列の終端、長さ、Unicode形式を確認します。ファイル選択ダイアログから返されたパスをそのまま信頼せず、必要に応じて存在確認や拡張子の検証も行います。
STARTUPINFOと起動オプション
STARTUPINFOは、子プロセスの初期ウィンドウ状態や標準ハンドルを指定する構造体です。通常の起動ではゼロ初期化し、cbに構造体のサイズを設定します。拡張フィールドを使用する場合はSTARTUPINFOEXを使い、EXTENDED_STARTUPINFO_PRESENTフラグを指定します。
コンソール画面を表示せずに起動したい場合は、dwFlagsにSTARTF_USESHOWWINDOWを設定し、wShowWindowにSW_HIDEなどを指定します。ただし、GUIアプリケーションとコンソールアプリケーションでは表示動作が異なるため、非表示指定だけで確実に画面を抑制できるとは限りません。
dwCreationFlagsには、優先度や新しいコンソールの作成方法を指定できます。たとえばCREATE_NO_WINDOWは、コンソールアプリケーションを新しいコンソールなしで起動する際に使われます。フラグは目的に合うものだけを指定し、不要な組み合わせは避けます。
ハンドル継承と標準入出力
標準出力を親プロセスで受け取るには、パイプを作成し、その書き込み側を子プロセスの標準出力へ割り当てます。このとき、STARTF_USESTDHANDLESを設定し、hStdInput、hStdOutput、hStdErrorを適切なハンドルに設定します。
第5引数のbInheritHandlesをTRUEにすると、継承可能なハンドルが子プロセスへ渡されます。ただし、意図しないハンドルまで共有される危険があります。Windows Vista以降では、STARTUPINFOEXとPROC_THREAD_ATTRIBUTE_HANDLE_LISTを使い、継承対象を明示的に限定する方法が安全です。
パイプを使う場合、親が読み取り側を保持し、子が書き込み側を保持します。親プロセスに不要な書き込み側が残っていると、子が終了しても読み取りが終了しないことがあります。起動直後に不要なハンドルを確実に閉じることが重要です。
起動後の終了待機
子プロセスが終わるまで待つには、WaitForSingleObjectへpi.hProcessを渡します。待機時間にINFINITEを指定すれば無期限に待機できますが、GUIスレッドで実行するとウィンドウが応答しなくなるため注意が必要です。
DWORD waitResult = WaitForSingleObject(pi.hProcess, 10000);
if (waitResult == WAIT_OBJECT_0) {
DWORD exitCode = 0;
if (GetExitCodeProcess(pi.hProcess, &exitCode)) {
std::cout << "exit code: " << exitCode << std::endl;
}
} else if (waitResult == WAIT_TIMEOUT) {
TerminateProcess(pi.hProcess, 1);
} else {
std::cerr << "WaitForSingleObject failed: "
<< GetLastError() << std::endl;
}
終了コードはGetExitCodeProcessで取得できます。まだ実行中の場合、この関数はSTILL_ACTIVEを返します。終了待機が不要でも、CreateProcessが返したプロセスハンドルとスレッドハンドルは、処理が終わった時点で必ずCloseHandleを呼び出します。
エラー処理とセキュリティ
CreateProcessが失敗した場合は、直後にGetLastErrorを呼び出してエラーコードを保存します。後続のログ出力や別のAPI呼び出しによって、最後のエラー値が変わる可能性があるためです。ERROR_FILE_NOT_FOUND、ERROR_ACCESS_DENIED、ERROR_BAD_EXE_FORMATなど、原因に応じて利用者へ伝える内容を分けると診断しやすくなります。
実行ファイルのパスを省略して検索に任せる場合、検索順序によって意図しない同名ファイルが実行される危険があります。外部入力から得たパスを使う場合は、絶対パスを利用し、許可されたディレクトリや拡張子を検証します。管理者権限で動作する親プロセスから、入力値をそのままコマンドラインへ連結する設計は避けます。
| 確認項目 | 推奨される扱い | 注意点 |
|---|---|---|
| API | CreateProcessW |
Unicode文字列を使用する |
| 実行ファイル | 絶対パスを指定 | 同名ファイルの誤起動を防ぐ |
| コマンドライン | 変更可能な配列 | 文字列リテラルを直接渡さない |
| 起動情報 | STARTUPINFOをゼロ初期化 |
cbを必ず設定する |
| 待機 | WaitForSingleObject |
GUIスレッドの無期限待機を避ける |
| 後始末 | CloseHandle |
プロセスとスレッドの両方を閉じる |
実装時に確認したいポイント
実装を組み込む前に、次の項目を確認すると、起動処理の不具合を減らせます。
CreateProcessWを使い、パスと引数の文字コードを統一する- 空白を含む実行ファイルパスを二重引用符で囲む
lpCommandLineへ変更可能なバッファーを渡す- 継承するハンドルを必要最小限に限定する
GetLastErrorを失敗直後に取得して記録する- 子プロセスとスレッドのハンドルを確実に解放する
- GUIアプリケーションでは待機中の応答性を維持する
CreateProcessは柔軟な反面、文字列、ハンドル、プロセス寿命を呼び出し側が管理するAPIです。実行ファイルの場所を明確にし、引数を安全に組み立て、終了状態を確認してからハンドルを閉じるという流れを守ることが、安定した子プロセス起動の基本です。読者が覚えておくべき点は、起動に成功したかどうかだけでなく、引数の安全性と起動後の後始末までを一つの処理として設計することです。