如何在C#中获取对象属性的内存地址?Celeste游戏Mod开发实践
如何在Celeste中获取房间名称的稳定内存地址?
问题分析
你尝试获取Celeste中房间名称(string类型)的内存地址以创建静态指针,但两次尝试都出现异常:
- GCHandle方法:得到极小且变化的数值——这是因为
GCHandle.ToIntPtr()返回的是CLR内部的句柄标识,并非string对象的实际内存地址。GCHandle的句柄是CLR追踪托管对象的索引,不是指向内存的指针。 - fixed方法:地址不稳定——这是因为
fixed(string* pointer = &level.Session.LevelData.Name)固定的是**string类型的引用变量**的内存位置,而非string对象实例本身的地址。当CLR对string对象进行GC移动时,引用变量的值会更新;不同会话中引用变量的存储位置也可能不同,导致地址波动。
关于ChatGPT的说法:不完全准确
C#可以获取对象属性的指针,但需要区分两种指针:
- 对于值类型属性:可直接获取属性实例的指针。
- 对于引用类型属性:默认获取的是引用变量的指针(存储对象引用的内存地址),而非对象实例的内存地址。但通过正确方式,仍能获取引用类型对象实例的指针。
可行的获取方法
1. 正确使用GCHandle获取string对象实例的地址
通过GCHandle.Alloc固定对象,再用AddrOfPinnedObject()获取对象的实际内存地址:
private void Level_OnLoadLevel(Level level, Player.IntroTypes playerIntro, bool isFromLoader) { unsafe { string roomName = level.Session.LevelData.Name; // 固定string对象,阻止GC移动它 GCHandle handle = GCHandle.Alloc(roomName, GCHandleType.Pinned); // 获取对象实例的内存地址 IntPtr objectAddress = handle.AddrOfPinnedObject(); Logger.Log(LogLevel.Verbose, "AddressLogger", $"房间名称对象地址: {(long)objectAddress:X}"); // 必须释放句柄,避免内存泄漏 handle.Free(); } }
注意:固定的对象在handle.Free()前不会被GC移动,地址暂时稳定,但跨会话无法保证固定——CLR内存布局是动态的,每次启动游戏时托管对象的加载位置都可能不同。
2. 针对静态指针的替代方案
由于托管对象地址受GC影响,无法作为跨会话的静态指针。若要创建静态指针,需:
- 用内存扫描工具(如Cheat Engine)配合Mod日志,找到游戏原生内存中对应房间名称的存储位置(你提到内存中并非以字符串形式存储,可能是游戏自定义编码或字符串池)。
- 追踪该原生地址的静态指针链,跳过托管层,直接访问游戏原生内存数据。
总结
- 你之前的两种方法都未正确获取
string对象实例的内存地址,前者混淆了句柄和指针,后者获取的是引用变量的地址。 - C#可以获取托管对象实例的指针,但托管地址无法跨会话稳定,不适合作为静态指针。
- 若要创建静态指针,应转向游戏原生内存层寻找对应数据的位置。
内容的提问来源于stack exchange,提问作者Ben Arkonovich
相关产品推荐
相关产品推荐

