Адрес памяти объекта в С#
У меня есть функция, написанная некоторое время назад (для .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;
}
}
Я получаю ошибку:
Объект содержит непримитивные или неблизкие данные.
Почему это больше не работает?
Как я могу теперь получить адрес памяти управляемых объектов?
Ответы
Ответ 1
Вы можете использовать GCHandleType.Weak вместо закрепленного. С другой стороны, есть еще один способ получить указатель на объект:
object o = new object();
TypedReference tr = __makeref(o);
IntPtr ptr = **(IntPtr**)(&tr);
Требует небезопасного блока и очень, очень опасен и не должен использоваться вообще. ☺
Редакция 2019 года: поскольку этому ответу удалось привлечь некоторое внимание, я чувствую, что нужно объяснить, что на самом деле делает код, чтобы это был правильный ответ.
Во-первых, в то время, когда в С# не было возможности использовать __makeref
, существовал один недокументированный механизм, который мог бы выполнить аналогичное действие - __makeref
.
object o = new object();
ref object r = ref o;
//roughly equivalent to
TypedReference tr = __makeref(o);
Есть одно важное отличие в том, что TypedReference является "универсальным"; его можно использовать для хранения ссылки на переменную любого типа. Для __refvalue(tr, object)
к такой ссылке требуется указать ее тип, например, __refvalue(tr, object)
, и, если она не совпадает, __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 требует, чтобы тип был легковесным, чтобы закрепить его. Он имеет преимущество в том, что не использует детали реализации, недокументированные ключевые слова и взлом памяти.
Ответ 2
Вместо этого кода вы должны вызвать GetHashCode()
, который вернет уникальное значение (hopefully-) для каждого экземпляра.
Вы также можете использовать ObjectIDGenerator
класс, который гарантированно будет уникальным.
Ответ 3
Там лучшее решение, если вам действительно не нужен адрес памяти, а некоторые средства уникальной идентификации управляемого объекта:
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()
здесь, потому что он прост и безопасен по потоку.
Update
Я пошел вперед и создал пакет Nuget Overby.Extensions.Attachments, который содержит некоторые методы расширения для присоединения объектов к другим объектам. Там есть расширение, называемое GetReferenceId()
, которое эффективно выполняет то, что показывает код в этом ответе.
Ответ 4
Когда вы освобождаете этот дескриптор, сборщик мусора может перемещать память, которая была закреплена. Если у вас есть указатель на память, который должен быть закреплен, и вы отключаете эту память, тогда все ставки отключены. То, что это работало вообще в 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
Это работает для меня...
#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
Ответ 6
Переключите тип распределения:
GCHandle handle = GCHandle.Alloc(a, GCHandleType.Normal);
Ответ 7
Вот простой способ, который я придумал, который не включает небезопасный код или закрепление объекта. Также работает в обратном порядке (объект с адреса):
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;
}
}
Ответ 8
Получение адреса произвольного объекта в .NET невозможно, но может быть выполнено, если вы измените исходный код и используете моно. См. Инструкции здесь: Получить адрес памяти объекта .NET(С#)