在Delphi 2010中,有没有一种方法可以使用ShellExecute启动一个应用程序,然后将该应用程序停靠在另一个应用程序内?
也就是说,在Delphi编写的程序A包含1个窗体。当显示该窗体时,会启动C#编写的程序B,并将其客户端停靠在程序A的窗体中?
保罗
也就是说,在Delphi编写的程序A包含1个窗体。当显示该窗体时,会启动C#编写的程序B,并将其客户端停靠在程序A的窗体中?
保罗
是的,你可以这样做。你需要获取另一个进程中主窗体的窗口句柄(调用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.
是的,我可以访问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;
ShellExecuteEx
,然后进程句柄将在传递给该函数的结构体中返回。不管怎样,对于这个问题,我会使用CreateProcess
代替。 - David Heffernan
EnumWindows
是常用的方式。在枚举过程中调用GetWindowThreadProcessId
并将其与新进程的进程ID进行匹配。 - David HeffernanGetProcessId
)。确保你知道进程句柄和进程ID之间的区别。 - David Heffernan