サブプロセスの終了を待つ ACT-2

サンプルスクリプト

さて、実際のスクリプト書いて見ましょう。

今回はメインプロセスからメモ帳を実行し、メモ帳が閉じられたらメインプロセスが実行を再開するようにしてみましょう。

サンプルスクリプト main1.as

    #include "llmod.as"

    ; 起動する実行可能ファイル名(コマンドラインパラメータも含む)
    cmd = "notepad.exe"

    ; STARTUPINFO構造体を設定
    dim stinfo, 17
    stinfo.0 = 68         ; 構造体サイズ(cbメンバ)

    ; PROCESS_INFOMATION構造体を設定
    dim prinfo, 4

    ; 新しいプロセスを起動
    pm.0 = 0              ; 実行可能ファイル名はパラメータとともに指定するのでNULL
    getptr pm.1, cmd      ; 実行可能ファイル名文字列のアドレス
    pm.2 = 0
    pm.3 = 0
    pm.4 = 0
    pm.5 = 0
    pm.6 = 0
    pm.7 = 0                  ; 起動時カレントディレクトリは親プロセスと同じ
    getptr pm.8, stinfo       ; STARTUPINFO構造体アドレス
    getptr pm.9, prinfo       ; PROCESS_INFOMATION構造体アドレス
    dllproc "CreateProcessA", pm, 10, D_KERNEL
    if dllret == 0 : dialog "起動失敗" : end

    hprocess = prinfo.0       ; プロセスのハンドル
    hthread = prinfo.1        ; プライマリスレッドのハンドル

    ; プライマリスレッドのハンドルは直ちにクローズ
    dllproc "CloseHandle", hthread, 1, D_KERNEL
    hthread = 0

    ; サブプロセスの終了を待機する
    pm.0 = hprocess           ; プロセスのハンドル
    pm.1 = -1                 ; INFINITE
    dllproc "WaitForSingleObject", pm, 2, D_KERNEL

    dialog "サブプロセスが終了しました。"

    ; プロセスハンドルをクローズ
    dllproc "CloseHandle", hprocess, 1, D_KERNEL
    hprocess = 0
    end

前回も述べましたが、普通に起動させるのであれば、STARTUPINFO構造体には特に何も指定する必要はありません。cbメンバに構造体サイズ68を格納し、それ以外のメンバはすべて0になるように指定します。

今回はCreateProcess関数の第2引数に実行ファイル名「notepad.exe」を指定しています。なぜ第1引数に指定しないのかというと、第1引数に実行ファイル名を指定する場合は、フルパス指定か、さもなくばカレントディレクトリの実行ファイルを指定することしかできないためです。第2引数に(必要ならコマンドラインパラメータも添えて)指定すると、実行ファイルをカレントディレクトリのほかにWindowsディレクトリやシステムディレクトリからも検索してくれます。また、拡張子「.exe」を省略することもできます。メモ帳はWindowsディレクトリ内にあるので、こうしなければならないのです。

CreateProcess関数を呼び出したら、必要のないプライマリスレッドのハンドルはすぐにCloseHandle関数を使ってクローズしておきます。その後、プロセスのハンドルを引数としてWaitForSingleObject関数を呼び出します。メモ帳が終了するまではここで処理が中断されます。処理が再開されたら、プロセスのハンドルをクローズします。

待機中にメインプロセスを動かすには

上のサンプルを実行させてみるとわかりまっすが、サブプロセスが終了しているのを待機している間は、メインプロセスのウィンドウはまったく反応しません。WaitForSingleObject関数の待ち時間として-1 (INFINITE) を指定しているため、サブプロセスが終了するまで無限に待ち続けます。その間、ウィンドウの移動やサイズ変更、プロセスの終了もできないというのは、少々困るような気もします。

これらの問題を解決する手段がないというわけではありません。例えば、以下の方法で行なうこともできるでしょう。

サンプルスクリプト main2.as

ここでは、上の2つの方法のうち、タイムアウト時間を指定する方法のみを紹介しておきましょう。

    #include "llmod.as"

    ; 起動する実行可能ファイル名(コマンドラインパラメータも含む)
    cmd = "notepad.exe"

    ; STARTUPINFO構造体を設定
    dim stinfo, 17
    stinfo.0 = 68         ; 構造体サイズ(cbメンバ)

    ; PROCESS_INFOMATION構造体を設定
    dim prinfo, 4

    ; 新しいプロセスを起動
    pm.0 = 0              ; 実行可能ファイル名はパラメータとともに指定するのでNULL
    getptr pm.1, cmd      ; 実行可能ファイル名文字列のアドレス
    pm.2 = 0
    pm.3 = 0
    pm.4 = 0
    pm.5 = 0
    pm.6 = 0
    pm.7 = 0                  ; 起動時カレントディレクトリは親プロセスと同じ
    getptr pm.8, stinfo       ; STARTUPINFO構造体アドレス
    getptr pm.9, prinfo       ; PROCESS_INFOMATION構造体アドレス
    dllproc "CreateProcessA", pm, 10, D_KERNEL
    if dllret == 0 : dialog "起動失敗" : end

    hprocess = prinfo.0       ; プロセスのハンドル
    hthread = prinfo.1        ; プライマリスレッドのハンドル

    ; プライマリスレッドのハンドルは直ちにクローズ
    dllproc "CloseHandle", hthread, 1, D_KERNEL
    hthread = 0

    ; 終了時にジャンプする
    onexit *lb_onexit

*lb_mainloop
    ; HSPウィンドウへの処理に対応するためには wait または await が必要
    await 0

    ; サブプロセスの終了を待機する
    pm.0 = hprocess           ; プロセスのハンドル
    pm.1 = 100                ; タイムアウト時間(100ミリ秒)
    dllproc "WaitForSingleObject", pm, 2, D_KERNEL
    if dllret == 0 {          ; 戻り値 WAIT_OBJECT_0
        ; シグナル状態になった(サブプロセスが終了した)場合
        dialog "サブプロセスが終了しました"
        goto *lb_quit
    } else {
        ; タイムアウト時間が経過した場合
        ; (このときの戻り値は $102 (WAIT_TIMEOUT))
        goto *lb_mainloop
    }

*lb_onexit
    ; 終了時(ウィンドウ右上の×ボタンが押されたとき)の処理
    m = {"
        サブプロセスはまだ終了していません。
        メインプロセスを終了しますか?
        (この場合サブプロセスは終了されません)
    "}
    dialog m, 2
    if stat == 6 {            ; "YES" が押された場合
        goto *lb_quit
    } else {                  ; "NO" が押された場合
        goto *lb_mainloop
    }

*lb_quit
    ; 終了処理(プロセスハンドルのクローズ)
    dllproc "CloseHandle", hprocess, 1, D_KERNEL
    hprocess = 0
    end