启动程序并与现有程序对接 Delphi 2010

3
在Delphi 2010中,有没有一种方法可以使用ShellExecute启动一个应用程序,然后将该应用程序停靠在另一个应用程序内?
也就是说,在Delphi编写的程序A包含1个窗体。当显示该窗体时,会启动C#编写的程序B,并将其客户端停靠在程序A的窗体中?
保罗
2个回答

6

是的,你可以这样做。你需要获取另一个进程中主窗体的窗口句柄(调用EnumWindows)。然后调用SetParent将该窗口设置为你的窗口的子窗口。

你可能希望修改窗口样式、位置等。在尝试查找新进程中的窗口句柄之前,还要调用WaitForInputIdle。你必须给新进程一个启动的机会。

现在你拥有了一个相当奇怪的东西。在一个容器内,你有两个进程。每个进程都有自己的UI线程。你可以同时显示和交互两个模态对话框。你真的可以玩得很开心!


编辑

为了好玩,我尝试写了一个简单的 Delphi 应用程序来完成这个任务。它非常脆弱,只能处理非常基本的应用程序。我认为你可能需要花费很长时间来使其运行良好,并最终得到不令人满意的结果。如果我是你,我会寻找其他解决方案,特别是因为你有这个 C# 应用程序的源代码。你肯定可以将其功能公开为 ActiveX 吧?

无论如何,为了让你好玩,我提供以下极不成熟的代码:

program AppHost;

uses
  Windows, Messages, SysUtils, Forms, Controls, ComCtrls;

{$R *.res}

procedure ResizePage(Page: TTabSheet);
var
  hwnd: Windows.HWND;
  Rect: TRect;
begin
  hwnd := Page.Tag;
  Rect := Page.ClientRect;
  MoveWindow(hwnd, Rect.Left, Rect.Top, Rect.Right-Rect.Left, Rect.Bottom-Rect.Top, True);
end;

type
  PEnumData = ^TEnumData;
  TEnumData = record
    ProcessID: DWORD;
    hwnd: HWND;
  end;

function EnumWindowsProc(hwnd: HWND; lParam: LPARAM): BOOL; stdcall;
var
  ProcessId: DWORD;
  EnumData: PEnumData;
begin
  EnumData := PEnumData(lParam);
  GetWindowThreadProcessId(hwnd, ProcessId);
  if EnumData.ProcessID=ProcessID then begin
    EnumData.hwnd := hwnd;
    Result := False;
    exit;
  end;
  Result := True;
end;

procedure Absorb(PageControl: TPageControl; const App: string; StartupInfo: TStartupInfo);
var
  Page: TTabSheet;
  ProcessInformation: TProcessInformation;
  EnumData: TEnumData;
begin
  Page := TTabSheet.Create(PageControl);
  Page.PageControl := PageControl;
  Page.Caption := ChangeFileExt(ExtractFileName(App), '');
  CreateProcess(PChar(App), nil, nil, nil, False, 0, nil, nil, StartupInfo, ProcessInformation);
  WaitForInputIdle(ProcessInformation.hProcess, INFINITE);
  EnumData.ProcessID := ProcessInformation.dwProcessId;
  EnumData.hwnd := 0;
  EnumWindows(@EnumWindowsProc, LPARAM(@EnumData));
  Page.Tag := Integer(EnumData.hwnd);
  SetParent(HWND(Page.Tag), Page.Handle);
  ResizePage(Page);
end;

type
  TEventProvider = class
  private
    FForm: TForm;
    FPageControl: TPageControl;
    procedure FormResize(Sender: TObject);
  public
    constructor Create(Form: TForm; PageControl: TPageControl);
  end;

{ TEventProvider }

constructor TEventProvider.Create(Form: TForm; PageControl: TPageControl);
begin
  inherited Create;
  FForm := Form;
  FPageControl := PageControl;
  FForm.OnResize := FormResize;
end;

procedure TEventProvider.FormResize(Sender: TObject);
var
  i: Integer;
begin
  for i := 0 to FPageControl.PageCount-1 do begin
    ResizePage(FPageControl.Pages[i]);
  end;
end;

procedure Main(Form: TForm);
var
  StartupInfo: TStartupInfo;
  PageControl: TPageControl;
begin
  Form.ClientHeight := 600;
  Form.ClientWidth := 800;
  Form.Caption := 'All your processes are belong to us';
  PageControl := TPageControl.Create(Form);
  PageControl.Parent := Form;
  PageControl.Align := alClient;
  StartupInfo.cb := SizeOf(StartupInfo);
  GetStartupInfo(StartupInfo);
  Absorb(PageControl, 'C:\Windows\Notepad.exe', StartupInfo);
  Absorb(PageControl, 'C:\Program Files\CommandLine\depends.exe', StartupInfo);
  TEventProvider.Create(Form, PageControl);
end;

var
  Form: TForm;

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm, Form);
  Main(Form);
  Application.Run;
  Form.Free;
end.

EnumWindows是常用的方式。在枚举过程中调用GetWindowThreadProcessId并将其与新进程的进程ID进行匹配。 - David Heffernan
我假设shellexecute会给我进程ID? - Paul Saxton
1
它返回一个进程句柄,你可以从中获取进程ID(GetProcessId)。确保你知道进程句柄和进程ID之间的区别。 - David Heffernan
非常感谢您的代码示例David,我刚看到它,所以明天会在办公室尝试。 - Paul Saxton
这个想法是,我将嵌入到我的主应用程序中的程序可以用多种语言编写,例如c#、Silverlight等,这将使ActiveX路线更加复杂。 - Paul Saxton
显示剩余3条评论

1

是的,我可以访问C#程序

我需要一个无论语言如何都能正常工作的解决方案,但以这种方式加载的任何程序都是我们编写的程序

GetProcessID返回0?

到目前为止,我所做的一切就是在Delphi中制作了两个程序,其中一个调用另一个

然后,我想让dockapp2停靠在dockapp1内部,并且用户不会意识到这是一个单独的程序。

GetProcessID返回0并不理想!

procedure TForm2.BitBtn1Click(Sender: TObject);
var 
n: Integer;
n2: Integer;
begin
  n := ShellExecute(0, 'open', PChar('c:\temp\dockapp2\dockapp2.exe'), nil, nil,     SW_SHOWNORMAL); 
  n2:= GetProcessId(n);
 Caption := IntToStr(n2);
end;

ShellExecute无法处理进程句柄。你的代码是无效的。你需要使用ShellExecuteEx,然后进程句柄将在传递给该函数的结构体中返回。不管怎样,对于这个问题,我会使用CreateProcess代替。 - David Heffernan
1
@Paul,我看得出你是 Stack Overflow 的新手,但是添加一个与问题无关的答案是错误的。这应该是对我的回答进行评论、对问题进行编辑,甚至是提出一个新问题。 - David Heffernan
@Paul,由于您说您可以访问C#程序的源代码,因此您可能会发现最简单的方法是将其运行在无边框、不可调整大小的模式下,没有系统菜单等。这样一来,一旦它被放置在您的Delphi主机中,用户就无法对它进行奇怪的操纵。 - David Heffernan
谢谢David,之前当我回复你的另一条评论时,我没有看到添加评论按钮,很抱歉。 - Paul Saxton
好的,我们可以做到,谢谢。这只是一个让它透明的问题。 - Paul Saxton
用户不知道它的两个程序。 - Paul Saxton

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