Branko
Branko

Reputation: 1458

How to get handle of a console window launched from my GUI application?

In my GUI app I run console app and need handle of its window. I tried with EnumWindows(), see code below, but it does not work. On list there is no my console app.

type
  TEnumWindowsData = record
    ProcessId: Cardinal;
    WinHandle: THandle;
    List: TStrings;                 // For test only
  end;
  PEnumWindowsData = ^TEnumWindowsData;

function FindWindow(hWnd: THandle; lParam: LPARAM): BOOL; stdcall;
var
  ParamData: TEnumWindowsData;
  ProcessId: Cardinal;
  WinTitle: array[0..200] of Char;  // For test only
begin
  ParamData := PEnumWindowsData(lParam)^;
  GetWindowThreadProcessId(hWnd, ProcessId);
  if ProcessId <> ParamData.ProcessId then
    Result := True
  else begin
    ParamData.WinHandle := hWnd;
    Result := False;
  end;
  // For test only
  GetWindowText(hWnd, WinTitle, Length(WinTitle) - 1);
  ParamData.List.Add(IntToStr(ProcessId) + ' ' + IntToStr(hWnd) + ' ' + WinTitle);
end;

procedure TForm1.Button1Click(Sender: TObject);

  function RunApp(const AProgram: string): Cardinal;
  var
    StartupInfo: TStartupInfo;
    ProcessInformation: TProcessInformation;
  begin
    Result := 0;
    ...
    if CreateProcess(nil, PChar(AProgram), nil, nil, False,
          NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInformation)
    then
      Result := ProcessInformation.dwProcessId;
    ...
  end;

var
  ParamData: TEnumWindowsData;
begin
  ParamData.ProcessId := RunApp('cmd.exe /C D:\TMP\TEST.exe');
  ParamData.WinHandle := 0;
  ParamData.List := Memo1.Lines;
  EnumWindows(@FindWindow, THandle(@ParamData));

  FWindowHandle := ParamData.WinHandle;
end;

Upvotes: 13

Views: 8341

Answers (2)

TLama
TLama

Reputation: 76693

The following code simply creates the process (console application), attaches your process to the newly created console by AttachConsole function and from that attached console takes the window handle using the GetConsoleWindow function.

The biggest weakness of the following code is that CreateProcess function returns immediately at the time, when the console is not yet fully initialized and when you try to attach the console immediately after, you will fail. Unfortunately, there's no WaitForInputIdle function for console applications so as one possible workaround I would choose the attempt to attach the console in some limited loop count and once it succeed, get the handle and detach the console.

