【问题标题】:How to get a raw memory pointer to a managed class?如何获取指向托管类的原始内存指针?
【发布时间】:2015-11-19 11:29:46
【问题描述】:

如何在 C# 中找到指向 托管 类的原始指针,并且希望它是内存中的原始大小?显然,CLR 不允许这样做 - 更准确地说,是严格禁止的,因为出于稳定性和安全原因,永远不应使用托管类的非托管表示 - 所以我正在寻找一个 hack。我不是在寻找序列化 - 我确实需要一个托管类的转储,因为它在原始内存中表示。

更准确地说,我在以下示例中寻找类似函数getObjectPtr 的东西:

IntPtr getObjectPtr(Object managedClass) {...}

void main() {
    var test=new TestClass();
    IntPtr* ptr_to_test=getObjectPtr(test);
    Console.WriteLine(ptr_to_test.ToString());
}

提前致谢!

编辑: 我终于自己找到了一个解决方案,当我回来发布它作为答案时,我对这么快已经发布的答案的数量感到非常惊讶......感谢你们所有人!这非常快,而且完全出乎意料。

最接近我的解决方案是@thehennyy 的解决方案,但我没有发布它,因为@Chino 提出了更好的解决方案(对不起,我一开始误认为它是错误的,我只是忘了再次取消引用指针)。它不需要代码是不安全的,并且更能容忍 GC:

class Program
{
    // Here is the function in case anyone needs it.
    // Note, though, it does not preserve the handle while you work with
    // pointer, so it is less reliable than the code in Main():
    static IntPtr getPointerToObject(Object unmanagedObject)
    {
        GCHandle gcHandle = GCHandle.Alloc(unmanagedObject, GCHandleType.WeakTrackResurrection);
        IntPtr thePointer = Marshal.ReadIntPtr(GCHandle.ToIntPtr(gcHandle));
        gcHandle.Free();
        return thePointer;
    }
    class TestClass
    {
        uint a = 0xDEADBEEF;
    }
    static void Main(string[] args)
    {
        byte[] cls = new byte[16];

        var test = new TestClass();

        GCHandle gcHandle = GCHandle.Alloc(test, GCHandleType.WeakTrackResurrection);
        IntPtr thePointer = Marshal.ReadIntPtr(GCHandle.ToIntPtr(gcHandle));
        Marshal.Copy(thePointer, cls, 0, 16); //Dump first 16 bytes...
        Console.WriteLine(BitConverter.ToString(BitConverter.GetBytes(thePointer.ToInt32())));
        Console.WriteLine(BitConverter.ToString(cls));

        Console.ReadLine();

        gcHandle.Free();
    }
}
/* Example output (yours should be different):
40-23-CA-02
4C-38-04-01-EF-BE-AD-DE-00-00-00-80-B4-21-50-73

That field's value is "EF-BE-AD-DE", 0xDEADBEEF as it is stored in memory. Yay, we found it!
*/

Hovewer,现在我有点不知所措。根据this的文章,类中的前2个地址应该是指向SyncBlock和RTTI结构的指针,因此第一个字段的地址必须偏移2个字[32位系统中8字节,64位系统中16字节系统] 从一开始。我的是 64 位的;但是,正如您在输出中看到的,很明显第一个字段与对象地址的原始偏移量只有 4 个字节,这没有任何意义。

我以separate question 的身份提出了这个问题。 也许我应该将此作为一个单独的问题提出,但我的解决方案可能存在错误。

【问题讨论】:

  • 简短的免责声明:我在谷歌上一无所获,因为搜索结果通常是指诸如“C++ 指针”和“C# 教程对傻瓜不安全”之类的内容,而我找不到合适的我自己在 C# 中的方法(包括查看 Marshal 类)。我很确定之前应该问过这个问题,但是在 SO 上查找“原始 C# 指针”、“C# 指向类的指针”、“指向类内存的原始指针”和“指向托管类的原始指针”没有产生任何结果。
  • 一般情况下你不能这样做,因为在你的转储过程中 GC 可以将你的对象移动到其他位置。
  • 或许,你可以使用var h = GCHandle.Alloc(obj); var address = (IntPtr)h;。
  • 也许你应该用你的编辑内容打开一个新问题。
  • @thehennyy,谢谢!我刚刚做了,并在这个问题中包含了指向新问题的链接。

标签: c# pointers reflection


【解决方案1】:

嘿,这就是你想要的吗?:

GCHandle gcHandle = GCHandle.Alloc(yourObject,GCHandleType.WeakTrackResurrection);
IntPtr thePointer = GCHandle.ToIntPtr(gcHandle);  

【讨论】:

  • 您的答案甚至不需要像以前接受的那样“不安全”的代码!抱歉,我一开始没有注意到。当我第一次测试您的解决方案时,我认为它是错误的,但在我决定写评论后,我又运行了一些测试以确定确切的错误并突然意识到我只是忘记再次取消引用它:GCHandle gcHandle = GCHandle.Alloc(test, GCHandleType.WeakTrackResurrection); IntPtr thePointer = Marshal.ReadIntPtr(GCHandle.ToIntPtr(gcHandle)); Marshal.Copy(thePointer, cls, 0, 16); WriteAsHexArray(cls);谢谢!它更快更好!
【解决方案2】:

您可以编写一个泄漏对象地址的小 IL 函数。

var o = new object();

var d = new DynamicMethod("GetPtr", typeof(IntPtr), new Type[] {typeof(object)}, Assembly.GetExecutingAssembly().ManifestModule);
var il = d.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ret);

var address = (IntPtr)d.Invoke(null, new object[] {o});
Console.WriteLine(address);

来源为:IllidanS4 / SharpUtils / UnsafeTools.cs

【讨论】:

  • 谢谢!它很简洁,但结果证明对 IL Emiter 的这种使用会产生一些后果。我第一次尝试它时,我必须启用不安全代码,否则它会在运行委托时抛出“运行时可以置于不稳定状态”异常。我不记得它的确切名称,也无法重现它,因为现在代码可以顺利运行,没有“不安全”-s 和任何异常。也许它使用存储在缓存中某处的编译函数,也许它完全接受这个 DynamicMethod 是一个安全且经过验证的......有很多选择,但事实是这个解决方案是不稳定的。
  • @DeFazer 由于 O 和 * 之间的转换,该方法无法验证。尝试使用我库中的版本,它应该绕过可验证性检查。
猜你喜欢
  • 1970-01-01
  • 2015-10-20
  • 2011-09-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-21
  • 1970-01-01
相关资源
最近更新 更多