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

使用Interop将C++的std::map导出到C#时遇访问冲突问题求助

跨C++/C#调用时std::map赋值引发访问违规的问题解决

问题重现

C++导出函数代码

extern "C" __declspec(dllexport) bool CallCustomObjectGetPropertyValue(AcDbObject* obj, long objId, std::map<std::string, std::vector<std::string>>& mapToFill) {
    CustomObject* custom = dynamic_cast<CustomObject*>(obj);
    if (!custom) {
        acutPrintf(_T("\nERROR: custom object is nullptr"));
        return false;
    }
    std::map<std::string, std::vector<std::string>> EmptyMapToFill = {};
    mapToFill = EmptyMapToFill;
}

C#调用代码

[DllImport("SheetMetalSample.brx")]
private static extern void CallCustomObjectGetPropertyValue(IntPtr obj, long objId, out IntPtr Int32mapPtr);

执行时触发错误:read access violation. _Head was 0x400,但赋值char、string类型时无异常。


问题根源

核心矛盾是C++标准库容器(如std::map)无法直接与C#的IntPtr进行跨语言交互:

  1. std::map是C++专属的复杂容器,内部包含指针、节点结构等依赖编译器实现的内存布局,没有统一的C兼容标准。
  2. C#端传递的out IntPtr只是一个原始内存地址,并非一个已正确构造的std::map对象,C++函数接收的引用绑定到了无效内存区域,赋值操作直接触发内存越界。
  3. char/string看似正常是巧合:char是基础类型,string如果是简单传递指针可能暂时未触发崩溃,但本质上同样存在内存管理和布局不兼容的风险。

解决方案

跨语言DLL调用必须使用C兼容的类型和内存传递方式,以下是两种可行方案:

方案一:序列化数据为字节数组(推荐)

将C++的map数据序列化为通用格式(如JSON),传递内存块指针给C#,C#解析后再释放内存。

C++端代码

#include <nlohmann/json.hpp> // 可替换为其他JSON库或自定义二进制序列化
using json = nlohmann::json;

extern "C" __declspec(dllexport) bool CallCustomObjectGetPropertyValue(AcDbObject* obj, long objId, char** outData, size_t* outSize) {
    CustomObject* custom = dynamic_cast<CustomObject*>(obj);
    if (!custom) {
        acutPrintf(_T("\nERROR: custom object is nullptr"));
        return false;
    }

    // 填充数据到json对象(示例)
    json j;
    // j["property1"] = custom->getProperty1();
    // j["property2"] = {"val1", "val2"};

    std::string jsonStr = j.dump();
    *outSize = jsonStr.size();
    *outData = new char[*outSize + 1];
    strcpy_s(*outData, *outSize + 1, jsonStr.c_str());
    return true;
}

// 必须导出内存释放函数,避免泄漏
extern "C" __declspec(dllexport) void FreeSerializedData(char* data) {
    delete[] data;
}

C#端代码

using System.Text.Json;
using System.Runtime.InteropServices;

[DllImport("SheetMetalSample.brx", CallingConvention = CallingConvention.Cdecl)]
private static extern bool CallCustomObjectGetPropertyValue(IntPtr obj, long objId, out IntPtr outData, out UInt32 outSize);

[DllImport("SheetMetalSample.brx", CallingConvention = CallingConvention.Cdecl)]
private static extern void FreeSerializedData(IntPtr data);

public static Dictionary<string, List<string>> GetCustomProperties(IntPtr obj, long objId) {
    if (!CallCustomObjectGetPropertyValue(obj, objId, out IntPtr dataPtr, out uint size)) {
        return null;
    }

    string jsonStr = Marshal.PtrToStringAnsi(dataPtr, (int)size);
    FreeSerializedData(dataPtr); // 必须调用C++端的释放函数

    return JsonSerializer.Deserialize<Dictionary<string, List<string>>>(jsonStr);
}

方案二:使用C风格结构体数组传递

定义C兼容的结构体,将map的键值对转换为数组传递。

