2012-12-10 5 views
12

내 GUI 응용 프로그램에서 콘솔 응용 프로그램을 실행하고 해당 창 핸들이 필요합니다. EnumWindows()를 사용해 보았습니다. 아래 코드를 참고하십시오. 그러나 작동하지 않습니다. 목록에 내 콘솔 앱이 없습니다.내 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 - 감사합니다. 'CreateProcess' 다음에'Sleep (50)'과 함께 작동합니다. – Branko

+0

Branko, 그건 내가 싫어하는거야. 더 신뢰할만한 것을 찾고 여기에 결과를 게시하려고 노력할 것입니다. – TLama

+0

@traama - 나는 왜 좋은 해결책 (AttachConsole (PID), GetConsoleWindow, FreeConsole)을 삭제했는지 묻고 싶다. 몇 ms 동안 '수면 (sleep)'에 대해 왜 걱정합니까? – Branko

답변

10

다음 코드는 단순히 공정 (콘솔 응용 프로그램)을 생성은 AttachConsole 기능에 의해 그 연결된 콘솔이 GetConsoleWindow 기능을 사용하여 창 핸들을 소요에서 새로 생성 된 콘솔 프로세스를 연결합니다.

다음 코드의 가장 큰 약점은 콘솔이 아직 완전히 초기화되지 않은 상태에서 즉시 CreateProcess 함수가 반환된다는 것이며 콘솔을 즉시 연결하려고하면 실패합니다. 불행하게도, WaitForInputIdle 기능은 for console applications입니다. 가능한 해결 방법 중 하나는 제한된 루프 횟수로 콘솔을 연결하려고 시도하고 일단 성공하면 콘솔을 분리하고 콘솔을 분리합니다.

다음 코드와 같습니다. RunApp 함수는 콘솔 창 핸들을 반환해야합니다 (콘솔 응용 프로그램 만 실행한다고 가정). 약 기다려야합니다. 콘솔 응용 프로그램에 1 초가 걸리기 시작했습니다. Attempt 변수의 초기 값을 변경하거나 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; 

다음과 같이 할 수 있습니다.

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

감사합니다. 불행히도 두 가지 대답을 받아 들일 수는 없습니다. 그리고 제 질문을 더 명확하게 공식화 해 주신 것에 대해서도 감사드립니다. 이것은 우리에게 나쁜 영어가있는 문제입니다. – Branko

+0

내 코드가 작동하지 않는 이유를 간접적으로 설명했기 때문에 대답 을 받아 들여야합니다. 'CreateProcess' 바로 다음에 창을 열거했습니다.하지만 Bummi는 나중에 버튼을 클릭 한 후에 코드가 항상 작동하는 이유입니다. 'CreateProcess' 다음에'Sleep (50)'을 넣으면 내 코드도 작동합니다. – Branko

+1

'ProcessInfo.hProcess'와'ProcessInfo.hThread'가 아마 처리할까요? –

5

만들어 주려면, 콘솔 프로세스

-find 과정

발견 콘솔 창

-set 자막

의 창 : 당신의 lauched 응용 프로그램의 콘솔 창 제목이 방법을 변경

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

고맙습니다. 코드가 잘 작동합니다! 내 코드가 무엇이 잘못되었는지 확인해야합니다. – Branko

+2

무엇을하고 있는지, 그리고 왜 그것이 쓰여졌는지에 대해 더 설명적인 텍스트가 있으면이 투표를하겠습니다. –

+1

@TLama 개선에 감사드립니다. Marjan Venema 나중에 몇 가지 정보를 추가하겠습니다. 나는 서둘러, 미안해 ... – bummi

관련 문제