2012-12-10 4 views
12

In meiner GUI-App ich laufe Konsolen-App und brauche Handle seines Fensters. Ich habe versucht mit EnumWindows(), siehe Code unten, aber es funktioniert nicht. Auf der Liste gibt es keine meiner Konsolen-App.Wie bekomme ich das Handle eines Konsolenfensters, das von meiner GUI-Anwendung gestartet wurde?

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; 
+0

@Tlama - Vielen Dank, sehr gut! Es funktioniert für mich mit 'Sleep (50)' nach 'CreateProcess'. – Branko

+0

Branko, das mag ich nicht. Ich werde versuchen, etwas zuverlässiger zu finden und hier die Ergebnisse zu veröffentlichen. – TLama

+0

@TLama - Ich wollte dich fragen, warum du für mich eine gute Lösung gelöscht hast ('AttachConsole (PID), GetConsoleWindow, FreeConsole'). Warum beschäftigen Sie sich für ein paar ms über 'Sleep()'? – Branko

Antwort

10

Der folgende Code erzeugt einfach die Prozess (Konsolenanwendung), legt Ihren Prozess auf die neu erstellte Konsole von AttachConsole Funktion und von der befestigten Konsole nimmt den Fenstergriff mit der GetConsoleWindow Funktion.

Die größte Schwäche des folgenden Codes ist, dass CreateProcess Funktion sofort zu der Zeit zurückgibt, wenn die Konsole noch nicht vollständig initialisiert ist und wenn Sie versuchen, die Konsole unmittelbar danach anzuschließen, werden Sie fehlschlagen. Leider gibt es keine WaitForInputIdle Funktion for console applications als eine mögliche Problemumgehung würde ich den Versuch wählen, die Konsole in einer begrenzten Anzahl von Schleifen zu verbinden und sobald es erfolgreich ist, erhalten Sie das Handle und trennen Sie die Konsole.

In Code, der wie folgt aussehen könnte. Die RunApp-Funktion sollte das Handle des Konsolenfensters zurückgeben (vorausgesetzt, dass Sie nur Konsolenanwendungen von ihm ausführen) und sollte ca. warten. 1 Sekunde für die Konsolenanwendung, die Sie bereits installiert haben. Sie können diesen Wert ändern, indem Sie entweder den Anfangswert der Variablen Attempt ändern und/oder das Intervall Sleep ändern.

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; 

Dann können Sie z.B. Ändern Sie den Titel eines Konsolenfenster Ihres lauched Anwendung auf diese Weise:

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; 
+0

Danke, leider kann ich zwei Antworten nicht als akzeptiert markieren. Und danke auch dafür, meine Frage besser, klarer formuliert zu haben. Dies ist das Problem von uns mit schlechtem Englisch. – Branko

+0

Ich muss Ihre Antwort akzeptieren weil es mir indirekt erklärt, warum mein Code nicht funktioniert hat. Ich hatte Fenster unmittelbar nach 'CreateProcess' aufgezählt - aber Bummi später, nach dem Klick auf die Taste und das ist der Grund, warum sein Code immer funktioniert. Wenn ich 'Sleep (50)' nach 'CreateProcess' setze funktioniert mein Code auch :) – Branko

+1

Schließen' ProcessInfo.hProcess' und 'ProcessInfo.hThread' vielleicht? –

5

-Erstellen Konsolprozess

-find das Fenster des Prozesses

-Set Beschriftung gefunden Konsolenfenster

-write to found Konsole

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. 
+0

Vielen Dank, Ihr Code funktioniert gut !! Ich muss sehen, was mit meinem Code falsch ist :) – Branko

+2

Ich würde dies abstimmen, wenn ich mehr erklärenden Text darüber hätte, was es macht und warum es so geschrieben wird, wie es ist. –

+1

@TLama danke für die verbesserung. Marjan Venema Ich werde später einige Informationen hinzufügen. Ich beeile mich jetzt, sorry ... – bummi

Verwandte Themen