C++导出含char*[]的结构体供C#调用时乱码崩溃如何解决
问题根因
你的封送逻辑存在三个核心错误,直接导致乱码和崩溃:
- 初始C#结构体定义内存布局完全不匹配:C++侧的
char* iAdapterName[6]是6个char类型指针构成的数组,32位程序下总长度为24字节(每个指针4字节),64位下总长度为48字节(每个指针8字节);你定义的char[] iAdapterName是6个单字节字符构成的数组,总长度仅6字节,内存错位必然导致读取乱码。 - 字符串类型匹配错误:你后续用
UnmanagedType.LPTStr对应C++的char*类型,LPTStr会随C#项目字符集设置自动映射为ANSI或Unicode宽字符类型,和固定为ANSI编码的char*不匹配,读取宽字符会直接访问非法内存。 - 参数传递和内存逻辑错误:用
out传参时,封送器会等非托管函数返回后才初始化托管结构体,C侧写入数据时结构体内部的数组成员根本没有分配有效内存;同时封送器默认会尝试释放非托管传入的字符串指针,但你C侧的字符串是适配器信息的内部缓存,不是用COM内存分配器申请的,释放非法内存直接触发程序崩溃。
正确实现方案
优先使用手动控制指针封送的方案,完全规避封送器的自动内存处理逻辑,32/64位下全兼容,不会出现乱码和崩溃:
- 修正结构体定义,用
IntPtr数组对应C++的char*指针数组,保证内存布局完全匹配
[StructLayout(LayoutKind.Sequential)] public struct Adapters { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public int[] iAdapterIndex; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public int[] iBusNumber; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public int[] iDeviceNumber; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public IntPtr[] iAdapterName; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public IntPtr[] iDisplayName; }
- 修正DLL导入声明,将
out改为ref,允许提前初始化结构体内部数组
[DllImport("Example.dll", EntryPoint = "GET_ADAPTERS_INFO", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)] public static extern int GET_ADAPTERS_INFO(ref Adapters adapters);
- 调用前提前初始化所有数组成员为固定长度6,调用完成后手动将IntPtr转换为ANSI编码字符串
// 初始化结构体,为所有数组分配固定长度内存 Adapters adapters = new Adapters(); adapters.iAdapterIndex = new int[6]; adapters.iBusNumber = new int[6]; adapters.iDeviceNumber = new int[6]; adapters.iAdapterName = new IntPtr[6]; adapters.iDisplayName = new IntPtr[6]; // 调用非托管接口 int result = GET_ADAPTERS_INFO(ref adapters); // 读取有效适配器的字符串信息 for (int i = 0; i < 6; i++) { if (adapters.iBusNumber[i] > -1) // 和C++侧有效适配器判断逻辑保持一致 { string adapterName = Marshal.PtrToStringAnsi(adapters.iAdapterName[i]); string displayName = Marshal.PtrToStringAnsi(adapters.iDisplayName[i]); // 业务逻辑处理adapterName、displayName即可 } }
可选简化方案
如果不想手动处理指针转换,可以直接用string[]类型,但必须严格指定子类型为ANSI字符串,且同样提前初始化数组:
[StructLayout(LayoutKind.Sequential)] public struct Adapters { [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public int[] iAdapterIndex; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public int[] iBusNumber; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public int[] iDeviceNumber; // 必须指定为LPStr对应ANSI char*,不能用LPTStr [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.LPStr, SizeConst = 6)] public string[] iAdapterName; [MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.LPStr, SizeConst = 6)] public string[] iDisplayName; }
调用时需要提前将string数组初始化为长度6的空数组,同样用ref传参。该方案依赖封送器默认行为,不同.NET版本下可能存在兼容性差异,优先使用手动指针转换方案。
内容的提问来源于stack exchange,提问作者Yuri Bubliy
相关产品推荐
相关产品推荐

