如何将含string[]字段的结构体从C#封送到带C绑定的C++库?
C#向C/C++结构体封送string[]的问题与解决方案
问题场景
需要从C#向带有C接口的C++库传递Config结构体,移除stringArray字段时其余字段可正常传递,添加后程序崩溃。尝试使用[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr)]封送stringArray仍无效,需解决以下问题:
- 封送
string[]的代码存在什么错误? - 是否可以将结构体中的
string[]从C#封送到C/C++? - 若不可行,有哪些替代方案?
原库代码
MyLib.h
#ifdef __cplusplus extern "C" { #endif #pragma pack(push, 1) typedef struct _Config { unsigned long arraySize; double* doubleArray; // array of arraySize doubles const char* stringVar; const char** stringArray; // array of arraySize strings as const char* } Config; #pragma pack(pop) __declspec(dllexport) int SetConfig(Config config); #ifdef __cplusplus } #endif
MyLib.cpp
#include "MyLib.h" #include <vector> #include <string> int SetConfig(Config config) { if (config.arraySize == 0) return -1; unsigned long N = config.arraySize; std::vector<double> doubles(N); for (unsigned long i = 0; i < N; i++) doubles[i] = config.doubleArray[i]; std::string stringVar = config.stringVar; std::vector<std::string> strings(N); for (unsigned long i = 0; i < N; i++) strings[i] = config.stringArray[i]; return 0; }
原C#调用代码
namespace MySpace { public class MyLib { [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] public struct Config { public uint arraySize; [MarshalAs(UnmanagedType.LPArray)] public double[] doubleArray; // array of arraySize doubles [MarshalAs(UnmanagedType.LPStr)] public string stringVar; [MarshalAs(UnmanagedType.LPArray)] public string[] stringArray; // array of arraySize strings } public void Test_SetConfig() { Config config; config.arraySize = 3; config.doubleArray = new double[3] { 1.11, 2.22, 3.33 }; config.stringVar = "str"; config.stringArray = new string[3] { "str 1", "str 2", "str 3" }; int result = MyLibNativeMethods.SetConfig(config); if (result != 0) throw new Exception("Error in Test_SetConfig"); } } public class MyLibNativeMethods { [DllImport("MyLib", CallingConvention = CallingConvention.Cdecl)] public static extern int SetConfig(MyLib.Config config); } }
问题解答
1. 封送string[]的代码错误
原C#代码存在两个关键错误:
- 未指定数组元素的封送类型:
stringArray的MarshalAs仅用了UnmanagedType.LPArray,默认会将字符串封送为BStr(COM字符串类型),而C++期望的是ANSI编码的const char*,类型不匹配导致内存访问错误。 - 未指定数组长度参数:封送器无法自动关联
arraySize字段作为stringArray的长度,可能导致封送的数组长度不符合预期,触发越界访问。
2. 是否可以将结构体中的string[]从C#封送到C/C++?
完全可以,只要修正封送属性,确保类型和布局与C++结构体完全匹配。
修正后的C# Config结构体代码:
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] public struct Config { public uint arraySize; [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] public double[] doubleArray; [MarshalAs(UnmanagedType.LPStr)] public string stringVar; [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPStr, SizeParamIndex = 0)] public string[] stringArray; }
SizeParamIndex = 0:告诉封送器用第0个字段(arraySize)的值作为数组的长度,确保数组元素数量正确。ArraySubType = UnmanagedType.LPStr:明确指定每个字符串封送为ANSI编码的const char*,与C++类型匹配。
3. 替代方案
如果直接封送仍有问题(比如复杂场景下的内存管理),可以采用以下替代方案:
- 手动管理非托管内存:用
IntPtr代替string[],手动分配非托管内存存储const char**数组,每个字符串用Marshal.StringToHGlobalAnsi转换,调用完函数后释放内存。示例代码:public void Test_SetConfig_Manual() { uint arraySize = 3; double[] doubleArray = new double[] {1.11, 2.22, 3.33}; string stringVar = "str"; string[] stringArray = new string[] {"str1", "str2", "str3"}; // 分配指针数组内存 IntPtr stringPtrArray = Marshal.AllocHGlobal((int)arraySize * IntPtr.Size); try { // 逐个转换字符串并写入指针数组 for (int i = 0; i < arraySize; i++) { IntPtr strPtr = Marshal.StringToHGlobalAnsi(stringArray[i]); Marshal.WriteIntPtr(stringPtrArray, i * IntPtr.Size, strPtr); } // 构建配置(用IntPtr代替string[]) ConfigManual config; config.arraySize = arraySize; config.doubleArray = doubleArray; config.stringVar = stringVar; config.stringArray = stringPtrArray; int result = MyLibNativeMethods.SetConfigManual(config); if (result != 0) throw new Exception("Error"); } finally { // 释放每个字符串的内存 for (int i = 0; i < arraySize; i++) { IntPtr strPtr = Marshal.ReadIntPtr(stringPtrArray, i * IntPtr.Size); Marshal.FreeHGlobal(strPtr); } // 释放指针数组内存 Marshal.FreeHGlobal(stringPtrArray); } } // 对应的结构体定义 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] public struct ConfigManual { public uint arraySize; [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] public double[] doubleArray; [MarshalAs(UnmanagedType.LPStr)] public string stringVar; public IntPtr stringArray; // 对应const char** } - 字符串拼接传递:将字符串数组用分隔符(如
|)拼接成单个字符串,在C++中拆分,适合字符串不含分隔符的场景。 - C++/CLI中间层:用C++/CLI编写包装类,直接处理托管
string[]到非托管const char**的转换,简化C#端调用。 - 传递结构体指针:将C#结构体封送到非托管内存,传递指针给C++函数,避免值传递时的复杂封送,调用后释放内存。
内容的提问来源于stack exchange,提问作者KoenT
相关产品推荐
相关产品推荐

