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)