C# P/Invoke调用含嵌套结构体数组的C函数时出现访问冲突
问题排查与修正方案
首先明确原C函数和结构体的核心定义:
int setData(OuterStruct *data); typedef struct InnerStruct { unsigned int a; unsigned int b; unsigned char *c; } InnerStruct; typedef struct OuterStruct { unsigned int d; InnerStruct *e; } OuterStruct;
你的C#代码存在几个关键问题,直接导致AccessViolationException,以下是具体分析和修正方案:
1. 指针与数组的映射完全不匹配
原C中OuterStruct.e是指向InnerStruct的指针(InnerStruct*),你却用[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]将其映射为托管值数组,这对应C语言里的栈上固定大小数组(如InnerStruct e[10]),类型完全不符。同理,InnerStruct.c是unsigned char*(字节指针),用ByValArray也会导致内存布局错位。
修正原则:
- 非托管指针类型在C#中用
IntPtr表示,而非值数组。 - 如果指针指向数组,需手动分配非托管内存,再将托管数据复制进去。
2. 调用约定不匹配
C语言默认函数调用约定是cdecl,你在DllImport中指定了CallingConvention.StdCall,会导致栈平衡错误,直接触发内存访问异常。必须将调用约定改为CallingConvention.Cdecl。
3. 结构体内存对齐验证
虽然使用了LayoutKind.Sequential,但需显式指定内存对齐值(如Pack=4),确保和C编译器的默认对齐规则一致(多数C编译器默认4字节对齐)。
修正后的完整代码示例
结构体定义
[StructLayout(LayoutKind.Sequential, Pack = 4)] public struct InnerStruct { public uint a; public uint b; // 用IntPtr对应C中的unsigned char* public IntPtr c; } [StructLayout(LayoutKind.Sequential, Pack = 4)] public struct OuterStruct { public uint d; // 用IntPtr对应C中的InnerStruct* public IntPtr e; }
DllImport修正
[DllImport("myDll.dll", EntryPoint = "setData", CallingConvention = CallingConvention.Cdecl)] public static extern int setData(ref OuterStruct data);
调用示例(假设e指向10个InnerStruct实例,c指向5字节缓冲区)
// 分配容纳10个InnerStruct的非托管内存 int innerCount = 10; IntPtr innerArrayPtr = Marshal.AllocHGlobal(Marshal.SizeOf<InnerStruct>() * innerCount); // 初始化每个InnerStruct并复制到非托管内存 for (int i = 0; i < innerCount; i++) { InnerStruct inner = new InnerStruct(); inner.a = 1; inner.b = 2; // 分配c对应的5字节缓冲区 inner.c = Marshal.AllocHGlobal(5); // 向缓冲区写入测试数据 byte[] buffer = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05 }; Marshal.Copy(buffer, 0, inner.c, 5); // 将InnerStruct复制到非托管数组的对应位置 Marshal.StructureToPtr(inner, IntPtr.Add(innerArrayPtr, i * Marshal.SizeOf<InnerStruct>()), false); } // 初始化OuterStruct OuterStruct outer = new OuterStruct(); outer.d = 100; outer.e = innerArrayPtr; // 调用DLL函数 int result = setData(ref outer); // 手动释放非托管内存(必须执行,避免内存泄漏) for (int i = 0; i < innerCount; i++) { InnerStruct inner = Marshal.PtrToStructure<InnerStruct>(IntPtr.Add(innerArrayPtr, i * Marshal.SizeOf<InnerStruct>())); Marshal.FreeHGlobal(inner.c); } Marshal.FreeHGlobal(innerArrayPtr);
额外排查点
- 若
setData是输出参数(DLL会修改传入的内存),需确保分配的非托管内存大小足够,且权限正确。 - 若
InnerStruct.c由DLL负责分配,需确认DLL是否提供对应的内存释放函数,避免跨Runtime内存泄漏。
内容的提问来源于stack exchange,提问作者Z A
相关产品推荐
相关产品推荐

