C#如何使用Marshal.PtrToStructure拷贝泛型结构体解决泛型类型不支持报错
问题原因
Marshal.PtrToStructure的底层实现硬编码了类型校验逻辑,禁止传入任何泛型类型,该限制属于 runtime 层面的约束,和泛型结构体是否为非托管类型无关,因此即使添加unmanaged约束也无法绕过。
可行方案(无需unsafe代码)
方案1:使用Span<T> + MemoryMarshal.Read(推荐,适用于.NET Core 2.1、.NET 5及以上版本)
这套是.NET 现代运行时提供的原生内存操作API,天然支持构造非托管泛型类型的读取,性能比Marshal系列API更高,也不需要手动pin内存:
- 保留你的泛型结构体定义,只要保留
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; }
- 修改读取逻辑,替换掉
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
相关产品推荐
相关产品推荐

