如何用C++/CLI封装C# DLL供非托管Pascal(Delphi)调用?
C++/CLI桥接C#与Delphi的实现验证及完整封装指南
一、当前实现方式的正确性验证
你的思路是完全可行的:C++/CLI作为托管(C#)与非托管(Delphi)代码的中间桥接层,确实比COM方案更灵活,能避开COM复杂的类型适配坑。只要你的C++/CLI项目满足以下几点,基础实现就是正确的:
- 项目类型为C++/CLI类库(而非纯C++ DLL)
- 已正确引用C# Selenium核心DLL,且能在C++/CLI代码中正常实例化C#类、调用方法
- 暴露给Delphi的导出函数用
extern "C"修饰,参数与返回值均为非托管类型(如const char*、int、void*等)
二、完整封装步骤
1. 项目结构与基础配置
- 整体分为三个部分:已完成的C# Selenium核心库、C++/CLI桥接DLL、Delphi测试项目
- C++/CLI项目关键设置:
- 右键项目→属性→常规→公共语言运行时支持:选择
公共语言运行时支持(/clr) - 链接器→高级→目标文件格式:设置为
DLL (/DLL)
- 右键项目→属性→常规→公共语言运行时支持:选择
2. 类型适配与内存管理
字符串处理
Delphi使用PAnsiChar/PWideChar,C++/CLI需完成托管字符串与非托管字符串的双向转换:
// 托管字符串转非托管宽字符 const wchar_t* ManagedToUnmanaged(String^ managedStr) { return Marshal::StringToHGlobalUni(managedStr).ToPointer(); } // 非托管宽字符转托管字符串 String^ UnmanagedToManaged(const wchar_t* unmanagedStr) { return gcnew String(unmanagedStr); }
注意:Delphi调用后需负责释放C++/CLI返回的字符串内存,或在桥接层提供专门的释放函数:
extern "C" __declspec(dllexport) void FreeString(wchar_t* str) { Marshal::FreeHGlobal(IntPtr(str)); }
对象句柄传递
C#对象无法直接传递给Delphi,需将托管对象引用包装为void*作为句柄传递:
// 全局字典保存C#驱动实例,防止被GC回收 static gcroot<Dictionary<IntPtr, IWebDriver^>^> s_DriverMap = gcnew Dictionary<IntPtr, IWebDriver^>(); // 创建ChromeDriver并返回句柄 extern "C" __declspec(dllexport) void* CreateChromeDriver() { ChromeDriver^ driver = gcnew ChromeDriver(); IntPtr handle = IntPtr(driver); s_DriverMap->Add(handle, driver); return handle.ToPointer(); } // 通过句柄获取C#驱动实例 IWebDriver^ GetDriver(void* handle) { IntPtr ptr(handle); return s_DriverMap[ptr]; } // 释放驱动实例并移除字典引用 extern "C" __declspec(dllexport) void DestroyChromeDriver(void* handle) { IntPtr ptr(handle); if (s_DriverMap->ContainsKey(ptr)) { IWebDriver^ driver = s_DriverMap[ptr]; driver->Quit(); s_DriverMap->Remove(ptr); } }
3. 导出核心Selenium功能
以打开网页、获取页面标题为例:
extern "C" __declspec(dllexport) const wchar_t* DriverGetTitle(void* driverHandle) { IWebDriver^ driver = GetDriver(driverHandle); String^ title = driver->Title; return ManagedToUnmanaged(title); } extern "C" __declspec(dllexport) void DriverNavigateTo(void* driverHandle, const wchar_t* url) { IWebDriver^ driver = GetDriver(driverHandle); driver->Navigate()->GoToUrl(UnmanagedToManaged(url)); }
4. Delphi端调用示例
type TDriverHandle = Pointer; function CreateChromeDriver: TDriverHandle; cdecl; external 'SeleniumBridge.dll'; procedure DestroyChromeDriver(Handle: TDriverHandle); cdecl; external 'SeleniumBridge.dll'; function DriverGetTitle(Handle: TDriverHandle): PWideChar; cdecl; external 'SeleniumBridge.dll'; procedure DriverNavigateTo(Handle: TDriverHandle; Url: PWideChar); cdecl; external 'SeleniumBridge.dll'; procedure FreeString(Str: PWideChar); cdecl; external 'SeleniumBridge.dll'; procedure TForm1.btnTestClick(Sender: TObject); var Driver: TDriverHandle; PageTitle: PWideChar; begin Driver := CreateChromeDriver; try DriverNavigateTo(Driver, 'https://www.google.com'); PageTitle := DriverGetTitle(Driver); try ShowMessage(WideString(PageTitle)); finally FreeString(PageTitle); end; finally DestroyChromeDriver(Driver); end; end;
三、关键注意事项
- GC回收防护:必须用全局容器保存托管对象引用,否则GC可能在Delphi仍在使用时回收C#对象
- 异常处理:C++/CLI中要捕获C#的异常,转换为非托管错误码返回给Delphi,避免程序崩溃
- 字符集统一:全程使用Unicode(宽字符),避免ANSI/Unicode转换混乱
- 平台一致性:C#、C++/CLI、Delphi需编译为相同平台(x86或x64),否则会出现调用错误
内容的提问来源于stack exchange,提问作者Ali
相关产品推荐
相关产品推荐

