深入解析 MemorySharp 的 MarshalType:托管类型与字节数组高效转换原理
【免费下载链接】MemorySharpA C# based memory editing library targeting Windows applications, offering various functions to extract and inject data and codes into remote processes to allow interoperability.项目地址: https://gitcode.com/gh_mirrors/me/MemorySharp
MemorySharp 是一款基于 C# 的 Windows 内存编辑库,它的核心能力是在远程进程中读写数据与注入代码。而支撑这一切的"数据搬运枢纽",正是内部组件MarshalType。本文将从源码出发,深入解析 MemorySharp 的 MarshalType 如何实现托管类型与字节数组之间的高效双向转换,并揭示它背后两条截然不同的转换路径与性能优化思路,帮助你真正读懂这个 C# 内存编辑库的底层读写原理。
MemorySharp 是什么?先认识这款 C# 内存编辑库
简单来说,MemorySharp 提供了一套面向对象的 API,让开发者可以像操作本地对象一样,读取和写入其他 Windows 进程的内存。它的典型应用场景包括游戏外挂开发、逆向工程辅助工具、自动化测试钩子等。
在使用过程中你会发现一个有趣的矛盾:内存读写的底层接口(如 Windows APIReadProcessMemory/WriteProcessMemory)只认字节数组,而业务代码里却是int、float、结构体这样的托管类型。谁来负责这两者之间的翻译?答案就是MarshalType<T>。
MarshalType 在 MemorySharp 内存读写原理中的角色
打开src/MemorySharp/MemorySharp.cs你会发现,Read<T>和Write<T>这两个最常用的 API,内部全部委托给了 MarshalType:
- 读取时:先
ReadBytes拿到原始字节,再调用MarshalType<T>.ByteArrayToObject还原成托管对象; - 写入时:先调用
MarshalType<T>.ObjectToByteArray把托管对象变成字节,再调用底层WriteBytes写进远程进程。
也就是说,你每一次sharp.Read<int>()或sharp.Write()背后,都在走 MarshalType 的转换管线。它同时服务于MarshalValue(值封送)、AssemblyTransaction(汇编执行结果读取)和RemoteThread(线程返回值读取),是整个库当之无愧的"中央转换器"。
核心原理一:ObjectToByteArray——托管对象转字节数组的快速通道
ObjectToByteArray定义在src/MemorySharp/Internals/MarshalType.cs,逻辑非常清晰:先尝试"零封送"快速路径,不行再走通用的非托管内存回退路径。
所谓快速路径,就是根据缓存的TypeCode直接命中BitConverter.GetBytes,例如:
case TypeCode.Int32: return BitConverter.GetBytes((int)(object)obj); case TypeCode.Double: return BitConverter.GetBytes((double)(object)obj);因为BitConverter是纯内存拷贝级别的转换,比 CLR 的 Marshal 封送快一个数量级,所以作者在注释里特意写道:"We'll tried to avoid marshalling as it really slows the process"(我们尽量避开封送,因为它严重拖慢进程)。
几个值得注意的细节:
char类型使用Encoding.UTF8.GetBytes转换;IntPtr按指针宽度拆成 4 或 8 字节;string直接抛出InvalidCastException——字符串在 MarshalType 层被刻意拒绝,因为字符串的长度不固定,需要走MarshalValue专门的远程分配逻辑。
核心原理二:ByteArrayToObject——字节数组转托管对象的还原流程
反向方法ByteArrayToObject(byte[] byteArray, int index = 0)同样遵循"快速路径优先"原则,并通过可选的index参数支持从字节流中间位置开始还原。这正是Read<T>(address, count)批量读取数组时能复用同一段字节缓冲区的关键——每MarshalType<T>.Size个字节还原一个元素,避免反复调用底层读取。
这里还有一个非常精巧的细节:IntPtr的还原会先检查字节数组的长度(1、2、4、8),分别补齐高位后转成ToInt32,兼容了从远程进程读回"不完整指针"的边界情况。
回退路径揭秘:LocalUnmanagedMemory 的通用封送方案
当类型不是基础类型(比如自定义结构体)时,MarshalType 会启用通用方案,借助src/MemorySharp/Memory/LocalUnmanagedMemory.cs完成转换:
- 在本进程用
Marshal.AllocHGlobal分配一块非托管内存; - 通过
Marshal.StructureToPtr把托管对象写入这块内存; - 再用
Marshal.Copy把整块内存原样拷成字节数组返回。
反向还原则是对称的:先把字节数组Marshal.Copy进非托管内存,再Marshal.PtrToStructure还原成托管对象。因为是using包裹的IDisposable实现,内存会在使用后自动释放,不会泄漏。这套通用方案保证了任意可封送结构体都能走通转换,代价是性能略低——这正是快速路径存在的意义。
静态度量:一次初始化、处处复用的类型元数据缓存
MarshalType<T>是一个泛型静态类,它的静态构造函数在第一次使用时执行一次,把Size、TypeCode、IsIntPtr、RealType、CanBeStoredInRegisters全部缓存下来。这意味着:
Marshal.SizeOf(RealType)只计算一次,后续读取结构体大小时零开销;bool被特殊处理为 1 字节(Type.GetTypeCode拿到的是 Boolean,走 1 字节而非 4 字节);- 静态缓存让每次
Read<T>/Write<T>都无需重复反射,这是 MemorySharp 内存读写保持高性能的基础。
注册表快速通道:CanBeStoredInRegisters 的寄存器优化
CanBeStoredInRegisters是 MarshalType 最有趣的优化开关。如果类型是IntPtr或各类整数、bool、char、单精度浮点(以及 x64 下的 64 位整数),它就能直接塞进 CPU 寄存器。
这个判断直接影响PtrToObject的行为:当类型可存入寄存器时,直接把指针本身的值当作数据返回(BitConverter.GetBytes(pointer.ToInt64()));否则才真正去远程进程读取Size字节。这解释了为什么MarshalType<long>.PtrToObject(sharp, new IntPtr(32))能直接返回 32——它读的不是指针指向的内存,而是指针数值本身。
实战流程:一次完整的内存读写如何运转
把上面的知识点串起来,一次sharp.Read<Point>(address)的完整链路是:
- 按
MarshalType<Point>.Size向远程进程读取原始字节; - 调用
ByteArrayToObject,因Point是结构体(TypeCode.Object)而非基础类型,进入非托管内存回退路径; Marshal.PtrToStructure还原出Point对象返回给调用方。
你可以直接看test/IntegrationTests/Internals/MarshalTypeTests.cs中的ToManagedObjectPointStruct测试:把结构体转成字节数组,再转回结构体,断言两者相等,完整验证了这条往返链路的正确性。
常见问题与注意事项
- 字符串转换会抛异常:
MarshalType<T>.ObjectToByteArray和ByteArrayToObject遇到string都会抛InvalidCastException,字符串应交给MarshalValue处理,它会自动在远程进程分配长度 + 1字节(含结束符\0)并写入; - 结构体必须可封送:走回退路径的结构体需要满足
[StructLayout]约束,否则StructureToPtr会失败; - 区分"指针值"与"指针指向的值":
PtrToObject对寄存器型类型返回的是指针数值本身,不要与Read<T>(address)读取的"地址处的数据"混淆; - 批量读取有额外优化:
MemorySharp.cs中读取T[]数组时,若T是byte会直接Buffer.BlockCopy,跳过逐元素转换。
结语:读懂 MarshalType,就读懂了 MemorySharp 的数据流
MarshalType<T>用不到 300 行代码,把一个看似简单的"类型 ↔ 字节"转换做到了极致:静态缓存类型元数据、BitConverter快速通道、非托管内存通用回退、寄存器级优化判断,四者环环相扣。无论你是想深入理解 MemorySharp 内存读写原理,还是准备二次开发这个 C# 内存编辑库,源码都值得反复研读——相关实现集中在src/MemorySharp/Internals/MarshalType.cs与src/MemorySharp/Internals/MarshalValue.cs,配套测试在test/IntegrationTests/Internals/目录下,配合阅读效果更佳。
【免费下载链接】MemorySharpA C# based memory editing library targeting Windows applications, offering various functions to extract and inject data and codes into remote processes to allow interoperability.项目地址: https://gitcode.com/gh_mirrors/me/MemorySharp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考