You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 21:52:43