얼마 전에 작성된 함수 (.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 (new Car ( “blue”))
public class Car
{
public string Color;
public Car(string color)
{
Color = color;
}
}
오류가 발생합니다.
객체에 원시 데이터가 아니거나 블리트 할 수없는 데이터가 있습니다.
왜 더 이상 작동하지 않습니까?
이제 관리 대상 개체의 메모리 주소를 어떻게 얻을 수 있습니까?
답변
Pinned 대신 GCHandleType.Weak을 사용할 수 있습니다. 반면에 객체에 대한 포인터를 얻는 또 다른 방법이 있습니다.
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 가 “generic” 이라는 점에는 한 가지 중요한 차이점이 있습니다 . 모든 유형의 변수에 대한 참조를 저장하는 데 사용할 수 있습니다. 이러한 참조에 액세스하려면 해당 유형 (예 :)을 지정해야하며 __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로 설정해야하므로 개체를 고정하는 가장 쉬운 방법 입니다. 구현 세부 사항, 문서화되지 않은 키워드 및 메모리 해킹을 사용하지 않는 이점이 있습니다.
답변
이 코드 대신을 호출해야합니다 GetHashCode()
. 그러면 각 인스턴스에 대해 고유 한 값이 반환됩니다.
고유 한 ObjectIDGenerator
클래스를 사용할 수도 있습니다 .
답변
실제로 메모리 주소가 필요하지 않고 관리 대상 개체를 고유하게 식별하는 수단이 필요한 경우 더 나은 솔루션이 있습니다.
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에서 전혀 작동하지 않았다는 것은 아마도 운이 좋을 것입니다. 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();
}
}
답변
안전하지 않은 코드 나 객체 고정을 포함하지 않는 간단한 방법이 있습니다. 또한 역으로 작동합니다 (주소의 객체) :
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;
}
}
답변
이것은 나를 위해 작동합니다 …
#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
답변
할당 유형을 전환합니다.
GCHandle handle = GCHandle.Alloc(a, GCHandleType.Normal);
