I am currently using Delphi to provide support, implement enhancements, and resolve bugs in legacy monolithic Windows applications. One key enhancement involved creating a launch pad to manage multiple pre-existing external executables, requiring seamless interaction with external programs. Whether it’s launching secondary tools like an image importer or bringing an already running application to the foreground, Delphi offers a powerful suite of tools to facilitate these tasks. By leveraging the Windows API and Delphi’s advanced process management capabilities, I can ensure smooth and efficient integration with external applications.
In this write up, I show how to achieve this functionality using Windows API and Delphi code.
Key Steps
Check if the Process is Running
Use the "IsProcessRunning" function to determine if the target application is already running. This function scans the active processes and matches their executable names with the desired process name.
Find the Window Associated with the Process
If the process is running, use the "FindWindowByProcess" function to locate the window handle of the application.
Bring the Application to the Foreground
Once the window handle is retrieved, the "SetForegroundWindow" API is used to bring the application window to the front. If minimized, the "ShowWindow" API restores it.
Launch the Application if it is Not Running
If the application is not running, the "ShellExecuteEx" API launches it. After launching, the program waits for the application window to appear and then brings it to the foreground.
Example Code
The following code implements the steps for a button click event in my Delphi application. This serves as the core logic and relies on two supporting functions.
procedure TForm1.btnMyButtonClientEvent(Sender: TObject);
var
ExecInfo: TShellExecuteInfo; // structure to define execution parameters
Handle: HWND; // handle to the window of the external application
begin
// check if the process "TheExternalProgram.exe" is already running
if IsProcessRunning('TheExternalProgram.exe') then
begin
// ff the process is running, find its window by its title
Handle := FindWindow(nil, 'Extenal Program Window Title');
if Handle <> 0 then
begin
// ff the window is found, restore it if minimized
ShowWindow(Handle, SW_RESTORE);
// bring the application window to the foreground
SetForegroundWindow(Handle);
end;
end
else
begin
// if the external executable is not running then prepare to launch it
ZeroMemory(@ExecInfo, SizeOf(ExecInfo)); // clear the structure
ExecInfo.cbSize := SizeOf(ExecInfo); // set the size of the structure
ExecInfo.fMask := SEE_MASK_NOCLOSEPROCESS; // ensure process handle remains open after launch
ExecInfo.lpFile := 'c:\thepath\TheExternalProgram.exe'; // path to the executable
ExecInfo.nShow := SW_SHOWNORMAL; // show the application normally
// attempt to launch the executable
if ShellExecuteEx(@ExecInfo) then
begin
// wait for the external exe main window to appear
repeat
Handle := FindWindow(nil, 'Extenal Program Window Title');
Sleep(100); // pause for 100ms to allow the external exe to initialize
until (Handle <> 0); // exit loop once the window handle is found
// restore the external exe window if minimized and bring it to the foreground
ShowWindow(Handle, SW_RESTORE);
SetForegroundWindow(Handle);
end
else
begin
// show an error message ff launching the external exe fails
MessageDlg('Failed to launch TheExternalProgram.exe', mtInformation, [mbOk], 0);
end;
end;
end;
Supporting Functions
1. Checking if the Process is Running
This supporting code is for the "IsProcessRunning" function. This function checks whether a specific process, identified by its executable name, is currently running on the system. It takes a process name as input, takes a snapshot of all running processes, and compares each process name to the target. If it finds a match, the function returns True, indicating the process is running; otherwise, it returns False.
function IsProcessRunning(const AProcessName: string): Boolean;
var
SnapShot: THandle; // handle to the process snapshot
ProcessEntry: TProcessEntry32; // structure to store process information
begin
Result := False; // default to process not running
SnapShot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); // take a snapshot of all processes
if SnapShot = INVALID_HANDLE_VALUE then Exit;
ProcessEntry.dwSize := SizeOf(TProcessEntry32); // initialize the structure size
if Process32First(SnapShot, ProcessEntry) then // iterate through the processes in the snapshot
begin
repeat
// compare each process name with the target process name (case-insensitive)
if SameText(ProcessEntry.szExeFile, AProcessName) then
begin
Result := True; // process is running
Break;
end;
until not Process32Next(SnapShot, ProcessEntry); // move to the next process
end;
CloseHandle(SnapShot); // release the snapshot handle
end;
2. Finding the Window Associated with a Process
This supporting code is for the "FindWindowByProcess" function. This function searches for a window that is associated with a running process by matching the process name. It iterates through all processes and their windows, comparing the process ID of each window to the target process. If a match is found, it returns the handle of the corresponding window.
function FindWindowByProcess(const ProcessName: string): HWND;
var
Snapshot: THandle; // handle to the process snapshot
ProcessEntry: TProcessEntry32; // Structure to store process information
Handle: HWND; // handle to the window
begin
Result := 0; // default to no window found
Snapshot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); // take a snapshot of all processes
if Snapshot = INVALID_HANDLE_VALUE then Exit;
ProcessEntry.dwSize := SizeOf(ProcessEntry); // initialize the structure size
// iterate through the processes in the snapshot
if Process32First(Snapshot, ProcessEntry) then
begin
repeat
// check if the process name matches the target name (case-insensitive)
if AnsiCompareText(ProcessEntry.szExeFile, ProcessName) = 0 then
begin
// iterate through all top-level windows
Handle := FindWindow(nil, nil);
while Handle <> 0 do
begin
// check if the window belongs to the target process
if GetWindowThreadProcessId(Handle, nil) = ProcessEntry.th32ProcessID then
begin
Result := Handle; // found the window handle
Break;
end;
Handle := GetWindow(Handle, GW_HWNDNEXT); // move to the next window
end;
Break;
end;
until not Process32Next(Snapshot, ProcessEntry); // move to the next process
end;
CloseHandle(Snapshot); // release the snapshot handle
end;
A Brief Note About the Order of Things
In Delphi, the "IsProcessRunning" and "FindWindowByProcess" functions must be defined before the "btnMyButtonClick" event handler in the code. This order is essential because the button click event needs to reference these functions and they must be available at the time the event is triggered. By defining these functions earlier in my code, I ensure that the button click handler can successfully call them to check if the process is running and to find the associated window.
Conclusion
Incorporating external executables and managing their interactions within a Delphi application is essential when working with legacy systems. By leveraging Delphi’s integration with the Windows API and utilizing key API functions such as "ShellExecuteEx", "ShowWindow", and "SetForegroundWindow", I can easily check if the external program is running, bring its window to the foreground if necessary, and launch it if it’s not already active.
Top comments (0)