不使用Marshal获取非托管结构体中字段偏移量的方法
解决非托管结构体含Auto布局嵌套类型时的字段偏移量计算问题
核心思路
Marshal.OffsetOf依赖类型的可编组特性,而嵌套的ValueTuple使用LayoutKind.Auto导致整个结构体无法被Marshal处理。我们可以直接通过内存地址计算偏移量——利用非托管结构体在内存中连续布局的特性,通过实例的字段地址与结构体起始地址的差值得到结果。
方法一:使用System.Runtime.CompilerServices.Unsafe类(推荐)
该类提供安全的内存操作方法,无需开启unsafe上下文,适用于.NET Core 2.1+、.NET 5+,或通过NuGet引入System.Runtime.CompilerServices.Unsafe的项目。
泛型版本(针对已知类型)
using System.Runtime.CompilerServices; [StructLayout(LayoutKind.Sequential, Pack=4)] struct TestStruct { public int A; public (int, int) B; } // 计算字段B的偏移量 long offsetB = Unsafe.ByteOffset( ref Unsafe.AsRef<TestStruct>(null), ref Unsafe.AsRef(in default(TestStruct).B) ); Console.WriteLine($"B的偏移量:{offsetB}"); // 输出4(匹配int的4字节长度与Pack=4的对齐规则)
反射版本(处理任意结构体类型)
using System; using System.Reflection; using System.Runtime.CompilerServices; public static long GetFieldOffset(Type structType, FieldInfo field) { // 创建结构体默认实例(值类型默认实例无堆内存分配) object structInstance = Activator.CreateInstance(structType); ref struct structRef = ref Unsafe.As<object, struct>(ref structInstance); // 获取字段的引用并计算字节偏移 ref object fieldRef = ref field.GetValueRefOrDefault(ref structRef); return Unsafe.ByteOffset( ref Unsafe.As<object, byte>(ref structInstance), ref Unsafe.As<object, byte>(ref fieldRef) ); } // 使用示例 Type testType = typeof(TestStruct); FieldInfo fieldB = testType.GetField(nameof(TestStruct.B))!; long offset = GetFieldOffset(testType, fieldB); Console.WriteLine(offset);
方法二:使用Unsafe代码(需开启unsafe上下文)
通过直接获取结构体实例和字段的内存地址,计算差值得到偏移量。
泛型版本
using System.Runtime.InteropServices; [StructLayout(LayoutKind.Sequential, Pack=4)] unsafe struct TestStruct { public int A; public (int, int) B; } unsafe void CalculateOffset() { TestStruct instance = default; fixed (TestStruct* ptr = &instance) { // 计算字段A的偏移量 long offsetA = (byte*)&ptr->A - (byte*)ptr; Console.WriteLine($"A的偏移量:{offsetA}"); // 输出0 // 计算字段B的偏移量(取ValueTuple第一个元素的地址) long offsetB = (byte*)&ptr->B.Item1 - (byte*)ptr; Console.WriteLine($"B的偏移量:{offsetB}"); // 输出4 } }
反射版本
using System; using System.Reflection; public static unsafe long GetFieldOffsetUnsafe(Type structType, FieldInfo field) { object structInstance = Activator.CreateInstance(structType); TypedReference structRef = __makeref(structInstance); IntPtr structPtr = **(IntPtr**)&structRef; TypedReference fieldRef = field.GetValueRefOrDefault(structRef); IntPtr fieldPtr = **(IntPtr**)&fieldRef; return (long)fieldPtr - (long)structPtr; } // 使用示例 FieldInfo fieldB = typeof(TestStruct).GetField(nameof(TestStruct.B))!; long offset = GetFieldOffsetUnsafe(typeof(TestStruct), fieldB); Console.WriteLine(offset);
注意事项
- 确保结构体为非托管且blittable:所有字段类型必须是blittable(如基本值类型、blittable结构体),否则偏移量计算无意义。
- 嵌套类型布局:虽然
ValueTuple标记为LayoutKind.Auto,但blittable的ValueTuple在CLR实际内存中会按顺序布局字段,计算结果准确。 - 内存对齐:结构体的
Pack属性会影响字段对齐,上述方法会自动适配CLR的内存对齐规则,结果与实际内存布局一致。
内容的提问来源于stack exchange,提问作者Walt D
相关产品推荐
相关产品推荐

