it-swarm-ru.tech

Адрес памяти объекта в C #

У меня есть функция, написанная некоторое время назад (для .NET 3.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 (новый автомобиль («синий»))

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

Я получаю ошибку: 

Объект содержит непримитивные или неблизкие данные.

Почему это больше не работает?

Как я могу теперь получить адрес памяти управляемых объектов?

60
lejon

Вы можете использовать GCHandleType.Weak вместо закрепленного. С другой стороны, есть еще один способ получить указатель на объект:

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

Требует небезопасного блока и очень, очень опасен и не должен использоваться вообще. ☺

Редакция 2019 года: поскольку этому ответу удалось привлечь некоторое внимание, я чувствую, что нужно объяснить, что на самом деле делает код, чтобы это был правильный ответ.

Во-первых, в то время, когда в C # не было возможности использовать локальные ссылки, был один недокументированный механизм, который мог бы выполнить аналогичную задачу - __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 требует, чтобы тип был блитируемым, чтобы закрепить его. Он имеет преимущество в том, что не использует детали реализации, недокументированные ключевые слова и хакерскую память.

44
IllidanS4

Вместо этого кода вы должны вызвать GetHashCode(), которая будет возвращать (надеюсь) уникальное значение для каждого экземпляра.

Вы также можете использовать ObjectIDGenerator класс , который гарантированно будет уникальным.

17
SLaks

Есть лучшее решение, если вам не нужен адрес памяти, а есть какое-то средство уникальной идентификации управляемого объекта:

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(), которое эффективно выполняет то, что показано в коде этого ответа.

10
Ronnie Overby

Когда вы освобождаете этот дескриптор, сборщик мусора может свободно перемещать память, которая была закреплена. Если у вас есть указатель на память, которая должна быть закреплена, и вы открепляете эту память, тогда все ставки сняты. То, что это работало вообще в 3.5, было, вероятно, просто удачей. JIT-компилятор и среда выполнения для 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();
    }
}
5
Jim Mischel

Это работает для меня ...

#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
2
Jay

Переключите тип alloc:

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

Вот простой способ, который я придумал, который не включает небезопасный код или закрепление объекта. Также работает в обратном порядке (объект с адреса):

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;
            return (T)AddressHelper.reinterpreter.AsObject.Object;
        }
    }

    // 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;
    }
}
0
MulleDK19