Jak zjistit ukazatel na objekt
Narozdíl od C++ se C# postaral o to, aby nám zjištění ukazatele co nejvíc ztížil. Přesto je to možné. Pokusíme se zjistit ukazatel do paměti na objekt o:
object o = new Object();
Jako první možnost by připadalo použití unsafe kontextu a &o. Unární operátor & ale funguje jen na instance struktur (by-val). Pokud by typ o byl struktura, je toto použití nejlepší.
Co ale v případě, že typ o je instance třídy nebo jiných typů než struktury (by-ref)? Pak existuje několik způsobů. První je použití struktury GCHandle, která získá odkaz na objekt v rámci garbage collectoru.
GCHandle handle = GCHandle.Alloc(o); GCHandle.ToIntPtr(handle);
Metoda ToIntPtr získá ukazatel, ale při každém dalším alokování se bude měnit. Částečně to řeší konstruktor GCHandle.Alloc(o, GCHandleType.Pinned), tento způsob ale lze použít jen na některé objekty.
Druhý způsob je vytvoření pole a zjištění ukazatele na první index.
object[] arr = new object[1]{o}; Marshal.UnsafeAddrOfPinnedArrayElement(arr, 0);
Toto lze použít pro všechny objekty, ale nevýhoda spočívá ve vytvoření samotného pole. Stává se totiž, že se ukazatel změní z důvodu přesunutí objektu v paměti v rámci fragmentace.
Poslední a funkční způsob je vytvoření tzv. typové refence (TypedReference), která slouží k získání managed reference na objekt a jeho typ. Je potřeba v metodě použít atribut unsafe a kód kompilovat s přepínačem "/unsafe".
//Ve třídě: [StructLayout(LayoutKind.Sequential)] struct TypedReferenceStructure { public IntPtr Value; public IntPtr Type; } //V metodě: TypedReference r = __makeref(o); TypedReferenceStructure rs = *((TypedReferenceStructure*)&r);
Struktura TypedReferenceStructure složením odpovídá struktuře TypedReference. Díky tomu lze mezi nimi přes ukazatele převádět. Pole rs.Value obsahuje ukazatel na objekt o. Pole rs.Value obsahuje ukazatel na proměnnou o – nikoliv na samotný objekt –, která obsahuje ukazatel na objekt o – *((IntPtr*)rs.Value)
vrací ukazatel typu IntPtr na objekt. V paměti je třída reprezentována odlišně než struktura, neboť má 4-bajtovou hlavičku. 5. bajt je začátek samotných dat. Kód ((byte*)*((IntPtr*)rs.Value))[4]
zjistí první bajt dat polí třídy.
Napsal jsem, že je lze převádět i obráceně. Tento způsob ale nedoporučuji, protože je velká šance výskytu různých chyb. Předpokládejme, že máme strukturu A s jediným veřejným polem f typu int.
TypedReferenceStructure rs = new TypedReferenceStructure(); A a = new A(); a.f = 256; rs.Value = (IntPtr)(&a); rs.Type = typeof(A).TypeHandle.Value; TypedReference r2 = *((TypedReference*)&rs); A x = __refvalue(r2, A); Console.WriteLine(x.f);
Z tohoto kódu je patrné, že lze typovou referenci tímto způsobem i vytvořit a ručně jí přiřadit hodnoty. Pole Type musí být nastaveno na správný typ (se správným složením), jinak vzniknou problémy, např. že typ x nebude dědit System.Object. Pokud by vše proběhlo správně, mělo by se vypsat 256.
Získání ukazatele na objekt a manipulace s ním je složitá a náchylná na chyby a proto se nedoporučuje.
Editace 5.2.2012: Stejnou metodou, jakou jsem přetypoval TypedReference na TypedReferenceStructure, jde díky typové referenci přetypovat jakýkoliv objekt na jiný typ.
// Je třeba zkompilovat s přepínačem /unsafe /// <summary>Nastaví typ reference na 'object' na T2 a novou referenci vrátí.</summary> /// <param name="object">Objekt, jehož referenci se nastaví nový typ.</param> /// <returns>Reference na 'object' s novým typem.</returns> public static unsafe T2 ChangeReferenceType<T1, T2>(T1 @object) { TypedReference typedref = __makeref(@object); IntPtr* ptr = (IntPtr*)&typedref; ptr[1] = typeof(T2).TypeHandle.Value; return __refvalue(typedref, T2); }
Typové parametry metody ChangeReferenceType musí být oba právě by-ref, nebo by-val.
—————