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

如何用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 15:47:08