C#中如何封送嵌套结构体指针数组?调用原生库触发异常
结构体指针嵌套数组跨语言调用触发访问违例问题
某库在使用结构体指针的嵌套数组时,调用原生函数触发System.AccessViolationException异常;若移除paramB结构体指针的嵌套数组则无异常。
C++原生库代码
typedef struct _paramB { int order; double value; }paramB; typedef struct _paramA { char name[SIZE_NAME]; double a; double b; double c; double d; paramB* pointerToParamB[SIZE_VECTOR]; }paramA; int native_ParamA(paramA *params[], int size);
报错的C#适配调用代码
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)] public struct paramA { [MarshalAs(UnmanagedType.ByValTStr, SizeConst = SIZE_NAME)] public string name; public double a; public double b; public double c; public double d; [MarshalAs(UnmanagedType.ByValArray, SizeConst = SIZE_VECTOR)] public IntPtr[] pointerToParamB; } public static paramA Factory() { paramA instance = new paramA(); instance.name = "UUUU"; instance.pointerToParamB = new IntPtr[SIZE_VECTOR]; for (int i = 0; i < SIZE_VECTOR; i++) { instance.pointerToParamB[i] = Marshal.AllocHGlobal(Marshal.SizeOf<paramB>()); paramB element = paramB.Factory(); Marshal.StructureToPtr<paramB>(element, instance.pointerToParamB[i], false); } return instance; } [DllImport("native_dll.dll", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true, CharSet = CharSet.Ansi)] internal static extern int native_ParamA([In] IntPtr[] params, int size); static int main() { var instance = new IntPtr[100]; paramA[] datos = paramA.Factory(3); // 注:原代码中Factory方法定义无参数,此处存在笔误 for (int i = 0; i < 3; i++) { instance[i] = Marshal.AllocHGlobal(Marshal.SizeOf<paramA>()); //==> 接下来的Marshal操作会处理一个内部包含已封送的IntPtr结构体数组的结构体 Marshal.StructureToPtr<paramA>(datos[i], instance[i], false); } native_ParamA(instance, 3); // 触发异常:System.AccessViolationException: 尝试读取或写入受保护的内存。这通常指示其他内存已损坏。 }
注:本示例未包含
Marshal.AllocHGlobal调用后的内存释放操作
问题原因及解决方案
核心原因
C#结构体设置的Pack = 1与C++编译器默认的内存对齐规则不匹配,导致结构体的内存布局和大小与原生库预期不一致,进而触发内存访问违例。
C++默认会按照结构体中最大基本数据类型的大小进行内存对齐(例如double为8字节,默认对齐为8字节),而C#中Pack = 1强制按1字节对齐,会破坏这种对齐规则,使得结构体成员的内存偏移与原生库不匹配。
解决方案
统一内存对齐规则
修改C#中paramA和paramB的结构体布局属性,设置与C编译器一致的Pack值(通常为8,若C显式设置了其他对齐值则对应调整):[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 8)] public struct paramA { // 成员定义不变 } [StructLayout(LayoutKind.Sequential, Pack = 8)] public struct paramB { public int order; public double value; public static paramB Factory() { return new paramB { order = 0, value = 0.0 }; } }确保常量定义一致
确认C#中SIZE_NAME和SIZE_VECTOR的数值与C++中的宏定义完全相同,避免因数组大小不一致导致结构体总长度不匹配。修正代码笔误
调整Factory方法实现,使其支持生成指定数量的paramA数组:public static paramA[] Factory(int count) { paramA[] arr = new paramA[count]; for (int j = 0; j < count; j++) { paramA instance = new paramA(); instance.name = "UUUU"; instance.pointerToParamB = new IntPtr[SIZE_VECTOR]; for (int i = 0; i < SIZE_VECTOR; i++) { instance.pointerToParamB[i] = Marshal.AllocHGlobal(Marshal.SizeOf<paramB>()); paramB element = paramB.Factory(); Marshal.StructureToPtr<paramB>(element, instance.pointerToParamB[i], false); } arr[j] = instance; } return arr; }
内容的提问来源于stack exchange,提问作者gavioto
相关产品推荐
相关产品推荐

