从C++ DLL函数返回wchar_t*至C#失败问题排查
C#调用C++ DLL第二个函数崩溃的排查与解决
问题现象
维护旧C++代码时遇到异常:C#调用DLL的testFunc1运行正常,但调用testFunc2后程序直接退出,返回错误代码3221226356。
相关代码
C++ DLL代码
#define EXPFUNC extern "C" __declspec(dllexport) static const wchar_t *TEST = const_cast<wchar_t*> (L"TEST"); EXPFUNC const unsigned int __stdcall testFunc1(const wchar_t *str) { // 使用*str查找并返回id的逻辑 return 123; } EXPFUNC const wchar_t* __stdcall testFunc2(const unsigned int id) { // 使用id查找并返回指针的逻辑 return TEST; }
C#调用代码
[DllImport("Test.dll", CallingConvention = CallingConvention.Cdecl)] internal static extern uint testFunc1([MarshalAs(UnmanagedType.BStr)] string symbol); [DllImport("Test.dll", CallingConvention = CallingConvention.Cdecl)] [return: MarshalAs(UnmanagedType.BStr)] internal static extern string testFunc2(uint id); testFunc1("some string"); // 运行正常 testFunc2(123); // 退出并返回代码3221226356
问题根源
调用约定不匹配
C++函数明确使用__stdcall调用约定,但C#的DllImport指定的是CallingConvention.Cdecl。两种约定的栈清理规则不同:__stdcall由被调用方清理栈,Cdecl由调用方清理。这种不匹配会导致栈不平衡,第一个函数未触发崩溃属于偶然,第二个函数直接暴露了问题。返回值封送错误
testFunc2返回的是静态常量宽字符指针(const wchar_t*),但C#用[return: MarshalAs(UnmanagedType.BStr)]来处理返回值。BStr是COM标准字符串,要求内存由SysAllocString分配,CLR会尝试释放返回的BStr内存,但静态常量的内存并非COM分配的,因此会触发非法内存访问,导致程序崩溃。
解决办法
1. 统一调用约定
将C#的DllImport调用约定改为CallingConvention.StdCall,与C++的__stdcall保持一致。
2. 修正封送类型
- 对于
testFunc1的参数:C++接收const wchar_t*,对应C#的UnmanagedType.LPWStr(宽字符指针),而非BStr。 - 对于
testFunc2的返回值:使用UnmanagedType.LPWStr封送静态常量宽字符指针,CLR会正确读取字符串且不会尝试释放内存。
修改后的C#代码:
[DllImport("Test.dll", CallingConvention = CallingConvention.StdCall)] internal static extern uint testFunc1([MarshalAs(UnmanagedType.LPWStr)] string symbol); [DllImport("Test.dll", CallingConvention = CallingConvention.StdCall)] [return: MarshalAs(UnmanagedType.LPWStr)] internal static extern string testFunc2(uint id);
修改后调用testFunc2(123)即可正常返回"TEST"字符串,不再崩溃。
内容的提问来源于stack exchange,提问作者defa0009
相关产品推荐
相关产品推荐

