5

我想从 Delphi 启动一个应用程序,并获得它的句柄,所以我可以将所述应用程序的主窗口嵌入到 TFrame 类型的框架上。到目前为止,我已经尝试过:

Function TFrmEmbeddedExe.StartNewApplication : Boolean;
var
  SEInfo: TShellExecuteInfo;
  ExitCode : DWORD;
begin

  FillChar(SEInfo, SizeOf(SEInfo), 0) ;
  SEInfo.cbSize := SizeOf(TShellExecuteInfo) ;
  with SEInfo do
  begin
    fMask := SEE_MASK_NOCLOSEPROCESS;
    Wnd := self.Handle;
    lpFile := PChar(self.fexecuteFileName) ;//  Example could be 'C:\Windows\Notepad.exe'
    nShow := SW_SHOWNORMAL;//SW_HIDE;
  end;

  if ShellExecuteEx(@SEInfo) then
  begin
    sleep(1500);
    self.fAppWnd := FindWindow(nil, PChar(self.fWindowCaption)); //Example : 'Untitled - Notepad'
    if self.fAppWnd <> 0 then
    begin
      Windows.SetParent(self.fAppWnd, SEInfo.Wnd);
      ShowWindow(self.fAppWnd, SW_SHOWMAXIMIZED);
      result := true;
    end
    else
      result := false;

  end

  else
    result := false;
end ;

上面的代码确实有效,但findWindow会找到我启动的应用程序的任何给定实例。我想嵌入我 Shell 执行的确切实例。因此,如果记事本已经启动了几次,我就无法使用 FindWindow 获得正确的记事本。

我试过了:

