サブプログラムの終了を待つ(2)

サンプルスクリプト

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

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

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

#uselib "kernel32.dll"
#func CloseHandle   "CloseHandle"    int
#func CreateProcess "CreateProcessA" int,int,int,int,int,int,int,int,int,int
#func WaitForSingleObject "WaitForSingleObject" int,int

#define INFINITE    0xFFFFFFFF

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

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

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

; 新しいプロセスを起動
CreateProcess 0,varptr(cmd),0,0,0,0,0,0,varptr(stinfo),varptr(prinfo)
if (stat == 0) : dialog "起動失敗" : end

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

; プライマリスレッドのハンドルは直ちにクローズ
CloseHandle hThread
hThread = 0

; サブプロセスの終了を待機する
WaitForSingleObject hProcess, INFINITE

; プロセスハンドルをクローズ
CloseHandle hProcess
hProcess = 0

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

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

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

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

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

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

これらの問題を解決するには、WaitForSingleObject関数の待ち時間として、INFINITE以外の適切なタイムアウト値(あるいは0)を指定します。そして、プロセスの終了を待機するループを作成し、関数が戻り値0 (WAIT_OBJECT_0) を返すまで繰り返し呼び出します。

この場合には、サブプロセスが終了したかどうかをループ中で監視をするのですが、ウィンドウの処理に対応するためにはこのループ中にwaitまたはawaitを実行する必要があります。この方法により、プロセス終了を監視しながら他の処理を平行して行うことも可能となります。

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

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

#uselib "kernel32.dll"
#func CloseHandle   "CloseHandle"    int
#func CreateProcess "CreateProcessA" int,int,int,int,int,int,int,int,int,int
#func WaitForSingleObject "WaitForSingleObject" int,int

#define WAIT_OBJECT_0   0

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

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

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

; 新しいプロセスを起動
CreateProcess 0,varptr(cmd),0,0,0,0,0,0,varptr(stinfo),varptr(prinfo)
if (stat == 0) : dialog "起動失敗" : end

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

; プライマリスレッドのハンドルは直ちにクローズ
CloseHandle hThread
hThread = 0

; HSPプログラム終了時にサブルーチンジャンプする
onexit gosub *lb_onexit

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

; サブプログラムの終了を待機する (タイムアウト時間:100ミリ秒)
WaitForSingleObject hProcess, 100
if (stat == WAIT_OBJECT_0) {
    ; プロセスがシグナル状態になった(サブプログラムが終了した)場合は終了
    dialog "サブプロセスが終了しました"
    goto *lb_quit
}
goto *lb_mainloop

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

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