我有一段时间写的功能(对于.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;
}
}
我收到错误:
对象包含非原始数据或非blittable数据。
为什么它不再起作用?
我现在如何获取托管对象的内存地址?
答案 0 :(得分:46)
您可以使用GCHandleType.Weak而不是Pinned。另一方面,还有另一种获取指向对象的指针的方法:
object o = new object();
TypedReference tr = __makeref(o);
IntPtr ptr = **(IntPtr**)(&tr);
需要不安全的阻止,非常非常危险,根本不应该使用。 ☺
在C#中无法使用by-ref locals的那一天,有一个未记录的机制可以完成类似的事情 - __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 要求类型为blittable以便固定它。它的优点是不使用实现细节,未记录的关键字和内存黑客。
答案 1 :(得分:18)
代替此代码,您应该调用GetHashCode()
,它将为每个实例返回一个(希望如此)唯一值。
您还可以使用ObjectIDGenerator
class,保证其唯一。
答案 2 :(得分:11)
如果您不是真的需要内存地址,而是一些唯一标识托管对象的方法,那么有一个更好的解决方案:
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()
的扩展程序可以有效地执行此答案中的代码所示。
答案 3 :(得分:5)
当你释放该句柄时,垃圾收集器可以自由移动被固定的内存。如果你有一个指向应该被固定的内存的指针,并且你取消固定那个内存,那么所有的赌注都会被关闭。这在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();
}
}
答案 4 :(得分:2)
这对我有用......
#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
答案 5 :(得分:0)
切换alloc类型:
GCHandle handle = GCHandle.Alloc(a, GCHandleType.Normal);
答案 6 :(得分:0)
这是我想到的一种简单方法,它不涉及不安全的代码或固定对象。也可以反向使用(地址中的对象):
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;
}
}
答案 7 :(得分:-2)
无法在.NET中获取任意对象的地址,但如果更改源代码并使用mono,则可以执行此操作。请参阅此处的说明:Get Memory Address of .NET Object (C#)