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

C#调用C++ DLL触发System.AccessViolationException的问题求助

C++ DLL 与 C# 交互触发 System.AccessViolationException 问题

我编写了一段C++代码,想要打包为DLL并在C# Windows Forms应用中调用。目前已生成DLL,但调用时触发System.AccessViolationException(最初连DLL都加载失败,解决加载问题后出现了这个访问冲突错误)。

团队需要把这段C代码集成到Windows Forms应用里,我正好借此学习C DLL项目以及和C#/Windows Forms的集成。参照YouTube教程写了如下DLL代码:

extern "C" _declspec(dllexport) Communication* CreateCommunicationObject(const char* ip, unsigned int p_out, unsigned int p_in)
{
    return &Communication::Communication(ip, p_out, p_in);
}

extern "C" _declspec(dllexport) st_rtioout getReceivedData(Communication *comm)
{
    return comm->getData();
}

extern "C" _declspec(dllexport) void sendNewData(Communication *comm, st_rtioin *data)
{
    return comm->sendData(*data);
}

extern "C" _declspec(dllexport) char* getReceptionStatus(Communication *comm)
{
    return comm->getReceptionStatus();
}

extern "C" _declspec(dllexport) char* getSendingStatus(Communication *comm)
{
    return comm->getSendingStatus();
}

生成DLL后,尝试在C#窗体应用中添加引用时,收到错误:无法添加对project2.dll的引用,请确保文件可访问且是有效的程序集或COM组件。查了Stack Overflow的方案后,写了如下C#代码:

[DllImport("Project2.dll", EntryPoint = "CreateCommunicationObject", CallingConvention = CallingConvention.StdCall)]
static extern IntPtr CreateCommunicationObject(string ip, int port_out, int port_in);

[DllImport("Project2.dll", EntryPoint = "getReceivedData", CallingConvention = CallingConvention.StdCall)]
static extern st_rtioout getReceivedData(IntPtr ptr);

[DllImport("Project2.dll", EntryPoint = "sendNewData", CallingConvention = CallingConvention.StdCall)]
static extern void sendNewData(IntPtr ptr, st_rtioin sendingdata);

[DllImport("Project2.dll", EntryPoint = "getReceptionStatus", CallingConvention = CallingConvention.StdCall)]
static extern string getReceptionStatus(IntPtr ptr);

[DllImport("Project2.dll", EntryPoint = "getSendingStatus", CallingConvention = CallingConvention.StdCall)]
static extern string getSendingStatus(IntPtr ptr);

但调用CreateCommunicationObject时触发了System.AccessViolationException(提示尝试读取或写入受保护的内存,通常表示其他内存已损坏)。我用的是VS2013,请问问题出在哪?该怎么修正?


编辑:

参考评论和建议修改了代码,但还是出现相同错误。

新的C++代码:

extern "C" _declspec(dllexport) void Initialize(std::string ip, unsigned int p_out, unsigned int p_in)
{
    const char* ipaddress = ip.c_str();
    Communication comms = Communication(ipaddress, p_out, p_in);
    Comms = &comms;
}

extern "C" _declspec(dllexport) st_rtioout getReceivedData()
{
    return Comms->getData();
}

extern "C" _declspec(dllexport) void sendNewData(st_rtioin data)
{
    Comms->sendData(data);
}

extern "C" _declspec(dllexport) std::string getReceptionStatus()
{
    std::string str = Comms->getReceptionStatus();
    return str;
}

extern "C" _declspec(dllexport) std::string getSendingStatus()
{
    std::string str = Comms->getSendingStatus();
    return str;
}

新的C#代码:

[DllImport("Project2.dll", EntryPoint = "Initialize", CallingConvention = CallingConvention.Cdecl)]
static extern void Initialize(string ip, int port_out, int port_in);

[DllImport("Project2.dll", EntryPoint = "getReceivedData", CallingConvention = CallingConvention.Cdecl)]
static extern st_rtioout getReceivedData();

[DllImport("Project2.dll", EntryPoint = "sendNewData", CallingConvention = CallingConvention.Cdecl)]
static extern void sendNewData(st_rtioin sendingdata);

[DllImport("Project2.dll", EntryPoint = "getReceptionStatus", CallingConvention = CallingConvention.Cdecl)]
static extern string getReceptionStatus();

[DllImport("Project2.dll", EntryPoint = "getSendingStatus", CallingConvention = CallingConvention.Cdecl)]
static extern string getSendingStatus();

