C#互操作中char*转string触发堆损坏异常的原因排查
C#封送C结构体char*为string触发堆损坏异常的原因分析
在用[MarshalAs(UnmanagedType.LPStr)]将C结构体中的char*封送到C#的string类型时,触发了“堆已损坏”异常,但C# string转C char*的反向操作正常。改用IntPtr接收char*则能正常运行,以下是具体细节及原因解析:
C端结构体与函数签名
struct STRUCTV { char* _pretext; char* _result; };
函数签名:void add_all_numbers(STRUCTV *structV);
触发异常的C#代码
结构体定义
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct STRUCTV { [MarshalAs(UnmanagedType.LPStr)] public string _pretext; [MarshalAs(UnmanagedType.LPStr)] public string _result; // 此处无法正常工作 }
导入Native函数
internal partial class NativeMethods { [DllImport( "Library.dll", EntryPoint = "function1", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] public static extern void function1(ref STRUCTV structV); }
调用方式
STRUCTV myStruct = new STRUCTV(); myStruct._pretext = "Pretext"; myStruct._result = null; NativeMethods.function1(ref myStruct); // 触发ntdll.dll : A heap has been corrupted异常 Console.WriteLine(myStruct._result);
C端内部逻辑
string resultstring = GetResult(); char* result = new char[resultstring.length() + 1]; strcpy_s(result, resultstring.length() + 1, resultstring.c_str()); structV->_result = result;
正常运行的C#代码(使用IntPtr)
结构体定义
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] public struct STRUCTV { [MarshalAs(UnmanagedType.LPStr)] public string _pretext; public IntPtr _result; }
调用方式
STRUCTV myStruct = new STRUCTV(); myStruct._pretext = "Pretext"; myStruct._result = IntPtr.Zero; NativeMethods.function1(ref myStruct); string resultString = Marshal.PtrToStringAnsi(myStruct._result); Console.WriteLine(resultString);
原因解析
- 当C#将结构体中的
string标记为[MarshalAs(UnmanagedType.LPStr)]时,CLR默认认为该字符串的内存由托管堆分配,在封送完成后会尝试用CoTaskMemFree释放指针指向的内存。 - 但C端代码是用C++的
new(CRT堆)分配的内存,CLR用CoTaskMemFree释放CRT堆内存会导致堆损坏——不同堆的分配/释放机制不兼容。 - 改用
IntPtr时,CLR不会自动释放该指针指向的内存,由开发者手动处理内存生命周期(若需释放,需在C端提供对应的释放函数,比如void free_result(STRUCTV* structV) { delete[] structV->_result; },再在C#调用完后执行该函数),因此不会触发堆损坏异常。
内容的提问来源于stack exchange,提问作者isswf
相关产品推荐
相关产品推荐

