Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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;
like image 423
Branko Avatar asked Dec 10 '12 06:12

Branko


People also ask

Is it possible to make a GUI or console application?

It would be very nice to be able to make Windows applications that can act as either a GUI or console application depending on how they are used (i.e. act as a GUI application if double clicked in Windows Explorer or as a console application if called from a cmd.exe window).

How do I obtain a console window handle (HWND)?

This article describes how to obtain a Console Window Handle (HWND). It may be useful to manipulate a window associated with a console application. The Win32 API provides no direct method for obtaining the window handle associated with a console application. However, you can obtain the window handle by calling FindWindow ().

How do I get the window handle in the Win32 API?

The Win32 API provides no direct method for obtaining the window handle associated with a console application. However, you can obtain the window handle by calling FindWindow (). This function retrieves a window handle based on a class name or window name. Call GetConsoleTitle () to determine the current console title.

How to get consolescreenbufferinfo using getstdhandle?

Afterwards, call AttachConsole to attach your calling process to the console of the given process, then call GetStdHandle to obtain a handle to STDOUT of your newly attached console. You can now call GetConsoleScreenBufferInfo using that handle.


2 Answers

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;
like image 107
TLama Avatar answered Oct 23 '22 18:10

TLama


-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.
like image 20
bummi Avatar answered Oct 23 '22 17:10

bummi