深入解析 MemorySharp 的 MarshalType:托管类型与字节数组高效转换原理
2026/8/18 17:46:49 网站建设 项目流程

深入解析 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)只认字节数组,而业务代码里却是intfloat、结构体这样的托管类型。谁来负责这两者之间的翻译?答案就是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完成转换:

  1. 在本进程用Marshal.AllocHGlobal分配一块非托管内存;
  2. 通过Marshal.StructureToPtr把托管对象写入这块内存;
  3. 再用Marshal.Copy把整块内存原样拷成字节数组返回。

反向还原则是对称的:先把字节数组Marshal.Copy进非托管内存,再Marshal.PtrToStructure还原成托管对象。因为是using包裹的IDisposable实现,内存会在使用后自动释放,不会泄漏。这套通用方案保证了任意可封送结构体都能走通转换,代价是性能略低——这正是快速路径存在的意义。

静态度量:一次初始化、处处复用的类型元数据缓存

MarshalType<T>是一个泛型静态类,它的静态构造函数在第一次使用时执行一次,把SizeTypeCodeIsIntPtrRealTypeCanBeStoredInRegisters全部缓存下来。这意味着:

  • Marshal.SizeOf(RealType)只计算一次,后续读取结构体大小时零开销;
  • bool被特殊处理为 1 字节(Type.GetTypeCode拿到的是 Boolean,走 1 字节而非 4 字节);
  • 静态缓存让每次Read<T>/Write<T>都无需重复反射,这是 MemorySharp 内存读写保持高性能的基础。

注册表快速通道:CanBeStoredInRegisters 的寄存器优化

CanBeStoredInRegisters是 MarshalType 最有趣的优化开关。如果类型是IntPtr或各类整数、boolchar、单精度浮点(以及 x64 下的 64 位整数),它就能直接塞进 CPU 寄存器。

这个判断直接影响PtrToObject的行为:当类型可存入寄存器时,直接把指针本身的值当作数据返回(BitConverter.GetBytes(pointer.ToInt64()));否则才真正去远程进程读取Size字节。这解释了为什么MarshalType<long>.PtrToObject(sharp, new IntPtr(32))能直接返回 32——它读的不是指针指向的内存,而是指针数值本身。

实战流程:一次完整的内存读写如何运转

把上面的知识点串起来,一次sharp.Read<Point>(address)的完整链路是:

  1. MarshalType<Point>.Size向远程进程读取原始字节;
  2. 调用ByteArrayToObject,因Point是结构体(TypeCode.Object)而非基础类型,进入非托管内存回退路径;
  3. Marshal.PtrToStructure还原出Point对象返回给调用方。

你可以直接看test/IntegrationTests/Internals/MarshalTypeTests.cs中的ToManagedObjectPointStruct测试:把结构体转成字节数组,再转回结构体,断言两者相等,完整验证了这条往返链路的正确性。

常见问题与注意事项

  • 字符串转换会抛异常MarshalType<T>.ObjectToByteArrayByteArrayToObject遇到string都会抛InvalidCastException,字符串应交给MarshalValue处理,它会自动在远程进程分配长度 + 1字节(含结束符\0)并写入;
  • 结构体必须可封送:走回退路径的结构体需要满足[StructLayout]约束,否则StructureToPtr会失败;
  • 区分"指针值"与"指针指向的值"PtrToObject对寄存器型类型返回的是指针数值本身,不要与Read<T>(address)读取的"地址处的数据"混淆;
  • 批量读取有额外优化MemorySharp.cs中读取T[]数组时,若Tbyte会直接Buffer.BlockCopy,跳过逐元素转换。

结语:读懂 MarshalType,就读懂了 MemorySharp 的数据流

MarshalType<T>用不到 300 行代码,把一个看似简单的"类型 ↔ 字节"转换做到了极致:静态缓存类型元数据、BitConverter快速通道、非托管内存通用回退、寄存器级优化判断,四者环环相扣。无论你是想深入理解 MemorySharp 内存读写原理,还是准备二次开发这个 C# 内存编辑库,源码都值得反复研读——相关实现集中在src/MemorySharp/Internals/MarshalType.cssrc/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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询