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

C# Marshal含数组指针的C++ Union时的类型加载异常问题

异常原因分析

CLR 在加载用于非托管互操作的结构体时,对 union 字段的类型有严格限制:

  • union 的内存是共享的,所有字段必须对应固定大小的非托管内存布局。
  • 托管数组(Foo[])是引用类型,它在托管堆中的存储包含额外元数据(比如数组长度、类型信息),并非单纯的内存地址。直接将其放在 union 中,CLR 无法将其映射到非托管 union 所需的固定大小内存(非托管中 Foo* 仅占一个指针的大小,即 nint 的长度),因此触发 TypeLoadException。
  • 即使添加 [MarshalAs] 属性也无效,因为该属性仅指导 marshal 过程如何转换数据,但无法改变 CLR 对 union 字段类型的基本要求——union 字段必须是值类型或指针类型(nint 就是指针的托管表示)。
无需nint直接使用数组的实现方式

方案1:封装指针为数组属性(适配动态长度数组)

仍然用 nint 存储指针,但对外暴露数组类型的属性,在属性的 getter/setter 中手动完成指针与数组的转换:

[StructLayout(LayoutKind.Sequential)]
struct Foo {
    public int a;
    public float b;
}

[StructLayout(LayoutKind.Explicit)]
struct UnionField1 {
    [FieldOffset(0)]
    public int uf11;
    [FieldOffset(0)]
    private nint _uf12;

    // 对外暴露数组属性,内部转换指针
    public Foo[] uf12 {
        get {
            // 需根据业务逻辑获取实际数组长度,此处示例为3
            int length = 3;
            Foo[] arr = new Foo[length];
            for (int i = 0; i < length; i++) {
                nint elemPtr = _uf12 + i * Marshal.SizeOf<Foo>();
                arr[i] = Marshal.PtrToStructure<Foo>(elemPtr);
            }
            return arr;
        }
        set {
            // 从托管数组分配非托管内存并赋值指针
            int size = Marshal.SizeOf<Foo>() * value.Length;
            _uf12 = Marshal.AllocHGlobal(size);
            for (int i = 0; i < value.Length; i++) {
                nint elemPtr = _uf12 + i * Marshal.SizeOf<Foo>();
                Marshal.StructureToPtr(value[i], elemPtr, false);
            }
        }
    }
}

[StructLayout(LayoutKind.Sequential)]
struct Bar {
    public int type;
    public UnionField1 u;
}

这种方式既满足非托管互操作的内存布局要求,又能让外部代码直接使用数组类型。

方案2:固定长度数组(仅适配非托管中固定大小的数组)

如果你的 C++ union 中是固定长度的数组(而非指针),比如 Foo uf12[5],则可以用 [MarshalAs(UnmanagedType.ByValArray)] 直接定义数组:

// C++ 固定长度数组的union
union UnionField1 {
    int uf11;
    Foo uf12[5];
};

对应的C#代码:

[StructLayout(LayoutKind.Explicit)]
struct UnionField1 {
    [FieldOffset(0)]
    public int uf11;
    [FieldOffset(0)]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
    public Foo[] uf12;
}

此时 CLR 可以确定数组的固定内存大小(5 * sizeof(Foo)),不会触发类型加载异常。

内容的提问来源于stack exchange,提问作者Paul Dubsky

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 15:52:34