C++端代码

// C兼容的结构体定义
typedef struct {
    const char* key;
    const char** values;
    int valueCount;
} PropertyEntry;

extern "C" __declspec(dllexport) bool CallCustomObjectGetPropertyValue(AcDbObject* obj, long objId, PropertyEntry** outEntries, int* outEntryCount) {
    CustomObject* custom = dynamic_cast<CustomObject*>(obj);
    if (!custom) {
        acutPrintf(_T("\nERROR: custom object is nullptr"));
        return false;
    }

    std::map<std::string, std::vector<std::string>> dataMap;
    // 填充dataMap(示例)
    // dataMap["example"] = {"val1", "val2"};

    *outEntryCount = dataMap.size();
    *outEntries = new PropertyEntry[*outEntryCount];

    int idx = 0;
    for (auto& pair : dataMap) {
        (*outEntries)[idx].key = _strdup(pair.first.c_str());
        int valCount = pair.second.size();
        (*outEntries)[idx].valueCount = valCount;
        (*outEntries)[idx].values = new const char*[valCount];
        for (int i = 0; i < valCount; i++) {
            (*outEntries)[idx].values[i] = _strdup(pair.second[i].c_str());
        }
        idx++;
    }

    return true;
}

// 释放结构体数组的内存
extern "C" __declspec(dllexport) void FreePropertyEntries(PropertyEntry* entries, int entryCount) {
    for (int i = 0; i < entryCount; i++) {
        free((void*)entries[i].key);
        for (int j = 0; j < entries[i].valueCount; j++) {
            free((void*)entries[i].values[j]);
        }
        delete[] entries[i].values;
    }
    delete[] entries;
}

C#端代码

using System.Runtime.InteropServices;

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
private struct PropertyEntry {
    public IntPtr key;
    public IntPtr values; // 指向string数组的指针
    public int valueCount;
}

[DllImport("SheetMetalSample.brx", CallingConvention = CallingConvention.Cdecl)]
private static extern bool CallCustomObjectGetPropertyValue(IntPtr obj, long objId, out IntPtr outEntries, out int outEntryCount);

[DllImport("SheetMetalSample.brx", CallingConvention = CallingConvention.Cdecl)]
private static extern void FreePropertyEntries(IntPtr entries, int entryCount);

public static Dictionary<string, List<string>> GetCustomProperties(IntPtr obj, long objId) {
    if (!CallCustomObjectGetPropertyValue(obj, objId, out IntPtr entriesPtr, out int entryCount)) {
        return null;
    }

    var result = new Dictionary<string, List<string>>();
    int entrySize = Marshal.SizeOf<PropertyEntry>();
    for (int i = 0; i < entryCount; i++) {
        IntPtr entryPtr = IntPtr.Add(entriesPtr, i * entrySize);
        PropertyEntry entry = Marshal.PtrToStructure<PropertyEntry>(entryPtr);
        
        string key = Marshal.PtrToStringAnsi(entry.key);
        var values = new List<string>();
        for (int j = 0; j < entry.valueCount; j++) {
            IntPtr valuePtr = Marshal.ReadIntPtr(entry.values, j * IntPtr.Size);
            values.Add(Marshal.PtrToStringAnsi(valuePtr));
        }
        
        result[key] = values;
    }

    FreePropertyEntries(entriesPtr, entryCount); // 释放内存
    return result;
}

关键注意事项

  1. 严格使用C兼容类型:跨语言调用禁止传递C++类、容器的引用或指针,不同编译器的内存布局和名称修饰规则不一致会导致崩溃。
  2. 统一内存管理:C端分配的内存必须通过C导出的函数释放,不能在C#端直接释放,避免内存泄漏或堆损坏。
  3. 指定调用约定:C++默认使用cdecl调用约定,C#的DllImport必须显式指定,否则会导致栈溢出或参数解析错误。

内容的提问来源于stack exchange,提问作者Guinea Pig Taco

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 19:50:33