S-JIS[2013-01-10/2013-01-15]

CreateProcess関数

CreateProcess()は、Windows API(C言語)で別プログラムを実行する(子プロセスを作成する)関数。


概要

WindowsのAPIでは、別プログラムを実行するのにCreateProcess()を使う。
(UNIX(POSIX)のforkexec、JavaのProcessBuilderに相当)

別プログラムの実行方法はおおまかに以下の様になる。

  1. CreateProcess()で別プログラムを実行(子プロセスを起動)する。
  2. WaitForSingleObject()で終了を待つ。
  3. GetExitCodeProcess()で終了コードを取得する。
  4. CloseHandle()で子プロセスのハンドルをクローズする。

CreateProcessの例

calc.exe(電卓)を実行してみる。

example1.c:

#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の引数

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構造体を渡して、プロセスの情報を受け取る。

applicationNameおよびcommandLineについて

commandLineには、実行ファイル名とその引数をスペース区切りでひとつにした文字列を指定する。
これが(C言語で言えば)main関数の引数のargvになる。

applicationNameには、実行ファイル名またはNULLを指定する。
NULLを指定した場合、commandLine内の先頭の文字列がコマンドとして実行される。環境変数PATHで指定されている場所も検索される。
NULL以外を指定すると、環境変数PATHの検索は行われない。

したがって、“必ず絶対パスで実行ファイル名を指定する”のでない場合は、
commandLineにコマンドを書いてapplicationNameはNULLにしておくのがよい。


startupInfoについて

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


C言語目次へ戻る / 技術メモへ戻る
メールの送信先:ひしだま