DEV Community

Cover image for Launching Executables and Switching Focus in Delphi
Sean Drew
Sean Drew

Posted on

Launching Executables and Switching Focus in Delphi

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; 
Enter fullscreen mode Exit fullscreen mode

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; 
Enter fullscreen mode Exit fullscreen mode

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; 
Enter fullscreen mode Exit fullscreen mode

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)