如何在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子窗体/模态对话框的方案:
- C#端改造:
- 项目属性里勾选「使程序集COM可见」,给程序集加上强名称(生成签名文件)。
- 把PDF/A转换的GUI封装成类,标记
[ComVisible(true)],提供ShowAsMDIChild(IntPtr parentHandle)和ShowAsModal(IntPtr parentHandle)方法。内部通过WindowInteropHelper设置窗口的父句柄,MDI子窗体还需要设置FormStyle = FormStyle.MdiChild。 - 编译后用管理员权限执行
regasm.exe /codebase YourAssembly.dll注册COM组件。
- 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消息通知结果:
- 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;
- 定义自定义消息:
- 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); }
方案3:进程间通信(IPC)
如果不想用临时文件传递结果,可以用WM_COPYDATA或命名管道:
- WM_COPYDATA:C#把文件列表打包成
COPYDATASTRUCT,用SendMessage发给Delphi,Delphi在WndProc里解析数据。 - 命名管道:Delphi启动时创建命名管道服务器,C#作为客户端连接管道,转换完成后直接把文件列表写入管道,Delphi异步读取。
总结
原方案完全不可行,核心问题是阻塞UI线程和独立进程无法嵌入GUI。优先选COM组件方案实现MDI/模态嵌入;如果不想大改C#,异步消息通知是最小成本的方案,两者都不需要重写现有Delphi业务代码。
内容的提问来源于stack exchange,提问作者Mahan
相关产品推荐
相关产品推荐

