起動したプロセスのトップレベルウィンドウのハンドルを取得

参照元


redball.gif目次へ戻る

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.
n00201.gif

実行して 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.
n00202.gif

実行してメモ帳を閉じると右図のようなメッセージが表示される。プロセスハンドルは、CreateProcess() を実行した関数から受け継いでいるので、使用後に CloseHandle() で閉じる必要がある。このようにして、別のプログラムを起動し、何かメッセージを使って遠隔操作を行ってから、それが終了するのを待つ、ということができるようになった。


redball.gif目次へ戻る