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;
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).
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 ().
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.
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.
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;
-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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With