In code that might look like follows. The RunApp function there should return the handle of the console window (assuming you'll run only console applications from it), and should wait approx. 1 second for the console application you've started to be attachable. You can modify this value either by changing initial value of the Attempt variable and/or by changing Sleep interval.

function GetConsoleWindow: HWND; stdcall;
  external kernel32 name 'GetConsoleWindow';
function AttachConsole(dwProcessId: DWORD): BOOL; stdcall;
  external kernel32 name 'AttachConsole';

function RunApp(const ACmdLine: string): HWND;
var
  CmdLine: string;
  Attempt: Integer;
  StartupInfo: TStartupInfo;
  ProcessInfo: TProcessInformation;
begin
  Result := 0;
  FillChar(StartupInfo, SizeOf(TStartupInfo), 0);
  FillChar(ProcessInfo, SizeOf(TProcessInformation), 0);
  StartupInfo.cb := SizeOf(TStartupInfo);
  StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
  StartupInfo.wShowWindow := SW_SHOWNORMAL;
  CmdLine := ACmdLine;
  UniqueString(CmdLine);
  if CreateProcess(nil, PChar(CmdLine), nil, nil, False,
    CREATE_NEW_CONSOLE, nil, nil, StartupInfo, ProcessInfo) then
  begin
    Attempt := 100;
    while (Attempt > 0) do
    begin
      if AttachConsole(ProcessInfo.dwProcessId) then
      begin
        Result := GetConsoleWindow;
        FreeConsole;
        Break;
      end;
      Sleep(10);
      Dec(Attempt);
    end;
    CloseHandle(ProcessInfo.hThread);
    CloseHandle(ProcessInfo.hProcess);
  end;
end;

Then you can e.g. change the title of a console window of your lauched application this way:

procedure TForm1.Button1Click(Sender: TObject);
var
  S: string;
  ConsoleHandle: HWND;
begin
  ConsoleHandle := RunApp('cmd.exe');
  if ConsoleHandle <> 0 then
  begin
    S := 'Hello! I''m your console, how can I serve ?';
    SendTextMessage(ConsoleHandle, WM_SETTEXT, 0, S);
  end;
end;

Upvotes: 10

bummi
bummi

Reputation: 27377

-Creating console process

-find the window of the process

-set caption of found console window

-write to found console

unit Unit3;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    PID: DWORD;
  public
    { Public-Deklarationen }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  AProgram: String;
  StartupInfo: TStartupInfoW;
  ProcessInfo: TProcessInformation;
begin
  AProgram := 'cmd /K Dir C:\temp';   // using /K for keeping console alive
  UniqueString(AProgram);             // ensure that AProgram is writeable by API
  ZeroMemory(@StartupInfo, SizeOf(StartupInfo)); // create minimum startup info
  StartupInfo.cb := SizeOf(StartupInfo);
  StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
  StartupInfo.wShowWindow := SW_SHOW;
  if CreateProcess(nil, PChar(AProgram), nil, nil, False,  // Create Consoleprocess
    CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo,
    ProcessInfo) then
    try
      PID := ProcessInfo.dwProcessId;  // Store  ProcessId to PID
    finally
      // close not longer required handles
      Showmessage(IntToStr(Integer(CloseHandle(ProcessInfo.hProcess))));
      Showmessage(IntToStr(Integer(CloseHandle(ProcessInfo.hThread))));
    end;
end;

type
  PEnumInfo = ^TEnumInfo;
  TEnumInfo = record ProcessID: DWORD; HWND: THandle; end;

function EnumWindowsProc(Wnd: DWORD; var EI: TEnumInfo): BOOL; stdcall;
var
  PID: DWORD;
begin
  GetWindowThreadProcessID(Wnd, @PID);  // get processID from WND of Enumeration
  // continue EnumWindowsProc if found PID is not our wished, visible and enabled  processID (EI.ProcessID)
  Result := (PID <> EI.ProcessID) or (not IsWindowVisible(WND)) or
    (not IsWindowEnabled(WND));
  if not Result then // WND found for EI.ProcessID
    EI.HWND := WND;
end;

function FindMainWindow(PID: DWORD): DWORD;
var
  EI: TEnumInfo;
begin
  //Store our processID and invalid Windowhandle to EI
  EI.ProcessID := PID;
  EI.HWND := 0;
  EnumWindows(@EnumWindowsProc, Integer(@EI));
  Result := EI.HWND;
end;

function AttachConsole(dwProcessId: DWORD): BOOL; stdcall;
  external kernel32 name 'AttachConsole';

procedure TForm1.Button2Click(Sender: TObject);
var
  Wnd: HWND;
  S: String;
begin
  if PID <> 0 then  // do we have a valid ProcessID
  begin
    Wnd := FindMainWindow(PID);
    if Wnd <> 0 then  // did we find the window handle
    begin
      S := 'Test';
      // change caption of found window
      SendMessage(Wnd, WM_SETTEXT, 0, LPARAM(@S[1]));
      if AttachConsole(PID) then   // are we able to attach to console of our proecess?
      begin
        Writeln('Here we are'); // write to attached console
        FreeConsole;  // free if not longer needed
      end;
    end;
  end;
end;

end.

Upvotes: 5

Related Questions