Obtener handle de una ventana a partir del PID de un proceso

Para encontrar el handle de una ventana, existen ya funciones como FindWindow, que permite encontrar una ventana conociendo su título, pero seria interesante poder limitar la búsqueda a las ventanas de un proceso determinado. Esto es lo que hace la función siguiente, encuentra el handle de una ventana, pasándole como parámetro el PID del proceso al que pertenece y opcionalmente el título de la ventana.

type
  TWindowRec = record
    Handle: THandle;
    ProcessId: Cardinal;
    WindowName: PChar;
  end;
  PWindowRec = ^TWindowRec;
 
function EnumWindowsProc(Handle: Thandle; lParam: LPARAM): BOOL; stdcall;
var
  ProcessId: Cardinal;
  Buffer: PChar;
  Size: Integer;
begin
  Result:= TRUE;
  ProcessId:= 0;
  GetWindowThreadProcessId(Handle, ProcessId);
  if ProcessId = PWindowRec(lParam).ProcessId then
  begin
    if PWindowRec(lParam).WindowName <> nil then
    begin
      Size:= GetWindowTextLength(Handle) + 1;
      GetMem(Buffer,Size);
      try
        GetWindowText(Handle,Buffer,Size);
        if StrIcomp(PWindowRec(lParam).WindowName, Buffer) = 0 then
        begin
          PWindowRec(lParam).Handle:= Handle;
          Result:= FALSE;
        end;
      finally
        Freemem(Buffer);
      end;
    end else
    begin
      PWindowRec(lParam).Handle:= Handle;
      Result:= FALSE;
    end;
  end;
end;
 
function GetWindowFromProcessId(ProcessId: Cardinal; WindowName: PChar): THandle;
var
  WindowRec: TWindowRec;
begin
  FillChar(WindowRec,Sizeof(WindowRec),0);
  WindowRec.ProcessId:= ProcessId;
  WindowRec.WindowName:= WindowName;
  EnumWindows(@EnumWindowsProc, LPARAM(@WindowRec));
  Result:= WindowRec.Handle;
end;

La función anterior (GetWindowFromProcessId) nos devolverá el handle de la ventana que buscamos, o cero si no la encuentra. El parámetro WindowName es opcional, si es igual a nil, se devolverá el primer handle que se encuentre asociado a ese proceso.

Ahora solo necesitamos el PID del proceso, la siguiente función ejecuta un programa y usando su PID busca el handle de la ventana. El parámetro WindowName sigue siendo opcional, y el parámetro TimeOut indica el tiempo (en milisegundos) que hay que esperar a que el programa cree la ventana. Si la ventana se crea antes, la función volverá inmediatamente.

function Ejecutar(Cmd: string; WindowName: PChar; Timeout: Cardinal): THandle;
var
  StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation;
  Ticks: Cardinal;
begin
  Result:= 0;
  FillChar(StartupInfo,Sizeof(StartupInfo),0);
  StartupInfo.wShowWindow:= SW_SHOW;
  Ticks:= GetTickCount;
  if CreateProcess(nil,PChar(Cmd),nil,nil, FALSE, CREATE_NEW_CONSOLE or
    NORMAL_PRIORITY_CLASS,nil,nil,StartupInfo, ProcessInfo) then
    while (Result = 0) and (GetTickCount - Ticks < TimeOut) do
    begin
      Result:= GetWindowFromProcessId(ProcessInfo.dwProcessId, WindowName);
      if Result = 0 then Sleep(200);
    end;
end;

Un ejemplo de como usar todo lo anterior seria lo siguiente. Ejecutamos el notepad, obtenemos el handle de la ventana, la ponemos al frente, y le mandamos la tecla 'a'.

procedure PonerDelante(Handle: THandle);
var
  FgThreadId  : DWORD;
  AppThreadId : DWORD;
begin
  FgThreadId  := GetWindowThreadProcessId(GetForegroundWindow, nil);
  AppThreadId := GetWindowThreadProcessId(Handle, nil);
  AttachThreadInput(AppThreadId, FgThreadId, true);
  SetForegroundWindow(Handle);
  AttachThreadInput(AppThreadId, FgThreadId, false);
end;
 
var
  H: THandle;
begin
  // Obtenemos el Handle de la ventana
  H:= Ejecutar('Notepad',nil,5000);
  if H <> 0 then
  begin
    // La ponemos por delante
    PonerDelante(H);
    // Le mandamos la letra 'a'
    keybd_event($41, 0, 0, 0);
    keybd_event($41, 0, KEYEVENTF_KEYUP, 0);
  end;
end;

Comentarios

Domingo, estoy seguro que funciona, pero, cuando escribes:

FillChar(WindowRec,Sizeof(WindowRec),0);
// O también
FillChar(StartupInfo,Sizeof(StartupInfo),0);

¿No debería ser de este otro modo?

FillChar(WindowRec,Sizeof(TWindowRec),0);
// Y también
FillChar(StartupInfo,Sizeof(TStartupInfo),0);

Me ha extrañado, más que nada, porque creo haberlo visto también como digo... pero estoy seguro de que algo se me escapa y tal vez pueda escribirse de ambas formas... o que sea más "correcto" como lo haces tú.

¿Puedes explicárnoslo a los ignorantes como yo? Gracias. :)

Pues dec, se puede utilizar de ambas maneras. De la manera que lo hago yo, Sizeof devuelve el tamaño que ocupa esa variable, mientras que si colocas el tipo, Sizeof devuelve el tamaño que ocuparía una variable de ese tipo. En este caso da igual que forma usemos, pero yo estoy acostumbrado a usar la primera porque si cambio el tipo de la variable no me tengo que estar preocupando de cambiar todas las sentencias Sizeof que hacen referencia a esa variable.

Otro caso distinto seria el siguiente:

var
  Prueba: PWindowRec;
begin
  GetMem(Prueba,Sizeof(TWindowRec));

Aquí si que no queda mas remedio que usar el tipo, ya que la variable es un puntero y no queremos saber su tamaño (normalmente solo 4 bytes) si no el tamaño de la variable a la que apunta.