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

C#如何使用Marshal.PtrToStructure拷贝泛型结构体解决泛型类型不支持报错

问题原因

Marshal.PtrToStructure的底层实现硬编码了类型校验逻辑,禁止传入任何泛型类型,该限制属于 runtime 层面的约束,和泛型结构体是否为非托管类型无关,因此即使添加unmanaged约束也无法绕过。

可行方案(无需unsafe代码)

方案1:使用Span<T> + MemoryMarshal.Read(推荐,适用于.NET Core 2.1、.NET 5及以上版本)

这套是.NET 现代运行时提供的原生内存操作API,天然支持构造非托管泛型类型的读取,性能比Marshal系列API更高,也不需要手动pin内存:

  1. 保留你的泛型结构体定义,只要保留unmanaged约束即可:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct PEHeaderWindowsNtSpecificFields<Tint> where Tint : unmanaged
{
    public Tint ImageBase;
    public uint SectionAlignment;
    public uint FileAlignment;
    public ushort OSMajor;
    public ushort OSMinor;
    public ushort UserMajor;
    public ushort UserMinor;
    public ushort SubSysMajor;
    public ushort SubSysMinor;
    public uint Reserved;
    public uint ImageSize;
    public uint HeaderSize;
    public uint FileChecksum;
    public ushort SubSystem;
    public ushort DLLFlags;
    public Tint StackReserveSize;
    public Tint StackCommitSize;
    public Tint HeapReserveSize;
    public Tint HeapCommitSize;
    public uint LoaderFlags;
    public uint NumberOfDataDirectories;
}
  1. 修改读取逻辑,替换掉Marshal.PtrToStructure调用:
static void Main(string[] args)
{
    const ushort MAGIC_PE32 = 0x10b;
    const ushort MAGIC_PE32_PLUS = 0x20b;

    var dll = args.SingleOrDefault() ?? typeof(Program).Assembly.Location;
    Console.WriteLine(dll);

    var bytes = File.ReadAllBytes(dll);
    ushort peHeaderMagic = BitConverter.ToUInt16(bytes, 0x98);

    object peHeader;
    switch (peHeaderMagic)
    {
        case MAGIC_PE32:
            Console.WriteLine("PE32");
            peHeader = MemoryMarshal.Read<PEHeaderWindowsNtSpecificFields<uint>>(bytes.AsSpan(0xB4));
            break;
        case MAGIC_PE32_PLUS:
            Console.WriteLine("PE32+");
            peHeader = MemoryMarshal.Read<PEHeaderWindowsNtSpecificFields<ulong>>(bytes.AsSpan(0xB0));
            break;
        default:
            throw new Exception("Unknown PE magic");
    }

    // 后续操作和原来完全一致
    foreach (var field in peHeader.GetType().GetFields())
    {
        Console.WriteLine("{0,24} = 0x{1:X}", field.Name, field.GetValue(peHeader));
    }
}

方案2:兼容.NET Framework 4.x的方案

如果需要兼容旧版本.NET Framework,没有MemoryMarshal可用,可以封装泛型读取方法用BinaryReader按偏移读取字段:

static T ReadPEHeader<T>(Stream stream, int offset) where T : struct
{
    stream.Seek(offset, SeekOrigin.Begin);
    var reader = new BinaryReader(stream);
    var type = typeof(T);
    var instance = Activator.CreateInstance<T>();
    foreach (var field in type.GetFields())
    {
        object fieldValue = Type.GetTypeCode(field.FieldType) switch
        {
            TypeCode.UInt16 => reader.ReadUInt16(),
            TypeCode.UInt32 => reader.ReadUInt32(),
            TypeCode.UInt64 => reader.ReadUInt64(),
            _ => throw new NotSupportedException($"不支持的字段类型:{field.FieldType.Name}")
        };
        field.SetValue(instance, fieldValue);
    }
    return instance;
}

调用时直接传入文件流和对应偏移即可,同样不需要重复定义结构体,也不需要unsafe代码。


内容的提问来源于stack exchange,提问作者Carl Walsh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.25 23:54:04