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
相关产品推荐
相关产品推荐

