2012-12-10 10 views
12

W mojej aplikacji GUI uruchamiam aplikację konsolową i potrzebuję obsługi jej okna. Próbowałem z EnumWindows(), zobacz kod poniżej, ale to nie działa. Na liście nie ma mojej aplikacji konsolowej.Jak uzyskać dostęp do okna konsoli uruchomionego z mojej aplikacji GUI?

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 - Dziękuję, świetnie! Działa to dla mnie z 'Sleep (50)' po 'CreateProcess'. – Branko

+0

Branko, tego nie lubię. Postaram się znaleźć coś bardziej wiarygodnego i zamieścić tutaj wyniki. – TLama

+0

@ TLama - Chciałem zapytać cię, dlaczego usunąłeś dla mnie dobre rozwiązanie ('AttachConsole (PID), GetConsoleWindow, FreeConsole'). Dlaczego martwisz się "Sleep()" przez kilka ms? – Branko

Odpowiedz

10

Poniższy kod po prostu tworzy (aplikację konsoli) Proces, proces przywiązuje do nowo utworzonej konsoli przez AttachConsole funkcji i od tego dołączone konsola zajmuje uchwyt okna przy użyciu funkcji GetConsoleWindow.

Największą wadą poniższego kodu jest to, że funkcja CreateProcess zwraca natychmiast w momencie, gdy konsola nie jest jeszcze w pełni zainicjalizowana, a przy próbie dołączenia konsoli natychmiast po tym wystąpi błąd. Niestety, nie ma funkcji WaitForInputIdle funkcja for console applications tak, że jedno możliwe obejście wybrałbym próbę dołączenia konsoli w ograniczonej ilości pętli i gdy się uda, uzyskać uchwyt i odłączyć konsolę.

W kodzie, który może wyglądać następująco. Funkcja RunApp powinna zwrócić uchwyt okna konsoli (zakładając, że będziesz uruchamiał z niego tylko aplikacje konsoli) i powinien poczekać ok. 1 sekunda dla aplikacji konsolowej, z której zacząłeś być dołączany. Możesz zmodyfikować tę wartość, zmieniając wartość początkową zmiennej Attempt i/lub zmieniając przedział Sleep.

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; 

wtedy można na przykład zmienić tytuł okna konsoli swojej lauched aplikacji w ten sposób:

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

Dziękuję, niestety, nie mogę oznaczyć dwóch odpowiedzi jako zaakceptowanych. I dziękuję, że lepiej sformułowałeś moje pytanie, jaśniej. To jest problem nas ze złym angielskim. – Branko

+0

Muszę zaakceptować twoją odpowiedź , ponieważ wyjaśniła mi ona pośrednio, dlaczego mój kod nie zadziałał. Wyliczyłem okna natychmiast po "CreateProcess" - ale Bummi później, po kliknięciu przycisku, dlatego jego kod zawsze działa. Jeśli wstawię 'Sleep (50)' po 'CreateProcess' mój kod też działa :) – Branko

+1

Zamknij' ProcessInfo.hProcess' i 'ProcessInfo.hThread' może obsługuje? –

5

-Tworzenie proces konsoli

-find okno procesu

-Set podpis okna znaleźć konsoli

-Podaj do znalezienia konsoli

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

Dziękujemy, Twój kod działa poprawnie! Muszę zobaczyć, co jest nie tak z moim kodem :) – Branko

+2

Wezmę to do głosu, jeśli ma więcej tekstu wyjaśniającego o tym, co robi i dlaczego jest napisane tak jak jest. –

+1

@TLama dzięki za poprawę. Marjan Venema Dodam trochę informacji, później. Pospiesz się, przepraszam ... – bummi

Powiązane problemy