【问题标题】:Start program and dock with an existing program Delphi 2010启动程序并与现有程序对接 Delphi 2010
【发布时间】:2011-03-07 07:13:59
【问题描述】:

在 Delphi 2010 中有没有办法使用 ShellExecute 启动应用程序,然后将该应用程序停靠在另一个应用程序中?

即用 Delphi 编写的程序 A 包含 1 种形式。当窗体显示时,用 C# 编写的程序 B 启动并将客户端停靠到程序 A 的窗体中?

保罗

【问题讨论】:

    标签: delphi delphi-2010


    【解决方案1】:

    是的,您可以这样做。您需要在另一个进程中获取主窗体的窗口句柄(调用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 匹配。
    • 我假设 shellexecute 会给我 processid?
    • 它返回一个进程句柄,您可以从中获取进程ID(GetProcessId)。确保您知道进程句柄和进程 ID 之间的区别。
    • 好的,谢谢,我今天要看看这个。当我完成所有这些工作时,这是否意味着 c# 程序不会出现在任务栏或 alt 选项卡中?我想让用户认为他们使用的是 1 个程序而不是 2 个
    • @Paul 无论如何,您可能都必须修改 C# 程序的窗口样式,您可以做的一件事就是将其从任务栏中删除。你有 C# 程序的源代码吗?
    【解决方案2】:

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

    我需要一个不受语言影响的解决方案,但是任何以这种方式加载的程序都将是我们编写的程序

    GetProcessID 返回 0?

    我现在所做的就是在Delphi中制作2个程序,1个调用另一个

    然后我想让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
    • @Paul 我可以看到您是 Stack Overflow 的新手,但是添加一个不能回答问题的答案是错误的。这应该是对我的回答的 cmets、对问题的编辑,甚至是一个新问题。
    • @Paul 既然您说您可以访问 C# 程序的源代码,您可能会发现最容易安排它以无边界、不可调整大小的模式、没有系统菜单等运行。这样一旦它被安置在您的 Delphi 主机中,用户将无法对它做奇怪的事情。
    • 谢谢大卫,我之前去回复你的其他评论时没有看到添加评论按钮对不起
    • 好的,谢谢,这只是让它透明的一个例子
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多