Ich habe vor einiger Zeit eine Funktion geschrieben (für .NET 3.5) und jetzt, da ich auf 4.0 aktualisiert habe
Ich kann es nicht zum Laufen bringen.
Die Funktion ist:
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");
}
}
Nun, wenn ich es nenne - MemoryAddress.Get (neues Auto ("blau"))
public class Car
{
public string Color;
public Car(string color)
{
Color = color;
}
}
Ich bekomme den Fehler:
Das Objekt enthält nicht primitive oder nicht blittbare Daten.
Warum funktioniert es nicht mehr?
Wie kann ich jetzt die Speicheradresse von verwalteten Objekten erhalten?
object.ReferenceEquals
Methode verwenden. Es sagt Ihnen nicht wirklich, was die Referenz ist, aber es gibt einen Booleschen Wert zurück, der angibt, ob die beiden Objekte auf denselben Heap-Speicherort verweisen oder nicht. (Hoffentlich hilft das jemandem.)GCHandle.ToIntPtr
Gibt eine interne Darstellung des Handles selbst zurück, nicht die Adresse des Objekts, auf das es zeigt. Wenn Sie mehrere Handles für dasselbe Objekt erstellen,GCHandle.ToIntPtr
werden für jedes Handle unterschiedliche Ergebnisse zurückgegeben. EsGCHandle.AddrOfPinnedObject
gibt die Adresse des Objekts zurück, auf das das Handle zeigt. Weitere Informationen finden Sie unter GCHandle.ToIntPtr vs. GCHandle.AddrOfPinnedObject .Antworten:
Sie können GCHandleType.Weak anstelle von Pinned verwenden. Auf der anderen Seite gibt es eine andere Möglichkeit, einen Zeiger auf ein Objekt zu erhalten:
object o = new object(); TypedReference tr = __makeref(o); IntPtr ptr = **(IntPtr**)(&tr);
Benötigt einen unsicheren Block und ist sehr, sehr gefährlich und sollte überhaupt nicht verwendet werden. ☺
Damals, als in C # keine Einheimischen mit Ref-Funktion möglich waren, gab es einen undokumentierten Mechanismus, der etwas Ähnliches erreichen konnte -
__makeref
.object o = new object(); ref object r = ref o; //roughly equivalent to TypedReference tr = __makeref(o);
Es gibt einen wichtigen Unterschied darin, dass TypedReference "generisch" ist. Es kann verwendet werden, um einen Verweis auf eine Variable eines beliebigen Typs zu speichern. Für den Zugriff auf eine solche Referenz muss deren Typ angegeben werden, z. B.
__refvalue(tr, object)
wenn sie nicht übereinstimmt, wird eine Ausnahme ausgelöst.Um die Typprüfung zu implementieren, muss TypedReference zwei Felder haben, eines mit der tatsächlichen Adresse der Variablen und eines mit einem Zeiger auf die Typendarstellung . Es ist einfach so, dass die Adresse das erste Feld ist.
Daher
__makeref
wird zuerst verwendet, um eine Referenz auf die Variable zu erhalteno
. Die Umwandlung(IntPtr**)(&tr)
behandelt die Struktur als ein Array (dargestellt durch einen Zeiger) vonIntPtr*
(Zeiger auf einen generischen Zeigertyp), auf das über einen Zeiger zugegriffen wird. Der Zeiger wird zuerst dereferenziert, um das erste Feld zu erhalten, und der Zeiger dort wird erneut dereferenziert, um den tatsächlich in der Variablen gespeicherten Wert zu erhalteno
- den Zeiger auf das Objekt selbst.Jedoch , seit 2012, ich habe eine bessere und sicherere Lösung zu kommen:
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>>)); } }
Dadurch wird eine dynamische Methode erstellt, die zuerst das Objekt fixiert (damit sich sein Speicher nicht im verwalteten Heap verschiebt) und dann einen Delegaten ausführt, der seine Adresse erhält. Während der Ausführung des Delegaten bleibt das Objekt fixiert und kann somit sicher über den Zeiger bearbeitet werden:
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
Dies ist der einfachste Weg, ein Objekt zu pinnen , da GCHandle erfordert, dass der Typ blittbar ist, um es zu pinnen. Es hat den Vorteil, dass keine Implementierungsdetails, nicht dokumentierte Schlüsselwörter und Speicherhacking verwendet werden.
quelle
object
. Wenn Sie etwas Numerisches möchten, können sowohl ObjectIDGenerator als auch GCHandle verwendet werden, um eine eindeutige Nummer für ein Objekt zu erstellen.IntPtr ptr = *(IntPtr*)&tr
??Anstelle dieses Codes sollten Sie aufrufen
GetHashCode()
, der für jede Instanz einen (hoffentlich) eindeutigen Wert zurückgibt.Sie können auch die
ObjectIDGenerator
Klasse verwenden , die garantiert eindeutig ist.quelle
Es gibt eine bessere Lösung, wenn Sie die Speicheradresse nicht wirklich benötigen, sondern ein verwaltetes Objekt eindeutig identifizieren können:
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(); } }
Dies ist threadsicher und verwendet intern schwache Referenzen, sodass keine Speicherlecks auftreten.
Sie können jedes beliebige Schlüsselgenerierungsmittel verwenden. Ich benutze
Guid.NewGuid()
hier, weil es einfach und threadsicher ist.Aktualisieren
Ich habe ein Nuget-Paket Overby.Extensions.Attachments erstellt , das einige Erweiterungsmethoden zum Anhängen von Objekten an andere Objekte enthält. Es gibt eine Erweiterung namens
GetReferenceId()
, die effektiv das tut, was der Code in dieser Antwort zeigt.quelle
System.Guid guid1 = Overby.Extensions.Attachments.AttachmentExtensions.GetReferenceId(myObject);
Wenn Sie dieses Handle freigeben, kann der Garbage Collector den angehefteten Speicher verschieben. Wenn Sie einen Zeiger auf den Speicher haben, der fixiert werden soll, und diesen Speicher entfernen, sind alle Wetten deaktiviert. Dass dies in 3.5 überhaupt funktionierte, war wahrscheinlich nur ein Glücksfall. Der JIT-Compiler und die Laufzeit für 4.0 machen die Analyse der Objektlebensdauer wahrscheinlich besser.
Wenn Sie dies wirklich tun möchten, können Sie a verwenden
try/finally
, um zu verhindern, dass das Objekt erst nach der Verwendung entfernt wird: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(); } }
quelle
a
verschoben werden kann, nachdem es nicht fixiert wurde, aber ich denke nicht, dass sich Ihr Codebeispiel und die OPs unterschiedlich verhalten. Beide rechnenpointer
vorherhandle.Free()
.Hier ist ein einfacher Weg, den ich mir ausgedacht habe, bei dem es nicht um unsicheren Code oder das Fixieren des Objekts geht. Funktioniert auch umgekehrt (Objekt von Adresse):
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; } }
quelle
Das funktioniert bei mir ...
#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
quelle
AddressOf(object obj)
In Ihrem Code wird die Adresse des zugewiesenen Stapels zurückgegebenTypedReference
, nicht die Objektadresse. Um die Objektadresse zu erhalten, müssen Sie wie in dieser Antwort zweimal dereferenzieren .Wechseln Sie den Zuordnungstyp:
quelle
GCHandle
Gibt für alle angehefteten oder nicht angehefteten TypenGCHandle.ToIntPtr
eine interne Darstellung des Handles selbst zurück, nicht die Adresse des Objekts, auf das es zeigt. Wenn Sie mehrere Handles für dasselbe Objekt erstellen,GCHandle.ToIntPtr
werden für jedes Handle unterschiedliche Ergebnisse zurückgegeben. EsGCHandle.AddrOfPinnedObject
gibt die Adresse des Objekts zurück, auf das das Handle verweist. Sie können diese Methode jedoch nur für angeheftete Handles verwenden.Das Abrufen der Adresse eines beliebigen Objekts in .NET ist nicht möglich, kann jedoch erfolgen, wenn Sie den Quellcode ändern und Mono verwenden. Siehe Anweisungen hier: Speicheradresse des .NET-Objekts abrufen (C #)
quelle