如何使用Delphi启动一个应用程序并获取其句柄?

6

我想从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会找到我启动的任何应用程序的实例。我想嵌入我Shellexecuted的确切实例。因此,如果记事本已经启动了几次,使用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开始菜单的菜单项..)

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


6
进程句柄并不是窗口句柄。请参考https://dev59.com/kXI-5IYBdhLWcg3wYXL8。 - Sertac Akyuz
看到标题,我本来打算进来坚持这一定是一个重复的问题,但在看到你是将这个应用程序嵌入自己的应用程序中之后,确实与众不同。好问题。 - Jerry Dodge
请不要在您的帖子中添加签名。StackOverflow已经为您显示了一个 - Deanna
2个回答

12

以下是您需要做的大致概述,编码方面由您自行决定:

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

不要忘记在使用完进程句柄后关闭它们。


David Heffernan,听起来确实是前进的正确方式,等我有时间了就这么做。 非常感谢您的解释。 - Jens Fudge

2
这段代码对我来说是有效的:

创建一个名为“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;

这里提供一个完整的解决方案,教你如何运行外部程序,并使用窗口句柄或进程句柄关闭它。

额外奖励:有时候你需要找到已经在运行的程序的句柄。你可以使用以下代码(添加到你的“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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接