.net C#中对象的内存地址

dw1jzc5e  于 2023-11-20  发布在  .NET
关注(0)|答案(9)|浏览(181)

我有一个前一段时间写的函数(为.NET3.5),现在我已经升级到4.0
我不能让它工作。
函数为:

public static class MemoryAddress
{
    public static string Get(object a)
    {
        GCHandle handle = GCHandle.Alloc(a, GCHandleType.Pinned);
        IntPtr pointer = GCHandle.ToIntPtr(handle);
        handle.Free();
        return "0x" + pointer.ToString("X");
    }
}

字符串
现在,当我调用它- MemoryAddress.Get(new Car(“blue”))时,

public class Car
{
    public string Color;
    public Car(string color)
    {
        Color = color;
    }
}


我得到的错误:
对象包含非基元数据或不可直接复制到原结构中的数据。
为什么不管用了?
我现在如何获得托管对象的内存地址?

b0zn9rqh

b0zn9rqh1#

你可以使用GCHandleType.Weak而不是Pinned。另一方面,还有另一种方法可以获得指向对象的指针:

object o = new object();
TypedReference tr = __makeref(o);
IntPtr ptr = **(IntPtr**)(&tr);

字符串
需要不安全的块,是非常,非常危险的,不应该使用。
在C#中不可能使用by-ref局部变量的时候,有一种没有文档的机制可以完成类似的事情-__makeref

object o = new object();
ref object r = ref o;
//roughly equivalent to
TypedReference tr = __makeref(o);


有一个重要的区别是 TypedReference 是“泛型”的;它可以用来存储对任何类型的变量的引用。如果这样的引用需要指定其类型,例如__refvalue(tr, object),如果不匹配,则抛出异常。
为了实现类型检查,TypedReference 必须有两个字段,一个是变量的实际地址,另一个是指向其类型表示的指针。碰巧地址是第一个字段。
因此,__makeref首先用于获取对变量o的引用。强制转换(IntPtr**)(&tr)将结构视为数组(通过指针表示)IntPtr*(指向通用指针类型的指针),通过指向它的指针访问。指针首先被解引用以获得第一字段,然后指针再次被解引用以获得实际存储在变量o中的值-指向对象本身的指针。
2012年,我提出了一个更好、更安全的解决方案:

public static class ReferenceHelpers
{
    public static readonly Action<object, Action<IntPtr>> GetPinnedPtr;

    static ReferenceHelpers()
    {
        var dyn = new DynamicMethod("GetPinnedPtr", typeof(void), new[] { typeof(object), typeof(Action<IntPtr>) }, typeof(ReferenceHelpers).Module);
        var il = dyn.GetILGenerator();
        il.DeclareLocal(typeof(object), true);
        il.Emit(OpCodes.Ldarg_0);
        il.Emit(OpCodes.Stloc_0);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Ldloc_0);
        il.Emit(OpCodes.Conv_I);
        il.Emit(OpCodes.Call, typeof(Action<IntPtr>).GetMethod("Invoke"));
        il.Emit(OpCodes.Ret);
        GetPinnedPtr = (Action<object, Action<IntPtr>>)dyn.CreateDelegate(typeof(Action<object, Action<IntPtr>>));
    }
}


这创建了一个动态方法,该方法首先固定对象(因此其存储不会在托管堆中移动),然后执行一个接收其地址的委托。在委托执行期间,对象仍然被固定,因此可以安全地通过指针进行操作:

object o = new object();
ReferenceHelpers.GetPinnedPtr(o, ptr => Console.WriteLine(Marshal.ReadIntPtr(ptr) == typeof(object).TypeHandle.Value)); //the first pointer in the managed object header in .NET points to its run-time type info


这是最简单的固定对象的方法,因为 GCHandle 要求类型是可复制的,以便固定它。它的优点是不使用实现细节,未记录的关键字和内存黑客。

68de4m5k

68de4m5k2#

您应该调用GetHashCode()而不是这段代码,它将为每个示例返回一个(希望是-)唯一的值。
您也可以使用ObjectIDGenerator class,它保证是唯一的。

lsmepo6l

lsmepo6l3#

如果你不需要内存地址,而是需要一些唯一标识托管对象的方法,那么有一个更好的解决方案:

using System.Runtime.CompilerServices;

public static class Extensions
{
    private static readonly ConditionalWeakTable<object, RefId> _ids = new ConditionalWeakTable<object, RefId>();

    public static Guid GetRefId<T>(this T obj) where T: class
    {
        if (obj == null)
            return default(Guid);

        return _ids.GetOrCreateValue(obj).Id;
    }

    private class RefId
    {
        public Guid Id { get; } = Guid.NewGuid();
    }
}

字符串
这是线程安全的,并且在内部使用弱引用,所以你不会有内存泄漏。
你可以使用任何你喜欢的密钥生成方法,我在这里使用Guid.NewGuid()是因为它简单而且线程安全。