Function TfrmEmbeddedExe.CreateProcessNewApplication : Boolean;
var
zAppName: array[0..512] of char;
StartupInfo: TStartupInfo;
ProcessInfo: TProcessInformation;
Res : DWORD;
DoWait : Boolean;
begin
  DoWait := False;
  StrPCopy(zAppName, self.fexecuteFileName);  //'C:\Windows\Notepad.exe'
  FillChar(StartupInfo, Sizeof(StartupInfo), #0);
  StartupInfo.cb := Sizeof(StartupInfo);
  StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
  StartupInfo.wShowWindow := SW_SHOWNORMAL;

  if CreateProcess (zAppName,
  nil, { pointer to command line string }
  nil, { pointer to process security attributes }
  nil, { pointer to thread security attributes }
  false, { handle inheritance flag }
  CREATE_NEW_CONSOLE or { creation flags }
  NORMAL_PRIORITY_CLASS,
  nil, { pointer to new environment block }
  nil, { pointer to current directory name }
  StartupInfo, { pointer to STARTUPINFO }
  ProcessInfo) then   { pointer to PROCESS_INF }
  begin
    if DoWait then  //just set it to false... so it will never enter here
    begin
      WaitforSingleObject(ProcessInfo.hProcess, INFINITE);
      GetExitCodeProcess(ProcessInfo.hProcess, Res);
    end
    else
    begin
      self.fAppWnd := ProcessInfo.hProcess;

      Windows.SetParent(self.fAppWnd, self.Handle);
      ShowWindow(self.fAppWnd, SW_SHOWMAXIMIZED);
      CloseHandle(ProcessInfo.hProcess);
      CloseHandle(ProcessInfo.hThread);


    end;

    result := true;
  end
  else begin
    Result := false;
  end;
end;

请不要运行上述代码!它会产生奇怪的结果,包括在所有正在运行的应用程序中的任意位置选择一个看似随机的窗口并将其嵌入(甚至是 Windows 开始菜单中的菜单项......)

所以基本上我需要的是如何启动应用程序,并获取应用程序主窗口的句柄。

4

2 回答 2

10

这是您需要做的事情的大致轮廓。我将把编码留给你:

  1. ShellExecuteEx使用或开始您的流程CreateProcess。这将产生一个进程句柄。
  2. 调用WaitForInputIdle进程句柄。这使进程有机会加载并启动其消息循环。
  3. 传递进程句柄以GetProcessId获取进程ID。
  4. 用于EnumWindows枚举顶层窗口。
  5. 将这些窗口中的每一个传递给以GetWindowThreadProcessId检查您是否找到了目标进程的顶级窗口。
  6. 一旦你找到一个进程 ID 与你的目标进程匹配的窗口,你就完成了!

完成后不要忘记关闭进程句柄。

于 2012-09-05T14:00:02.867 回答
0

这段代码对我有用:

创建一个“Utils”- 具有以下内容的单元 >>

....

interface

.....

function RunProg(PName, CmdLine: String; out ProcessHdl: HWND): HWND;


implementation


type
  TEnumData = record      // Record Type for Enumeration
    WHdl: HWND;
    WPid: DWORD;
    WTitle: String;
  end;
  PEnumData = ^TEnumData;  // Pointer to Record Type

// Enumeration Function for GetWinHandleFromProcId (below)
function EnumWindowsProcMatchPID(WHdl: HWND; EData: PEnumData): bool; stdcall;
var
  Wpid : DWORD;
begin
  Result := True; // continue enumeration
  GetWindowThreadProcessID(WHdl, @Wpid);
  // Filter for only visible windows, because the Pid is not unique to the Main Form
  if (EData.WPid = Wpid) AND IsWindowVisible(WHdl) then
  begin
    EData.WHdl := WHdl;
    Result := False; // stop enumeration
  end;
end;

// Find Window from Process Id and return the Window Handle
function GetWinHandleFromProcId(ProcId: DWORD): HWND;
var
  EnumData: TEnumData;
begin
  ZeroMemory(@EnumData, SizeOf(EnumData));
  EnumData.WPid := ProcId;
  EnumWindows(@EnumWindowsProcMatchPID, LPARAM(@EnumData));
  Result := EnumData.WHdl;
end;

    
// Run Program using CreateProcess >> Return Window Handle and Process Handle
function RunProg(PName, CmdLine: String; out ProcessHdl: HWND): HWND;
var
  StartInfo: TStartupInfo;
  ProcInfo: TProcessInformation;
  ProcessId : DWORD;
  WinHdl : HWND;
  bOK : boolean;
  ix : integer;
begin
  FillChar(StartInfo, SizeOf(StartInfo), 0);
  StartInfo.cb := SizeOf(StartInfo);
  StartInfo.dwFlags := STARTF_USESHOWWINDOW;
  StartInfo.wShowWindow := SW_Show;
  bOK := CreateProcess(PChar(PName), PChar(CmdLine), nil, nil, False, 0, nil, nil, StartInfo, ProcInfo);
  ProcessHdl := ProcInfo.hProcess;
  ProcessId := ProcInfo.dwProcessId;

  // Note : "WaitForInputIdle" does not always wait long enough, ... 
  //          so we combine it with a repeat - until - loop >>
  WinHdl := 0;
  if bOK then  // Process is running
  begin
    WaitForInputIdle(ProcessHdl,INFINITE);
    ix := 0;
    repeat     // Will wait (up to 10+ seconds) for a program that takes very long to show it's main window
      WinHdl := GetWinHandleFromProcId(ProcessId);
      Sleep(25);
      inc(ix);
    until (WinHdl > 0) OR (ix > 400);  // Got Handle OR Timeout
  end;

  Result := WinHdl;
  CloseHandle(ProcInfo.hThread);
end;

把它放在使用“Utils”的主程序中 - 单元>>

var
  SlaveWinHdl : HWND;  // Slave Program Window Handle
  SlaveProcHdl : HWND;  // Slave Program Process Handle

// Button to run Notepad - Returning Window Handle and Process Handle
procedure TForm1.Button1Click(Sender: TObject);
var
  Pname, Pcmnd: string;
begin
  Pname := 'C:\WINDOWS\system32\notepad.exe';
  Pcmnd := '';
  SlaveWinHdl := RunProg(Pname, Pcmnd, SlaveProcHdl);
end;

// Button to Close program using Window Handle
procedure TForm1.Button2Click(Sender: TObject);
begin
  PostMessage(SlaveWinHdl, WM_CLOSE, 0, 0);
end;

// Button to Close program using Process Handle
procedure TForm1.Button3Click(Sender: TObject);
begin
  TerminateProcess(SlaveProcHdl, STILL_ACTIVE);
  CloseHandle(SlaveProcHdl);
end;

所以你有了它,一个如何运行外部程序的完整解决方案,然后使用窗口句柄或进程句柄关闭它。

额外奖励:有时您必须找到已经运行的程序的句柄。您可以使用以下代码根据 Window- Title 找到它(添加到您的“Utils”单元)>>

function EnumWindowsProcMatchTitle(WHdl: HWND;  EData: PEnumData): bool; stdcall; 
var
  WinTitle: array[0..255] of char;
  Wpid : DWORD;
begin
  Result := True; // continue enumeration
  GetWindowText(WHdl, WinTitle, 256);
  if (Pos(EData.WTitle, StrPas(WinTitle)) <> 0) then // Will also match partial title
  begin
    EData.WHdl := WHdl;
    GetWindowThreadProcessID(WHdl, @Wpid);
    EData.WPid := Wpid;
    Result := False; // stop enumeration
  end;
end;

function GetHandlesFromWinTitle(WinTitle: String; out ProcHdl : HWND): HWND;  
var
  EnumData: TEnumData;
begin
  ZeroMemory(@EnumData, SizeOf(EnumData));
  EnumData.WTitle := WinTitle;
  EnumWindows(@EnumWindowsProcMatchTitle, LPARAM(@EnumData));
  ProcHdl := OpenProcess(PROCESS_ALL_ACCESS,False,EnumData.WPid);
  Result := EnumData.WHdl;
end;

并调用它(从你的主程序),像这样>>

strWT := ‘MyList.txt – Notepad’;  // example of Notepad Title
SlaveWinHdl  :=  GetHandlesFromWinTitle(strWT, SlaveProcHdl);
于 2021-11-17T01:30:29.230 回答