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だった。