更新

我继续创建了一个Nuget包Overby.Extensions.Attachments,其中包含一些用于将对象附加到其他对象的扩展方法。有一个名为GetReferenceId()的扩展可以有效地执行此答案中的代码。

qmb5sa22

qmb5sa224#

当你释放句柄时,垃圾收集器就可以自由地移动被固定的内存。如果你有一个指向内存的指针,而你又不固定该内存,那么所有的赌注都被取消了。这在Python 3.5中工作可能只是运气好。JIT编译器和Python 4.0的运行时可能在对象生存期分析方面做得更好。
如果你真的想这样做,你可以使用try/finally来防止对象被取消固定,直到你使用它:

public static string Get(object a)
{
    GCHandle handle = GCHandle.Alloc(a, GCHandleType.Pinned);
    try
    {
        IntPtr pointer = GCHandle.ToIntPtr(handle);
        return "0x" + pointer.ToString("X");
    }
    finally
    {
        handle.Free();
    }
}

字符串

n1bvdmb6

n1bvdmb65#

这里有一个我想到的简单方法,它不涉及不安全的代码或固定对象。也可以反向工作(从地址的对象):

public static class AddressHelper
{
    private static object mutualObject;
    private static ObjectReinterpreter reinterpreter;

    static AddressHelper()
    {
        AddressHelper.mutualObject = new object();
        AddressHelper.reinterpreter = new ObjectReinterpreter();
        AddressHelper.reinterpreter.AsObject = new ObjectWrapper();
    }

    public static IntPtr GetAddress(object obj)
    {
        lock (AddressHelper.mutualObject)
        {
            AddressHelper.reinterpreter.AsObject.Object = obj;
            IntPtr address = AddressHelper.reinterpreter.AsIntPtr.Value;
            AddressHelper.reinterpreter.AsObject.Object = null;
            return address;
        }
    }

    public static T GetInstance<T>(IntPtr address)
    {
        lock (AddressHelper.mutualObject)
        {
            AddressHelper.reinterpreter.AsIntPtr.Value = address;
            T obj = (T)AddressHelper.reinterpreter.AsObject.Object;
            AddressHelper.reinterpreter.AsObject.Object = null;
            return obj;
        }
    }

    // I bet you thought C# was type-safe.
    [StructLayout(LayoutKind.Explicit)]
    private struct ObjectReinterpreter
    {
        [FieldOffset(0)] public ObjectWrapper AsObject;
        [FieldOffset(0)] public IntPtrWrapper AsIntPtr;
    }

    private class ObjectWrapper
    {
        public object Object;
    }

    private class IntPtrWrapper
    {
        public IntPtr Value;
    }
}

字符串

sg3maiej

sg3maiej6#

这对我来说很有效...

#region AddressOf

    /// <summary>
    /// Provides the current address of the given object.
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public static System.IntPtr AddressOf(object obj)
    {
        if (obj == null) return System.IntPtr.Zero;

        System.TypedReference reference = __makeref(obj);

        System.TypedReference* pRef = &reference;

        return (System.IntPtr)pRef; //(&pRef)
    }

    /// <summary>
    /// Provides the current address of the given element
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="t"></param>
    /// <returns></returns>
    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public static System.IntPtr AddressOf<T>(T t)
        //refember ReferenceTypes are references to the CLRHeader
        //where TOriginal : struct
    {
        System.TypedReference reference = __makeref(t);

        return *(System.IntPtr*)(&reference);
    }

    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    static System.IntPtr AddressOfRef<T>(ref T t)
    //refember ReferenceTypes are references to the CLRHeader
    //where TOriginal : struct
    {
        System.TypedReference reference = __makeref(t);

        System.TypedReference* pRef = &reference;

        return (System.IntPtr)pRef; //(&pRef)
    }

    /// <summary>
    /// Returns the unmanaged address of the given array.
    /// </summary>
    /// <param name="array"></param>
    /// <returns><see cref="IntPtr.Zero"/> if null, otherwise the address of the array</returns>
    [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
    public static System.IntPtr AddressOfByteArray(byte[] array)
    {
        if (array == null) return System.IntPtr.Zero;

        fixed (byte* ptr = array)
            return (System.IntPtr)(ptr - 2 * sizeof(void*)); //Todo staticaly determine size of void?
    }

    #endregion

字符串

ef1yzkbh

ef1yzkbh7#

切换分配类型:

GCHandle handle = GCHandle.Alloc(a, GCHandleType.Normal);

字符串

1mrurvl1

1mrurvl18#

对.net core

public static string Get(object a)
{
    var ptr = Unsafe.As<object, IntPtr>(ref a);
    return $"0x{ptr:X}";
}

字符串

ruoxqz4g

ruoxqz4g9#

在.NET中获取任意对象的地址是不可能的,但如果您更改源代码并使用mono,则可以完成。请参阅此处的说明:获取.NET对象的内存地址(C#)

相关问题