CreateProcess()は、Windows API(C言語)で別プログラムを実行する(子プロセスを作成する)関数。
|
|
WindowsのAPIでは、別プログラムを実行するのにCreateProcess()を使う。
(UNIX(POSIX)のfork・exec、JavaのProcessBuilderに相当)
別プログラムの実行方法はおおまかに以下の様になる。
calc.exe(電卓)を実行してみる。
#include <stdio.h> #include <windows.h>
void printError(const char *message)
{
LPVOID lpvMessageBuffer;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR)&lpvMessageBuffer,
0,
NULL);
fprintf(stderr, "%s: %s\n", message, lpvMessageBuffer);
LocalFree(lpvMessageBuffer);
}
Windows APIでエラーになるとGetLastError()でエラーコードが取れる。
FormatMessage()で、そのエラーコードの説明文が取得できる。
標準Cライブラリーのperror()に相当。
HANDLE childProcess = NULL;
int execute(LPTSTR commandLine)
{
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi = {};
if (!CreateProcess(
NULL,
commandLine,
NULL, //プロセスのセキュリティー記述子
NULL, //スレッドのセキュリティー記述子
FALSE, //ハンドルを継承しない
0, //作成フラグ
NULL, //環境変数は引き継ぐ
NULL, //カレントディレクトリーは同じ
&si,
&pi)
) {
printError("CreateProcess");
return -1;
}
// 子プロセス起動成功
childProcess = pi.hProcess;
// 不要なスレッドハンドルをクローズする
if (!CloseHandle(pi.hThread)) {
printError("CloseHandle(hThread)");
return -1;
}
printf("child processId=%d\n", pi.dwProcessId);
// 子プロセスの終了待ち
DWORD r = WaitForSingleObject(childProcess, INFINITE);
switch(r) {
case WAIT_FAILED:
printError("wait result=WAIT_FAILED");
return -1;
case WAIT_ABANDONED:
printf("wait result=WAIT_ABANDONED\n");
return -1;
case WAIT_OBJECT_0: //正常終了
printf("wait result=WAIT_OBJECT_0\n");
break;
case WAIT_TIMEOUT:
printf("wait result=WAIT_TIMEOUT\n");
return -1;
default:
printf("wait result=%d\n", r);
return -1;
}
// 子プロセスの終了コードを取得
DWORD exitCode;
if (!GetExitCodeProcess(childProcess, &exitCode)) {
printError("GetExitCodeProcess");
return -1;
}
printf("exitCode=%d/%x\n", exitCode, exitCode);
return exitCode;
}
WaitForSingleObject()でプロセスの終了を待つ。
例えば第1引数のプロセスハンドルがNULLだとWAIT_FAILEDが返る。[2013-01-15]
第2引数にはタイムアウト時間を指定する。INFINITEだと無限に待つ。
GetExitCodeProcess()でプロセスの終了コードを取得する。
int main()
{
printf("main[%d] start\n", GetCurrentProcessId());
LPTSTR commandLine = TEXT("calc.exe"); //電卓
int r = execute(commandLine);
// 子プロセスのハンドルのクローズ
if (childProcess != NULL) {
if(!CloseHandle(childProcess)) {
printError("CloseHandle(childProcess)");
}
}
printf("main[%d] end\n", GetCurrentProcessId());
return r;
}
子プロセスの起動に成功したら、子プロセスのハンドルが得られる。
最後にはこのハンドルをクローズする必要がある。
C言語にはJavaのようなtry〜finallyが無いので、ちゃんとクローズするようなコーディングが必要。
正常に子プロセスが実行・終了されると、このサンプルは以下の様な出力になる。
main[5468] start child processId=556 wait result=WAIT_OBJECT_0 exitCode=0/0 main[5468] end
実行ファイルが見つからないと以下の様なエラーが表示される。
main[932] start CreateProcess: 指定されたファイルが見つかりません。 main[932] end
CreateProcess()の引数についての正式な説明はMSDNを参照。
| 引数 | 概要 | 備考 |
|---|---|---|
| lpApplicationName | 実行コマンド(ファイル名) | NULLまたはargv[0]相当を指定する。 |
| lpCommandLine | 実行コマンド(ファイル名)およびその引数 | パースされてargvになる。 |
| lpProcessAttributes | プロセスのセキュリティー記述子 | NULL可。 |
| lpThreadAttributes | スレッドのセキュリティー記述子 | NULL可。 |
| bInheritHandles | ハンドルの継承オプション | TRUEにすると、ファイル等のハンドルが子プロセスに継承される。 (子プロセスでも親プロセスのハンドルを使える) FALSEにすると、継承されない。 (子プロセスでは親プロセスのハンドルは使えない) |
| dwCreationFlags | 作成フラグ | 色々! |
| lpEnvironment | 環境ブロック | NULLにしておくと、親プロセスの環境変数が子プロセスで使える。 |
| lpCurrentDirectory | カレントディレクトリー | NULLにしておくと、カレントディレクトリーは親プロセスと同じになる。 |
| lpStartupInfo | スタートアップ情報 | STARTUPINFO構造体に初期情報を格納しておく。 |
| lpProcessInformation | プロセス情報(戻り値) | PROCESS_INFORMATION構造体を渡して、プロセスの情報を受け取る。 |
commandLineには、実行ファイル名とその引数をスペース区切りでひとつにした文字列を指定する。
これが(C言語で言えば)main関数の引数のargvになる。
applicationNameには、実行ファイル名またはNULLを指定する。
NULLを指定した場合、commandLine内の先頭の文字列がコマンドとして実行される。環境変数PATHで指定されている場所も検索される。
NULL以外を指定すると、環境変数PATHの検索は行われない。
したがって、“必ず絶対パスで実行ファイル名を指定する”のでない場合は、
commandLineにコマンドを書いてapplicationNameはNULLにしておくのがよい。
startupInfo(STARTUPINFO構造体)に子プロセスの初期情報(子プロセスへ引き継ぐ情報)を指定する。
STARTUPINFOは先頭のフィールド「cb」で、そのSTARTUPINFO構造体のサイズを指定することになっている。
(Windows APIではよく見る方式。将来、構造体にフィールドが追加されても、サイズからバージョンを推定することが出来る)
STARTUPINFO si; si.cb = sizeof(STARTUPINFO); // si.cb = sizeof(si);
STARTUPINFO si = { sizeof(STARTUPINFO) };
プロセスの起動はCreateProcess()だが、終了させるにはTerminateProcess()を使用する。
UINT exitCode = 123;
if (!TerminateProcess(hProcess, exitCode)) {
printError("TerminateProcess");
}
第1引数で“終了させたいプロセス”のハンドルを指定する。
第2引数で“終了させたいプロセス”の終了コードを指定する。
“対象プロセスを実行している側”からは対象プロセス(TerminateProcessで終了させたプロセス)は正常終了したように見え(WaitForSingleObject()がWAIT_OBJECT_0を返す)、TerminateProcessで指定された終了コードを受け取る。
コマンドプロンプトからはtasklistコマンドで実行中のプログラム(プロセス)を確認できる。
> example1.exe main[5736] start child processId=3552
> tasklist | findstr example1.exe example1.exe 5736 Console 1 2,052 K > tasklist | findstr calc.exe calc.exe 3552 Console 1 17,136 K
そして、taskkillコマンドで終了させることが出来る。
> taskkill /pid 3552
成功: PID 3552 のプロセスに強制終了のシグナルを送信しました。
以下の様に普通の強制終了は出来ないことがあるが、「/F」オプションを付ければ終了できる。
> taskkill /pid 1272 エラー: PID 1272 のプロセスを強制終了できませんでした。 理由: この処理は、/F オプションのみで強制終了できます。 > taskkill /pid 1272 /f 成功: PID 1272 のプロセスは強制終了されました。
ちなみに、子プロセスをtaskkillで終了させた場合、親プロセス側では正常終了に見え(WaitForSingleObject()がWAIT_OBJECT_0を返す)、受け取る終了コードは0だった。
「/f」付きで終了させると、正常終了で終了コードは1だった。