如何用C++ Builder创建DLL接收C#传递的Dictionary<string, object>
C++ Builder DLL接收C# Dictionary<string, object>的实现方案
C#的Dictionary<string, object>是托管类型,无法直接传递给非托管的C++ Builder DLL,必须通过非托管序列化结构或COM接口做中转。以下是最实用的结构体数组方案,适配XE7及以上版本:
一、C#端预处理(必须完成)
先定义与C++ Builder完全匹配的非托管结构体,将字典内容转换为结构体数组后再调用DLL:
1. 定义非托管结构体
// 类型标记:对应不同的object类型 public enum ValueType : int { Int = 0, String = 1, CustomCommonClass = 2 } // 键值对非托管结构 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct UnmanagedKeyValuePair { public IntPtr Key; // ANSI字符串指针 public ValueType ValType; // 用FieldOffset模拟Union,存储不同类型的值 [FieldOffset(8)] public int IntVal; [FieldOffset(8)] public IntPtr StringVal; [FieldOffset(8)] public IntPtr CustomObjPtr; } // 自定义通用类的非托管映射(如果需要传递自定义对象) [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct UnmanagedMyCommonClass { public int Id; public IntPtr Name; }
2. 转换字典并调用DLL
// DLL导入声明,调用约定必须与C++端一致(Cdecl) [DllImport("YourCppDll.dll", CallingConvention = CallingConvention.Cdecl)] public static extern void ProcessDict(UnmanagedKeyValuePair[] pairs, int pairCount); public static void CallCppDll(Dictionary<string, object> inputDict) { var unmanagedPairs = new UnmanagedKeyValuePair[inputDict.Count]; int idx = 0; foreach (var kv in inputDict) { // 处理Key:分配非托管ANSI字符串内存 unmanagedPairs[idx].Key = Marshal.StringToHGlobalAnsi(kv.Key); // 处理Value:根据类型赋值 if (kv.Value is int intVal) { unmanagedPairs[idx].ValType = ValueType.Int; unmanagedPairs[idx].IntVal = intVal; } else if (kv.Value is string strVal) { unmanagedPairs[idx].ValType = ValueType.String; unmanagedPairs[idx].StringVal = Marshal.StringToHGlobalAnsi(strVal); } else if (kv.Value is MyCommonClass customObj) { unmanagedPairs[idx].ValType = ValueType.CustomCommonClass; // 转换自定义类到非托管结构并分配内存 var unmanagedCustom = new UnmanagedMyCommonClass { Id = customObj.Id, Name = Marshal.StringToHGlobalAnsi(customObj.Name) }; IntPtr objPtr = Marshal.AllocHGlobal(Marshal.SizeOf(unmanagedCustom)); Marshal.StructureToPtr(unmanagedCustom, objPtr, false); unmanagedPairs[idx].CustomObjPtr = objPtr; } idx++; } // 调用DLL函数 ProcessDict(unmanagedPairs, inputDict.Count); // 释放所有非托管内存(必须做,否则内存泄漏) foreach (var pair in unmanagedPairs) { Marshal.FreeHGlobal(pair.Key); if (pair.ValType == ValueType.String) Marshal.FreeHGlobal(pair.StringVal); else if (pair.ValType == ValueType.CustomCommonClass) { // 先释放自定义对象内的字符串,再释放对象本身 var customObj = Marshal.PtrToStructure<UnmanagedMyCommonClass>(pair.CustomObjPtr); Marshal.FreeHGlobal(customObj.Name); Marshal.FreeHGlobal(pair.CustomObjPtr); } } }
二、C++ Builder端实现
1. 定义匹配的结构体
// 与C#对应的类型标记枚举 enum class ValueType : int { Int = 0, String = 1, CustomCommonClass = 2 }; // 自定义类的非托管结构 struct UnmanagedMyCommonClass { int Id; char* Name; }; // 键值对结构 struct UnmanagedKeyValuePair { char* Key; ValueType ValType; union { int IntVal; char* StringVal; UnmanagedMyCommonClass* CustomObjPtr; }; };
2. 导出DLL函数
注意调用约定必须为__cdecl,与C#端一致:
// 导出函数声明 extern "C" __declspec(dllexport) void __cdecl ProcessDict(UnmanagedKeyValuePair* pairs, int pairCount) { for (int i = 0; i < pairCount; ++i) { UnmanagedKeyValuePair& currPair = pairs[i]; // 转换ANSI键到C++ Builder的UnicodeString UnicodeString key = UnicodeString(currPair.Key); // 根据类型提取值 switch (currPair.ValType) { case ValueType::Int: { int val = currPair.IntVal; // 业务逻辑处理,示例:弹窗显示 ShowMessage(Format(L"Key: %s, Int值: %d", ARRAYOFCONST((key, val)))); break; } case ValueType::String: { UnicodeString val = UnicodeString(currPair.StringVal); ShowMessage(Format(L"Key: %s, 字符串值: %s", ARRAYOFCONST((key, val)))); break; } case ValueType::CustomCommonClass: { UnmanagedMyCommonClass* customObj = currPair.CustomObjPtr; UnicodeString objName = UnicodeString(customObj->Name); ShowMessage(Format(L"Key: %s, 自定义对象ID: %d, 名称: %s", ARRAYOFCONST((key, customObj->Id, objName)))); break; } } } }
三、关键注意事项
类型转换
- 字符串:示例用ANSI字符串适配兼容性,若需Unicode,C#端改为
CharSet.Unicode,C++ Builder用UnicodeString(wchar_t*)转换。 - 自定义对象:必须保证C#与C++的结构体内存布局完全一致,字段顺序、类型大小(如int为4字节、指针为8字节在64位环境)严格匹配,否则会内存错乱。
内存管理
- C#端全权负责内存分配与释放:非托管内存由C#的
Marshal.AllocHGlobal分配,必须在调用DLL后用Marshal.FreeHGlobal释放,C++ Builder端绝对不能释放这些内存,否则会触发内存访问错误。 - 禁止在C++ Builder中保存C#传递的指针:C#调用完DLL后会立即释放内存,后续访问指针会导致崩溃。
- 位数一致性:DLL与C#程序必须同为32位或64位,否则指针大小不匹配会引发严重错误。
复杂场景替代方案
如果字典内容复杂或需频繁传递,可采用COM接口方案:C#端创建COM可见的字典接口类,C++ Builder通过COM导入接口直接访问。但该方案配置繁琐,适合大型项目,简单场景优先用结构体数组方案。
内容的提问来源于stack exchange,提问作者Giovanni
相关产品推荐
相关产品推荐

