Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to Start an application and obtain a handle to it with Delphi?

I want to start an application from Delphi, and obtain a handle to it, so I can embed the main window of said application on a frame of type TFrame. So far I have tried:

Function TFrmEmbeddedExe.StartNewApplication : Boolean;
var
  SEInfo: TShellExecuteInfo;
  ExitCode : DWORD;
begin

  FillChar(SEInfo, SizeOf(SEInfo), 0) ;
  SEInfo.cbSize := SizeOf(TShellExecuteInfo) ;
  with SEInfo do
  begin
    fMask := SEE_MASK_NOCLOSEPROCESS;
    Wnd := self.Handle;
    lpFile := PChar(self.fexecuteFileName) ;//  Example could be 'C:\Windows\Notepad.exe'
    nShow := SW_SHOWNORMAL;//SW_HIDE;
  end;

  if ShellExecuteEx(@SEInfo) then
  begin
    sleep(1500);
    self.fAppWnd := FindWindow(nil, PChar(self.fWindowCaption)); //Example : 'Untitled - Notepad'
    if self.fAppWnd <> 0 then
    begin
      Windows.SetParent(self.fAppWnd, SEInfo.Wnd);
      ShowWindow(self.fAppWnd, SW_SHOWMAXIMIZED);
      result := true;
    end
    else
      result := false;

  end

  else
    result := false;
end ;

The above code actually works, but findWindow will find any given instans of the application I started. I want to embed the exact instans that I Shellexecuted. So if Notepad had been started a couple of times, there is no way I can get the correct one using FindWindow.

I have tried:

Function TfrmEmbeddedExe.CreateProcessNewApplication : Boolean;
var
zAppName: array[0..512] of char;
StartupInfo: TStartupInfo;
ProcessInfo: TProcessInformation;
Res : DWORD;
DoWait : Boolean;
begin
  DoWait := False;
  StrPCopy(zAppName, self.fexecuteFileName);  //'C:\Windows\Notepad.exe'
  FillChar(StartupInfo, Sizeof(StartupInfo), #0);
  StartupInfo.cb := Sizeof(StartupInfo);
  StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
  StartupInfo.wShowWindow := SW_SHOWNORMAL;

  if CreateProcess (zAppName,
  nil, { pointer to command line string }
  nil, { pointer to process security attributes }
  nil, { pointer to thread security attributes }
  false, { handle inheritance flag }
  CREATE_NEW_CONSOLE or { creation flags }
  NORMAL_PRIORITY_CLASS,
  nil, { pointer to new environment block }
  nil, { pointer to current directory name }
  StartupInfo, { pointer to STARTUPINFO }
  ProcessInfo) then   { pointer to PROCESS_INF }
  begin
    if DoWait then  //just set it to false... so it will never enter here
    begin
      WaitforSingleObject(ProcessInfo.hProcess, INFINITE);
      GetExitCodeProcess(ProcessInfo.hProcess, Res);
    end
    else
    begin
      self.fAppWnd := ProcessInfo.hProcess;

      Windows.SetParent(self.fAppWnd, self.Handle);
      ShowWindow(self.fAppWnd, SW_SHOWMAXIMIZED);
      CloseHandle(ProcessInfo.hProcess);
      CloseHandle(ProcessInfo.hThread);


    end;

    result := true;
  end
  else begin
    Result := false;
  end;
end;

PLEASE DO NOT RUN THE ABOVE CODE! It produces weird results involving picking a seemingly random window anywhere in all running applications and embedding that (even menu-items from the Windows start menu..)

So basically what I need is how do I start an application, and grab a handle to the application's main window.

like image 279
Jens Fudge Avatar asked Sep 05 '12 13:09

Jens Fudge


2 Answers

Here's the rough outline of what you need to do. I'll leave the coding up to you:

  1. Start your process with either ShellExecuteEx or CreateProcess. This will yield a process handle.
  2. Call WaitForInputIdle on the process handle. This gives the process a chance to load and start its message loop.
  3. Pass the process handle to GetProcessId to obtain the process ID.
  4. Use EnumWindows to enumerate the top level windows.
  5. Pass each of these windows to GetWindowThreadProcessId to check whether or not you have found the top level window of your target process.
  6. Once you find a window whose process ID matches your target process, you're done!

Don't forget to close your process handles once you are done with them.

like image 91
David Heffernan Avatar answered Oct 31 '22 14:10

David Heffernan


This code works for me:

Create a "Utils"- Unit with the following >>

....

interface

.....

function RunProg(PName, CmdLine: String; out ProcessHdl: HWND): HWND;


implementation


type
  TEnumData = record      // Record Type for Enumeration
    WHdl: HWND;
    WPid: DWORD;
    WTitle: String;
  end;
  PEnumData = ^TEnumData;  // Pointer to Record Type

// Enumeration Function for GetWinHandleFromProcId (below)
function EnumWindowsProcMatchPID(WHdl: HWND; EData: PEnumData): bool; stdcall;
var
  Wpid : DWORD;
begin
  Result := True; // continue enumeration
  GetWindowThreadProcessID(WHdl, @Wpid);
  // Filter for only visible windows, because the Pid is not unique to the Main Form
  if (EData.WPid = Wpid) AND IsWindowVisible(WHdl) then
  begin
    EData.WHdl := WHdl;
    Result := False; // stop enumeration
  end;
end;

// Find Window from Process Id and return the Window Handle
function GetWinHandleFromProcId(ProcId: DWORD): HWND;
var
  EnumData: TEnumData;
begin
  ZeroMemory(@EnumData, SizeOf(EnumData));
  EnumData.WPid := ProcId;
  EnumWindows(@EnumWindowsProcMatchPID, LPARAM(@EnumData));
  Result := EnumData.WHdl;
end;

    
// Run Program using CreateProcess >> Return Window Handle and Process Handle
function RunProg(PName, CmdLine: String; out ProcessHdl: HWND): HWND;
var
  StartInfo: TStartupInfo;
  ProcInfo: TProcessInformation;
  ProcessId : DWORD;
  WinHdl : HWND;
  bOK : boolean;
  ix : integer;
begin
  FillChar(StartInfo, SizeOf(StartInfo), 0);
  StartInfo.cb := SizeOf(StartInfo);
  StartInfo.dwFlags := STARTF_USESHOWWINDOW;
  StartInfo.wShowWindow := SW_Show;
  bOK := CreateProcess(PChar(PName), PChar(CmdLine), nil, nil, False, 0, nil, nil, StartInfo, ProcInfo);
  ProcessHdl := ProcInfo.hProcess;
  ProcessId := ProcInfo.dwProcessId;

  // Note : "WaitForInputIdle" does not always wait long enough, ... 
  //          so we combine it with a repeat - until - loop >>
  WinHdl := 0;
  if bOK then  // Process is running
  begin
    WaitForInputIdle(ProcessHdl,INFINITE);
    ix := 0;
    repeat     // Will wait (up to 10+ seconds) for a program that takes very long to show it's main window
      WinHdl := GetWinHandleFromProcId(ProcessId);
      Sleep(25);
      inc(ix);
    until (WinHdl > 0) OR (ix > 400);  // Got Handle OR Timeout
  end;

  Result := WinHdl;
  CloseHandle(ProcInfo.hThread);
end;

Put this in your main program that uses the "Utils"- Unit >>

var
  SlaveWinHdl : HWND;  // Slave Program Window Handle
  SlaveProcHdl : HWND;  // Slave Program Process Handle

// Button to run Notepad - Returning Window Handle and Process Handle
procedure TForm1.Button1Click(Sender: TObject);
var
  Pname, Pcmnd: string;
begin
  Pname := 'C:\WINDOWS\system32\notepad.exe';
  Pcmnd := '';
  SlaveWinHdl := RunProg(Pname, Pcmnd, SlaveProcHdl);
end;

// Button to Close program using Window Handle
procedure TForm1.Button2Click(Sender: TObject);
begin
  PostMessage(SlaveWinHdl, WM_CLOSE, 0, 0);
end;

// Button to Close program using Process Handle
procedure TForm1.Button3Click(Sender: TObject);
begin
  TerminateProcess(SlaveProcHdl, STILL_ACTIVE);
  CloseHandle(SlaveProcHdl);
end;

So there you have it, a complete solution of how to Run an external program, and then Close it by using either the Window Handle or Process Handle.

Extra Bonus: Sometimes you have to find the handles for a program that is already running. You can find it based on the Window- Title with the following code (added to your “Utils” unit) >>

function EnumWindowsProcMatchTitle(WHdl: HWND;  EData: PEnumData): bool; stdcall; 
var
  WinTitle: array[0..255] of char;
  Wpid : DWORD;
begin
  Result := True; // continue enumeration
  GetWindowText(WHdl, WinTitle, 256);
  if (Pos(EData.WTitle, StrPas(WinTitle)) <> 0) then // Will also match partial title
  begin
    EData.WHdl := WHdl;
    GetWindowThreadProcessID(WHdl, @Wpid);
    EData.WPid := Wpid;
    Result := False; // stop enumeration
  end;
end;

function GetHandlesFromWinTitle(WinTitle: String; out ProcHdl : HWND): HWND;  
var
  EnumData: TEnumData;
begin
  ZeroMemory(@EnumData, SizeOf(EnumData));
  EnumData.WTitle := WinTitle;
  EnumWindows(@EnumWindowsProcMatchTitle, LPARAM(@EnumData));
  ProcHdl := OpenProcess(PROCESS_ALL_ACCESS,False,EnumData.WPid);
  Result := EnumData.WHdl;
end;

And call it (from your main program), like this >>

strWT := ‘MyList.txt – Notepad’;  // example of Notepad Title
SlaveWinHdl  :=  GetHandlesFromWinTitle(strWT, SlaveProcHdl);
like image 20
Johan Avatar answered Oct 31 '22 14:10

Johan