如何从C#调用含std::function参数的非托管C++ DLL函数?
C#调用带std::function参数的非托管C++ DLL函数问题
问题背景
非托管C++ DLL导出函数定义:
typedef std::function<void ( unsigned int msgType, unsigned int size, const char* const value ) > SCallback; __declspec(dllexport) int StartS( SCallback fnS );
用户尝试的C#调用代码:
[DllImport("SeqLib.dll", EntryPoint = "?StartS@SApi@@YAKV?$function@$$A6AXIIPBD@Z@std@@@Z", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)] public static extern int StartS(DCallback cb); public delegate void DCallback(int msgType, int size, IntPtr value); public static void MessageReceived(int msgType, int size, IntPtr value) { // 处理逻辑 } DCallback callback = new DCallback(MessageReceived); StartS(callback);
运行时抛出异常:
System.AccessViolationException: "Attempted to read or write protected memory. This is often an indication that other memory is corrupt."
原实现思路的错误
直接用C# delegate 映射C++ std::function 是完全错误的,核心原因:
std::function是C++标准库中的类类型,内部包含函数指针、捕获状态等复杂结构,并非单纯的函数指针。- C#的
delegate在互操作时会被封送为C风格函数指针(仅指向函数入口),和std::function的参数类型完全不匹配,传递后会导致DLL内部访问非法内存,触发异常。
正确的调用方案
方案1:修改C++ DLL,导出C风格函数指针接口
如果有权限修改原DLL,建议将导出函数改为接受C风格函数指针,因为C#只能直接和C风格函数指针交互。
修改后的C++导出代码:
// 定义C风格函数指针类型 typedef void (*SCallbackPtr)(unsigned int msgType, unsigned int size, const char* value); // 导出接受函数指针的接口,内部可将指针包装为std::function使用 __declspec(dllexport) int StartS(SCallbackPtr fnS) { std::function<void(unsigned int, unsigned int, const char*)> callback(fnS); // 原逻辑中使用callback即可 return 0; }
对应的C#调用代码:
[DllImport("SeqLib.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int StartS(DCallback cb); // 参数类型严格匹配:uint对应unsigned int,IntPtr对应const char* public delegate void DCallback(uint msgType, uint size, IntPtr value); public static void MessageReceived(uint msgType, uint size, IntPtr value) { // 将IntPtr转换为字符串示例 string content = Marshal.PtrToStringAnsi(value, (int)size); // 处理逻辑 } // 必须保留delegate引用,防止被GC回收 private static DCallback _callback; public static void Init() { _callback = new DCallback(MessageReceived); StartS(_callback); }
方案2:使用C++/CLI编写中间桥接层
如果无法修改原DLL,需要通过C++/CLI作为中间层,完成托管C# delegate到非托管std::function的转换。
- 创建C++/CLI类库项目,引用原非托管DLL;
- 编写桥接代码:
#include <functional> #include "OriginalDllHeader.h" // 原DLL的头文件 using namespace System; using namespace System::Runtime::InteropServices; namespace SApiBridge { // 定义与C#兼容的托管委托 public delegate void ManagedCallback(UInt32 msgType, UInt32 size, IntPtr value); public ref class ApiWrapper { private: // 保留托管委托引用,防止被GC回收 static gcroot<ManagedCallback^> s_managedCallback; // 非托管回调函数,转发到托管委托 static void NativeCallback(unsigned int msgType, unsigned int size, const char* value) { s_managedCallback->Invoke(msgType, size, IntPtr((void*)value)); } public: static int StartS(ManagedCallback^ cb) { s_managedCallback = cb; // 将非托管回调函数包装为std::function,传递给原DLL std::function<void(unsigned int, unsigned int, const char*)> nativeCallback(&NativeCallback); return ::StartS(nativeCallback); } }; }
- C#项目引用该C++/CLI类库,直接调用桥接层:
using SApiBridge; public static void MessageReceived(uint msgType, uint size, IntPtr value) { string content = Marshal.PtrToStringAnsi(value, (int)size); // 处理逻辑 } // 保留委托引用防止GC回收 private static ManagedCallback _callback; public static void Init() { _callback = new ManagedCallback(MessageReceived); ApiWrapper.StartS(_callback); }
内容的提问来源于stack exchange,提问作者Mike
相关产品推荐
相关产品推荐

