Unsafe类能否实现Marshal.PtrToStructure的结构体指针读取功能?
问题:Unsafe类读取结构体失败,能否实现Marshal.PtrToStructure的等效功能?
尝试用Unsafe类方法从指针读取结构体时全部失败,调试器显示“Unable to read memory”,但Marshal.PtrToStructure可以正常工作:
var info1 = Marshal.PtrToStructure<DbgHelp.SYMBOL_INFO_V>(pSymInfo); // 正常工作 var info2 = Unsafe.AsRef<DbgHelp.SYMBOL_INFO_V>((void*)pSymInfo); // 无法读取内存 var info3 = Unsafe.Read<DbgHelp.SYMBOL_INFO_V>((void*)pSymInfo); // 无法读取内存 var info4 = Unsafe.ReadUnaligned<DbgHelp.SYMBOL_INFO_V>((void*)pSymInfo); // 无法读取内存
补充说明:
- pSymInfo是IntPtr类型
- SYMBOL_INFO_V结构体定义:
public struct SYMBOL_INFO_V { public uint SizeOfStruct; public uint TypeIndex; public ulong Reserved0; private ulong Reserved1; public uint Index; public uint Size; public ulong ModBase; public SYMFLAG Flags; // enum, uint public ulong Value; public ulong Address; public uint Register; public uint Scope; public uint Tag; public uint NameLen; public uint MaxNameLen; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1)] public string Name; }
解答
可以实现,但需要调整结构体定义,因为Unsafe类和Marshal的内存处理逻辑完全不同:
核心原因
Marshal.PtrToStructure会自动处理[MarshalAs(UnmanagedType.ByValTStr)]标记的字段,把非托管内存里的固定长度字符数组转换为托管string。但Unsafe类是直接按内存布局拷贝值类型,不做托管/非托管的类型转换——而C#的string是托管引用类型,非托管内存里的是原始字符数据,不是string对象的引用,直接读取会导致内存访问错误。
解决步骤
修改结构体定义,替换string为fixed字符数组
把Name字段从string改成fixed char数组,让结构体的内存布局和非托管端完全匹配:[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] // 匹配非托管字符集,按需调整为Ansi public struct SYMBOL_INFO_V { public uint SizeOfStruct; public uint TypeIndex; public ulong Reserved0; private ulong Reserved1; public uint Index; public uint Size; public ulong ModBase; public SYMFLAG Flags; public ulong Value; public ulong Address; public uint Register; public uint Scope; public uint Tag; public uint NameLen; public uint MaxNameLen; public fixed char Name[1]; // 用fixed数组代替string,SizeConst=1对应原定义的占位 }注:SYMBOL_INFO的Name字段实际是可变长度缓冲区,SizeConst=1只是声明占位,实际分配内存时会按MaxNameLen调整大小,fixed数组的长度不影响Unsafe读取,后续可以按NameLen提取有效字符。
使用Unsafe类读取结构体
调整结构体后,就可以用Unsafe方法正常读取:// 直接读取结构体 var info = Unsafe.Read<DbgHelp.SYMBOL_INFO_V>((void*)pSymInfo); // 手动提取Name字符串(从fixed数组中读取到终止符或NameLen指定长度) string name; fixed (char* namePtr = info.Name) { name = new string(namePtr, 0, (int)info.NameLen); }关键注意事项
- 必须保证结构体的
StructLayout属性和非托管端的内存对齐、字符集完全一致,否则读取的字段值会错位 - Unsafe类操作的是非托管内存,需确保指针有效且内存未被释放,否则会引发内存访问异常
- 必须保证结构体的
内容的提问来源于stack exchange,提问作者aybe
相关产品推荐
相关产品推荐

