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

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字节对齐,会破坏这种对齐规则,使得结构体成员的内存偏移与原生库不匹配。

解决方案

  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 };
        }
    }
    
  2. 确保常量定义一致
    确认C#中SIZE_NAME和SIZE_VECTOR的数值与C++中的宏定义完全相同,避免因数组大小不一致导致结构体总长度不匹配。

  3. 修正代码笔误
    调整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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 08:37:04