[c#] 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 (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);