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

不使用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);

注意事项

  1. 确保结构体为非托管且blittable:所有字段类型必须是blittable(如基本值类型、blittable结构体),否则偏移量计算无意义。
  2. 嵌套类型布局:虽然ValueTuple标记为LayoutKind.Auto,但blittable的ValueTuple在CLR实际内存中会按顺序布局字段,计算结果准确。
  3. 内存对齐:结构体的Pack属性会影响字段对齐,上述方法会自动适配CLR的内存对齐规则,结果与实际内存布局一致。

内容的提问来源于stack exchange,提问作者Walt D

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 04:54:35