CreateProcess の謎

Windows API の CreateProcess を使用していて不可解な謎に遭遇したのでメモします。

CreateProcess で得たハンドラを TerminateProcess できない?!

CreateProcess をデバッグオプション付きで起動していた場合、ブレイクポイント点で動作がいったん止まりますが、この時に TerminateProcess しても Terminate されないようです。(タスクスケジューラを見るとプロセスが残っています。)

通常に CreateProcess した場合は子プロセスは最後まで実行されて Exit Code が取れます。プロセスデバッグオプションが何かインタラクションを起こしていると想起されます。

Windows サービスから CreateProcess した時の挙動

Windows サービス内から CreateProcess すると、SYSTEM ユーザ権限で起動します。ぐぐると CreateProcess できないような記述も見受けられますが、そうではありません。ただし、標準入出力がないのでデバッグが少し困難になります。

Windows サービスから起動すると、カレントディレクトリは c:\ になってしまうため、相対パスを指定することはできないと考えた方がよいようです。相対パスを使用したい場合は、GetModuleFileNameW で自身の絶対パスを求め、_tsplitpath, _tcscat でドライブ名とディレクトリを結合する必要があります。

コードで書くとこんな感じになります。

#include <windows.h>
TCHAR gsMyName[MAX_PATH], gsDrive[MAX_PATH], gsDir[MAX_PATH], gsFilename[MAX_MATH], gsExt[MAX_PATH];

int __cdecl wmain(int argc, TCHAR** argv) {
    if (GetModuleFileNameW(NULL, gsMyName, _countof(gsMyName)) != 0LL) {
        _tsplitpath(gsMyName, gsDrive, gsDir, gsFilename, gsExt);
        _tcscat(gsDrive, gsDir);
    }
...
}

子プロセスを作成するために CreateProcessW を呼ぶ場合は第1引数を NULL にしないとうまく動きませんでした。この場合、マニュアルにある通り、szCommandLine を空白文字で区切った場合の最初の文字列が実行ファイル名となります。

#include <string>

BOOL StartChild(std::wstring &szExeName, std::wstring &szArg, PROCESS_INFORMATION &pi) {
    STARTUPINFO si;
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    ZeroMemory(&pi, sizeof(pi));
    std::wstring szCommandLine = szExeName + L" " + szArg;
    if (!CreateProcessW(NULL, szCommandLine.c_str(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
        writeLog(L"CreateProcess failed: %ws", szCommandLine.c_str());
        reutrn FALSE;
    }
    writeLog(L"Process \"%ws\" started", szCommandLine.c_str());
    return TRUE;
}

プロセス終了を待機する場合はマニュアルにある通り、 WaitForMultipleObjects などを使用します。

また、hProcess だけでなく、hThread も CloseHandle でハンドルを削除しておく必要があります。

void MainLoop() {
    PROCESS_INFORMATION pi;
    HANDLE hStop = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (hStop == INVALID_HANDLE_VALUE) {
        writeLog("CreateEvent failed");
        return;
    }
    while(TRUE) {
        if (!StartChild(gsMyName, L"--option", &pi)) {
            writeLog("StartChild failed");
            break;
        }
        HANDLE objHandles[] = { hStop, pi.hProcess };
        DWORD r = WaitForMultipleObjects(2, &objHandles, FALSE, 8 * 60 * 1000);
        if (r == WAIT_TIMEOUT) {
            // 時間切れ
            // 8分経過しても実行が終了しない場合はサービスを停止する
            TerminateProcess(pi.hProcess, 0);
            break;
        }
        else if (r == WAIT_OBJECT_0) {
       // サービス終了イベントを受信
            TerminateProcess(pi.hProcess, 0);
            break;
        }
        else if (r == WAIT_OBJECT_0 + 1) {
            // 子プロセス終了を受信
            CloseHandle(pi.hProcess);
            CloseHandle(pi.hThread);
        }
        else {
            break;
         }

        // 5分おきにサービス提供
        r = WaitForSingleObject(hStop, 5 * 60 * 1000);
        if (r != WAIT_TIMEOUT) {
            break;
        }
    }
    CloseHandle(hStop);
    if (pi.hProcess != NULL) {
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
    }
    writeLog("Stopping Service");
    // 実際にはここでサービスの停止処理を行う
}

プログラムが Exit Code = 1 で返る原因はいろいろある

コードを書いた時、 writeLog 関数のパス名が期待している値と異なっていることに気づかずに ofstream を使用していました。引数を指定しないと ofstream のコンストラクタあるいは open メソッドは例外を発生し、どこでもキャッチされないと Exit Code = 1 で終了します。

このことに気付かないまま、HelloWorld のような簡単なプロセスは動くのに目的のプロセスが動かない現象に1日頭を悩ましていました。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です