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.
Here's the rough outline of what you need to do. I'll leave the coding up to you:
ShellExecuteEx
or CreateProcess
. This will yield a process handle.WaitForInputIdle
on the process handle. This gives the process a chance to load and start its message loop.GetProcessId
to obtain the process ID.EnumWindows
to enumerate the top level windows.GetWindowThreadProcessId
to check whether or not you have found the top level window of your target process.Don't forget to close your process handles once you are done with them.
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);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With