You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Delphi MDI程序中嵌入外部C#程序为MDI子窗体/模态对话框?

Delphi MDI程序集成C# PDF/A转换功能的可行方案

原ShellExecute+循环等待方案为什么不可行

  • 你写的repeat Sleep(300) until FileExists(...)直接卡死了Delphi的UI线程。VCL的所有界面更新、用户交互都依赖主线程处理消息循环,Sleep期间主线程根本没法响应任何操作,程序无响应是必然结果。
  • ShellExecute启动的C#程序是独立进程,它的窗口属于自己的进程空间,没法嵌入Delphi的MDI子窗体容器,也没法作为Delphi窗体的模态对话框(模态对话框必须依赖父窗口的消息循环才能锁住父窗口)。

无需重写Delphi代码的替代方案

方案1:把C#程序封装成COM组件(最优嵌入GUI)

这是唯一能让C#界面直接成为Delphi MDI子窗体/模态对话框的方案:

  1. C#端改造:
    • 项目属性里勾选「使程序集COM可见」,给程序集加上强名称(生成签名文件)。
    • 把PDF/A转换的GUI封装成类,标记[ComVisible(true)],提供ShowAsMDIChild(IntPtr parentHandle)和ShowAsModal(IntPtr parentHandle)方法。内部通过WindowInteropHelper设置窗口的父句柄,MDI子窗体还需要设置FormStyle = FormStyle.MdiChild。
    • 编译后用管理员权限执行regasm.exe /codebase YourAssembly.dll注册COM组件。
  2. Delphi端调用:
    • 打开Delphi的「导入类型库」,找到你注册的C#组件,生成对应的单元。
    • 在代码里创建COM实例,调用对应方法:
      var
        PDFAConverter: IPDFAConverter; // 导入的COM接口
      begin
        PDFAConverter := CoPDFAConverter.Create;
        // 作为MDI子窗体显示
        PDFAConverter.ShowAsMDIChild(MainForm.Handle);
        // 或者作为模态对话框显示
        // PDFAConverter.ShowAsModal(Self.Handle);
      end;
      

方案2:异步消息通知(最小改动)

如果不想把C#改成COM,只需要给C#加个命令行参数,用Windows消息通知结果:

  1. Delphi端修改:
    • 定义自定义消息:const WM_PDFA_DONE = WM_USER + 100;
    • 重写窗体的WndProc处理消息:
      procedure TEditEmailDlg.WndProc(var Message: TMessage);
      var
        tf: string;
        sl: TStringList;
      begin
        if Message.Msg = WM_PDFA_DONE then
        begin
          tf := TempFolder + 'FCDAA5F7-E26D-4C54-9514-68BDEC845AE3.Finished';
          sl := TStringList.Create;
          try
            if FileExists(tf) then
            begin
              sl.LoadFromFile(tf);
              // 这里处理转换后的文件列表
            end;
          finally
            sl.Free;
            if FileExists(tf) then DeleteFile(tf);
          end;
        end
        else
          inherited;
      end;
      
    • 启动C#时传递当前窗体句柄和临时文件路径:
      procedure TEditEmailDlg.btnAttachFileClick(Sender: TObject);
      var
        tf: string;
        params: string;
      begin
        tf := TempFolder + 'FCDAA5F7-E26D-4C54-9514-68BDEC845AE3.Finished';
        if FileExists(tf) then DeleteFile(tf);
        // 把句柄和临时文件路径传给C#
        params := Format('%d "%s"', [Self.Handle, tf]);
        ShellExecute(Self.Handle, 'open', 'C:\Program Files (x86)\GL-K-S\tools\2PDFA.exe', PChar(params), '', SW_SHOWNORMAL);
      end;
      
  2. C#端修改:
    • 读取命令行参数,拿到Delphi窗口句柄和临时文件路径。
    • 转换完成后用SendMessage发通知:
      using System.Runtime.InteropServices;
      
      [DllImport("user32.dll")]
      private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
      
      static void Main(string[] args)
      {
          // 业务逻辑:处理PDF/A转换,写入临时文件
          // ...
      
          // 通知Delphi完成
          var delphiWnd = new IntPtr(int.Parse(args[0]));
          const uint WM_PDFA_DONE = 0x400 + 100; // WM_USER+100
          SendMessage(delphiWnd, WM_PDFA_DONE, IntPtr.Zero, IntPtr.Zero);
      }
      
    这样Delphi主线程不会被阻塞,全程保持响应。

方案3:进程间通信(IPC)

如果不想用临时文件传递结果,可以用WM_COPYDATA或命名管道:

  • WM_COPYDATA:C#把文件列表打包成COPYDATASTRUCT,用SendMessage发给Delphi,Delphi在WndProc里解析数据。
  • 命名管道:Delphi启动时创建命名管道服务器,C#作为客户端连接管道,转换完成后直接把文件列表写入管道,Delphi异步读取。

总结

原方案完全不可行,核心问题是阻塞UI线程和独立进程无法嵌入GUI。优先选COM组件方案实现MDI/模态嵌入;如果不想大改C#,异步消息通知是最小成本的方案,两者都不需要重写现有Delphi业务代码。

内容的提问来源于stack exchange,提问作者Mahan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 03:40:31