Thursday, 1 December 2016

Run an application and get the handle to its window

Sometimes, when we start an application programmatically, it is useful to have a handle to it's window. The process of how to do this is described below.
1. create a new unit utils.pas:

unit utils;

interface

uses
Controls, Graphics, StdCtrls, ExtCtrls, ComCtrls, Buttons, Dialogs, Classes,
SysUtils, Windows, ShellApi, Forms;

procedure CloseMessage (process_id : Cardinal);

// get proc id
function RunCommandEx (const Cmd, Params: String) : Cardinal;

// get handle
function ExecApplication(APPName, CmdLine: String; out proc_id : Cardinal): Cardinal;

// for use with ExecApplication
function GetHandles(ThreadID: Cardinal): Cardinal;

var
WindowList: TList;

function GetWindow (Handle: Cardinal; LParam: longint): bool; stdcall;

implementation

function GetWindow (Handle: Cardinal; LParam: longint): bool; stdcall;
begin
    Result := true;
    WindowList.Add (Pointer (Handle));
end;

function ExecApplication(APPName, CmdLine: String; out proc_id : Cardinal): Cardinal;
var
    StartInfo: TStartupInfo;
    ProcInfo: TProcessInformation;
    process_id : Cardinal;
begin
    FillChar(StartInfo, SizeOf(StartInfo), 0);
    StartInfo.cb:=SizeOf(StartInfo);
    StartInfo.dwFlags:=STARTF_USESHOWWINDOW;
    StartInfo.wShowWindow:=SW_Show;
    if AppName <> '' then
    CreateProcess(PChar(APPName), PChar(CmdLine), nil, nil, False, 0, nil, nil,
        StartInfo, ProcInfo);
    Sleep(500);
    process_id := ProcInfo.dwProcessId;
    proc_id := ProcInfo.hProcess;
    Result := GetHandles(process_id);
    // CloseHandle (ProcInfo.hProcess);
    CloseHandle (ProcInfo.hThread );
end;

function GetHandles(ThreadID: Cardinal): Cardinal;
var
    i: integer;
    hnd : Cardinal;
    cpid : DWord;
begin
    Result:=0;
    WindowList := TList.Create;
    EnumWindows (@GetWindow, 0);
    for i := 0 to WindowList.Count - 1 do
    begin
        hnd := HWND (WindowList [i]);
        GetWindowThreadProcessID (hnd, @cpid);
        if ThreadID = CPID then
        begin
            Result := hnd;
            Exit;
        end;
    end;
    WindowList.Free;
end;

procedure CloseMessage (process_id : Cardinal);
var
    StatusCode : Cardinal;
begin
    if process_id > 0 then
    begin
        if GetExitCodeProcess (process_id, StatusCode) then
        begin
            if StatusCode = STILL_ACTIVE then
                TerminateProcess (process_id, StatusCode);
            CloseHandle (process_id);
        end;
    end;
end;

function RunCommandEx (const Cmd, Params: String) : Cardinal;
var
    SEI: TShellExecuteInfo;

begin
    result := 0;

    //Fill record with zero byte values
    FillChar(SEI, SizeOf(SEI), 0);

    // Set mandatory record field
    SEI.cbSize := SizeOf(SEI);

    // Ask for an open process handle
    SEI.fMask := see_Mask_NoCloseProcess;

    // Tell API which window any error dialogs should be modal to
    SEI.Wnd := Application.Handle;

    //Set up command line
    SEI.lpFile := PChar(Cmd);

    if Length (Params) > 0 then
    SEI.lpParameters := PChar(Params);

    SEI.nShow := sw_ShowNormal;

    // Try and launch child process. Raise exception on failure
    if not ShellExecuteEx(@SEI) then
        Abort;

    // Wait until process has started its main message loop
    WaitForInputIdle(SEI.hProcess, Infinite);

    result := SEI.hProcess;
end;

end.
2. Create Window Handle
Now lets get back to the main form and create two global variables,
windows_handle and proc_id, and reference utils.pas:
type
TForm1 = class(TForm)

...

private
{ Private declarations }

public
{ Public declarations }
end;

var
    Form1: TForm1;
    window_handle : cardinal;
    proc_id : cardinal;

implementation

{$R *.dfm}

uses
    utils;
3. Use in Application
The application is ran as following:
procedure TForm1.cmdRunApplicationClick(Sender: TObject);
var
    app_name : string;
    command_line : string;
    StatusCode : Cardinal;

begin

    if proc_id > 0 then
        if GetExitCodeProcess (proc_id, StatusCode) then
            if StatusCode = STILL_ACTIVE then
            begin
                MessageDlg ('Application is already loaded', mtWarning, [mbOk], 0);
                exit;
            end;

    app_name := 'C:\WINDOWS\system32\notepad.exe';

    command_line := '';

    window_handle := ExecApplication (app_name, command_line, proc_id);
end;
In the code above, we run Notepad. Here windows_handle is the handle of Notepad window.

4. put this on FormClose event:
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
    CloseMessage (proc_id);
end;

5. Example
Here are examples of what we can do with the application's handle:
procedure TForm1.cmdDoSomethingClick(Sender: TObject);
begin
    if window_handle > 0 then
        SetWindowText (window_handle, PChar ('Hello'));  // change title

    ShowWindow (window_handle, SW_SHOW);                 // activate the window
    PostMessage (window_handle, WM_CLOSE, 0, 0);         // close the window
end;

Source : Here

0 comments:

Post a Comment