CreateProcess() を使って起動した GUI アプリの最上位のトップレベルウィンドウのハンドルを取得することを考えよう。起動が開始されてウィンドウが生成するまで WaitForInputIdle() で待つことができる。
List 1
unit Main; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private 宣言 } public { Public 宣言 } end; var Form1: TForm1; implementation {$R *.DFM} function EnumTopWindow(hTopWnd: HWND; lp: LPARAM): BOOL; stdcall; var p: LPDWORD; processId: DWORD; begin result := true; GetWindowThreadProcessId(hTopWnd, @processId); p := LPDWORD(lp); if (processId = p^) and (GetWindow(hTopWnd,GW_OWNER) = 0) and IsWindowVisible(hTopWnd) then begin p^ := hTopWnd; result := false; end; end; function ExecAndGetWindow(ExeAppStr:string):HWND; var SI:TStartupInfo; PI:TProcessInformation; ret,dw: DWORD; begin result := 0; FillChar(SI,SizeOf(SI),#0); SI.cb := SizeOf(TStartupInfo); SI.dwFlags := STARTF_USESHOWWINDOW; SI.wShowWindow := SW_SHOW; if not CreateProcess(nil,PChar(ExeAppStr),nil,nil,false,0,nil,nil,SI,PI) then exit else begin repeat ret := WaitForInputIdle(PI.hProcess,50); Application.ProcessMessages; until ret <> WAIT_TIMEOUT; Sleep(500); if ret = 0 then begin dw := PI.dwProcessId; EnumWindows(@EnumTopWindow, LPARAM(@dw)); if dw <> PI.dwProcessId then result := dw; end; CloseHandle(PI.hThread); CloseHandle(PI.hProcess); end; end; procedure TForm1.Button1Click(Sender: TObject); var hMainWnd,hMemoEdit:HWND; s:string; begin hMainWnd := ExecAndGetWindow('Notepad.exe'); hMemoEdit := GetWindow(hMainWnd,GW_CHILD); if hMemoEdit <> 0 then begin s := 'I have found you!!'; SendMessage(hMemoEdit,WM_SETTEXT,0,integer(PChar(s))); end else ShowMessage('メモ帳が見つかりません'); end; end.
実行して Button1 を押すとメモ帳が起動され、右図のようなテキストを表示する。すでにメモ帳が複数起動されていても、間違うことなく自分が起動したインスタンスを見つける。
起動完了は WaitForInputIdle() で待つ。
function WaitForInputIdle(hProcess: THandle; dwMilliseconds: DWORD): DWORD; stdcall;
hProcess には、プロセスハンドルを設定する。dwMilliseconds には、待ち時間を設定する。INFINITE を設定するとこのプロセスがアイドル状態になるまで復帰しない。戻り値は、
0 // 待ち条件が完了 WAIT_TIMEOUT = $00000102; // 設定待ち時間が経過した WAIT_FAILED = DWORD($FFFFFFFF); // エラーが起こった
である。ここでは、以下のようなループで起動完了を待っている。
repeat ret := WaitForInputIdle(PI.hProcess,50); Application.ProcessMessages; until ret <> WAIT_TIMEOUT;
起動完了を確認したら、プロセスハンドルをオプションパラメータとして EnumWindows() を呼び出す。コールバック関数では、トップレベルウィンドウを列挙し、そのなかでオーナーを持っていないウィンドウを見つけたらオプションパラメータに代入して呼び出しもとへ戻す。ここでは、メモ帳のトップレベルウィンドウからそのなかの Edit コントロールのハンドルを取得し、文字列を WM_SETTEXT で送っている。
追記[2003.9.24]メモ帳のような軽いアプリだと上記のような待機によってトップレベルウィンドウのハンドルの取得に成功するが、IE6 のような重いアプリだと、ハンドルの取得に失敗することがある。そのような場合は以下のようにする。
List 2
function ExecAndGetWindow(ExeAppStr:string):HWND; var SI:TStartupInfo; PI:TProcessInformation; ret,dw: DWORD; WaitCount:integer; begin result := 0; FillChar(SI,SizeOf(SI),#0); SI.cb := SizeOf(TStartupInfo); SI.dwFlags := STARTF_USESHOWWINDOW; SI.wShowWindow := SW_SHOW; if not CreateProcess(nil,PChar(ExeAppStr),nil,nil,false,0,nil,nil,SI,PI) then exit else begin repeat ret := WaitForInputIdle(PI.hProcess,50); Application.ProcessMessages; until ret <> WAIT_TIMEOUT; WaitCount := 0; repeat Sleep(100); if ret = 0 then begin dw := PI.dwProcessId; EnumWindows(@EnumTopWindow, LPARAM(@dw)); if dw <> PI.dwProcessId then result := dw; end; Inc(WaitCount); until IsWindow(dw) or (WaitCount > 5); CloseHandle(PI.hThread); CloseHandle(PI.hProcess); end; end;
このように、EnumWindows() を含むループを Sleep(100) とともに最大5回繰り返すと、必ず成功するようだ。[追記終わり]
つぎに、上記に加えて起動したプロセスが終了するのを待つことにしよう。すでに 他のプログラムを起動してそれが終了するのを待つ で示した WaitFor() 関数を少し変更を加えて使う。以下に全コードを示す。
List 3
unit Main; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private 宣言 } public { Public 宣言 } end; var Form1: TForm1; implementation {$R *.DFM} function EnumTopWindow(hTopWnd: HWND; lp: LPARAM): BOOL; stdcall; var p: LPDWORD; processId: DWORD; begin result := true; GetWindowThreadProcessId(hTopWnd, @processId); p := LPDWORD(lp); if (processId = p^) and (GetWindow(hTopWnd,GW_OWNER) = 0) and IsWindowVisible(hTopWnd) then begin p^ := hTopWnd; result := false; end; end; function ExecAndGetWindowProcessHandle(ExeAppStr:string;var PH:DWORD):HWND; var SI:TStartupInfo; PI:TProcessInformation; ret,dw: DWORD; begin result := 0; FillChar(SI,SizeOf(SI),#0); SI.cb := SizeOf(TStartupInfo); SI.dwFlags := STARTF_USESHOWWINDOW; SI.wShowWindow := SW_SHOW; if not CreateProcess(nil,PChar(ExeAppStr),nil,nil,false,0,nil,nil,SI,PI) then exit else begin repeat ret := WaitForInputIdle(PI.hProcess,50); Application.ProcessMessages; until ret <> WAIT_TIMEOUT; Sleep(500); if ret = 0 then begin dw := PI.dwProcessId; EnumWindows(@EnumTopWindow, LPARAM(@dw)); if dw <> PI.dwProcessId then result := dw; end; CloseHandle(PI.hThread); PH := PI.hProcess; end; end; procedure WaitFor(processHandle:THandle); var Msg: TMsg; ret: DWORD; begin repeat ret := MsgWaitForMultipleObjects(1,processHandle,false,INFINITE, QS_PAINT or QS_SENDMESSAGE); if ret = WAIT_FAILED then exit; if ret = (WAIT_OBJECT_0 + 1) then begin while PeekMessage(Msg, 0, WM_PAINT, WM_PAINT, PM_REMOVE) do DispatchMessage(Msg); end; until ret = WAIT_OBJECT_0; while PeekMessage(Msg,0,0,0,PM_REMOVE) do; end; procedure TForm1.Button1Click(Sender: TObject); var hMainWnd,hMemoEdit:HWND; s:string; PH:DWORD; begin hMainWnd := ExecAndGetWindowProcessHandle('Notepad.exe',PH); hMemoEdit := GetWindow(hMainWnd,GW_CHILD); if hMemoEdit <> 0 then begin s := 'I have found you!!'; SendMessage(hMemoEdit,WM_SETTEXT,0,integer(PChar(s))); WaitFor(PH); CloseHandle(PH); ShowMessage('メモ帳が終了しました'); end else begin CloseHandle(PH); ShowMessage('メモ帳が見つかりません'); exit; end; end; end.
実行してメモ帳を閉じると右図のようなメッセージが表示される。プロセスハンドルは、CreateProcess() を実行した関数から受け継いでいるので、使用後に CloseHandle() で閉じる必要がある。このようにして、別のプログラムを起動し、何かメッセージを使って遠隔操作を行ってから、それが終了するのを待つ、ということができるようになった。