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

delphi, delphi-xe2, winapi, windows-7

Solution

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;

Problem

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; ```

Original source

Related problems