问题根源与修复方案

1. 初始代码的核心问题

(1)构造函数调用错误

最初的CreateCommunicationObject里,&Communication::Communication(ip, p_out, p_in)是错误写法:直接调用构造函数会创建临时对象,函数返回后临时对象立即销毁,返回的指针指向已释放的内存,后续调用必然触发访问冲突。

(2)调用约定不匹配

C++默认调用约定是cdecl,但最初的C#代码用了StdCall,调用约定不匹配会破坏栈结构,引发内存错误。

(3)字符串类型跨语言不兼容

直接返回char*或std::string到C#存在风险:std::string是C++非托管类型,C#无法直接解析;char*若指向DLL内部栈内存,会随函数返回被释放,导致野指针。

2. 修改后代码的新问题

新写的Initialize函数中,Communication comms = Communication(...)创建的是栈上局部对象,函数执行完毕后对象被销毁,全局指针Comms指向已释放的内存,后续调用getReceivedData等函数时访问无效指针,依然触发访问冲突。

正确的修复步骤

步骤1:正确创建和销毁Communication对象

必须在堆上分配Communication对象,避免局部对象销毁导致的野指针:

// 全局指针保存对象实例(多线程场景需加锁保证线程安全)
Communication* Comms = nullptr;

extern "C" _declspec(dllexport) bool Initialize(const char* ip, unsigned int p_out, unsigned int p_in)
{
    // 先释放已有实例,避免内存泄漏
    if (Comms != nullptr) {
        delete Comms;
        Comms = nullptr;
    }
    try {
        Comms = new Communication(ip, p_out, p_in);
        return true;
    } catch (...) {
        return false;
    }
}

// 添加销毁函数,避免内存泄漏
extern "C" _declspec(dllexport) void DestroyCommunication()
{
    if (Comms != nullptr) {
        delete Comms;
        Comms = nullptr;
    }
}

步骤2:统一调用约定

C++端保持默认cdecl,C#端DllImport也指定CallingConvention = CallingConvention.Cdecl,避免栈破坏。

步骤3:字符串类型的安全传递

不能直接返回std::string或栈上char*,推荐两种安全方式:

  • 方式一:由C#分配缓冲区,DLL将字符串复制到缓冲区
extern "C" _declspec(dllexport) void getReceptionStatus(char* buffer, int bufferSize)
{
    if (Comms == nullptr || buffer == nullptr || bufferSize <= 0) return;
    std::string status = Comms->getReceptionStatus();
    // 复制字符串到缓冲区,确保不越界
    strncpy_s(buffer, bufferSize, status.c_str(), _TRUNCATE);
}

C#端调用:

[DllImport("Project2.dll", CallingConvention = CallingConvention.Cdecl)]
static extern void getReceptionStatus(StringBuffer buffer, int bufferSize);

// 使用示例
var buffer = new StringBuffer(256);
getReceptionStatus(buffer, buffer.Capacity);
string status = buffer.ToString();
  • 方式二:返回由CoTaskMemAlloc分配的字符串,C#会自动释放内存
#include <comutil.h>
#pragma comment(lib, "comsuppw.lib")

extern "C" _declspec(dllexport) BSTR getReceptionStatus()
{
    if (Comms == nullptr) return nullptr;
    std::string status = Comms->getReceptionStatus();
    // 转换为BSTR,由C#自动释放
    return _com_util::ConvertStringToBSTR(status.c_str());
}

C#端调用:

[DllImport("Project2.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.BStr)]
static extern string getReceptionStatus();

步骤4:结构体的内存布局匹配

确保C#端的st_rtioout和st_rtioin结构体与C++端内存布局完全一致:

  • C++端结构体指定对齐方式,比如#pragma pack(push, 1)
  • C#端结构体添加[StructLayout(LayoutKind.Sequential, Pack = 1)](根据C++对齐方式调整Pack值)

示例:
C++端:

#pragma pack(push, 1)
struct st_rtioout {
    int field1;
    float field2;
    // 其他字段
};
#pragma pack(pop)

C#端:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct st_rtioout {
    public int field1;
    public float field2;
    // 对应C++的其他字段
};

步骤5:DLL部署注意事项

  • 确保C#项目的平台目标(x86/x64)与C++ DLL的编译平台一致,避免位数不匹配
  • 将DLL复制到C#项目的输出目录(如bin/Debug),或者在DllImport中指定完整路径

内容的提问来源于stack exchange,提问作者Flight Dynamic Model

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 23:15:09