2012-01-27 12 views
5

mam jakiś kod przeznaczony dostać struct z tablicy bajtów:Dlaczego nie mogę użyć Marshal.Copy() do aktualizacji struct?

public static T GetValue<T>(byte[] data, int start) where T : struct 
    { 
     T d = default(T); 
     int elementsize = Marshal.SizeOf(typeof(T)); 

     GCHandle sh = GCHandle.Alloc(d, GCHandleType.Pinned); 
     Marshal.Copy(data, start, sh.AddrOfPinnedObject(), elementsize); 
     sh.Free(); 

     return d; 
    } 

Jednak struktura d nigdy nie jest modyfikowany, a zawsze zwraca jego wartość domyślną.

Sprawdziłem "prawidłowy" sposób, aby to zrobić i używam tego zamiast tego, ale jestem nadal ciekawy, ponieważ nie rozumiem, dlaczego powyższe nie powinno działać.

Jest to tak proste, jak to tylko możliwe: przydziel trochę pamięci, d, zdobądź do niej wskaźnik, skopiuj kilka bajtów do pamięci wskazanej przez to, return. Co więcej, , ale kiedy używam podobnego kodu, ale d jest tablicą T, działa dobrze. O ile sh.AddrOfPinnedObject() naprawdę nie wskazuje na d, ale jaki jest tego sens?

Czy ktoś może mi powiedzieć, dlaczego powyższe nie działa?

+0

Po prostu z ciekawości, jaki był "właściwy" sposób? – Dmitry

+0

@Dmitry, Hi, poprawną metodą jest użycie PtrToStructure() przekazując wskaźnik do niezarządzanej pamięci zawierającej zawartość struktury, jak to opisano tutaj: http://msdn.microsoft.com/en-us/library/4ca6d5z7.aspx Korzystanie z reflektora jest widoczne PtrToStructure() tworzy nowy obiekt i zapełnia go, ale jak to działa Nie jestem pewien, ponieważ te szczegóły, które moim zdaniem są w CLR, których nie widzę (http://stackoverflow.com/questions/11788625/pinvoke-win32-function-for-marshal-ptrtostructure-in-silverlight-5) – sebf

Odpowiedz

4

Ostrzeżenie o szczegółach implementacji alertu może nie być prawdziwe w przyszłych wersjach .Net.

structs są typami wartości i są (ogólnie) przechowywane na stosie (*), a nie na stercie. Adres struktury jest bez znaczenia, ponieważ są przekazywane przez wartość, a nie przez odniesienie. Tablica struct jest typem odniesienia, czyli wskaźnikiem do pamięci na stercie, więc adres w pamięci jest całkowicie poprawny.

Punktem AddrOfPinnedObject jest, aby adres w obiektu thats pamięci jest przypięty, a nie struct.

Dodatkowo Eric Lippert napisał a series of very good blog posts na temat typów odniesienia i typów wartości.

(*), że:

1 są to pola o klasie
2 są pudełkowej
3 są "uchwycone zmienne"
4 są w bloku iteracyjnej

(Nb pkt 3 i 4 są następstwami pkt 1)

+1

Należy zauważyć, że 'structs' może być przechowywany na' heap', stanie się to, gdy są członkami 'class' – gdoron

+0

@gdoron Dobry punkt, odpowiedź zaktualizowana. –

+0

To ma sens, dziękuję! – sebf

1

Oto przykład praca:

public static T GetValue<T>(byte[] data, int start) where T : struct 
{ 
    int elementsize = Marshal.SizeOf(typeof(T)); 

    IntPtr ptr = IntPtr.Zero; 

    try 
    { 
     ptr = Marshal.AllocHGlobal(elementsize); 

     Marshal.Copy(data, start, ptr, elementsize); 
     return (T)Marshal.PtrToStructure(ptr, typeof(T)); 
    } 
    finally 
    { 
     if (ptr != IntPtr.Zero) 
     { 
      Marshal.FreeHGlobal(ptr); 
     } 
    } 
} 

Ale chciałbym użyć jawnego układu tutaj z powodu struct alignment.

[StructLayout(LayoutKind.Explicit, Size = 3)] 
public struct TestStruct 
{ 
    [FieldOffset(0)] 
    public byte z; 

    [FieldOffset(1)] 
    public short y; 
} 
8
GCHandle sh = GCHandle.Alloc(d, GCHandleType.Pinned); 

To gdzie zaczął problem. Struktura jest wartością typu, GCHandle.Alloc() może przydzielać tylko uchwyty dla typów odniesienia . Rodzaj, którego obiekty są przydzielane na kupie śmieci. I takie, które sprawiają, że przypinanie jest sensowne. Kompilator C# jest tutaj nieco zbyt pomocny, automatycznie wysyła konwersję bokserską, aby ustawić wartość i sprawić, by instrukcja działała. Który jest zwykle bardzo miły i tworzy iluzję, że typy wartości pochodzą z System.Object. Pisanie jak w kaczce.

Problem polega na tym, że Marshal.Copy() zaktualizuje zapakowaną kopię o wartości. Nie Twoja zmienna. Więc nie widzisz, żeby to się zmieniło.

Bezpośrednia aktualizacja wartości struktury jest możliwa tylko przy użyciu metody Marshal.PtrToStructure(). Zawiera wymagane elementy inteligentne, aby przekonwertować opublikowany układ struktury struct (atrybut StructLayout) na układ wewnętrzny. Który nie jest taki sam, a poza tym niemożliwy do wykrycia.

+0

+1 Dziękuję za wyjaśnienie automatycznego boksowania d; W pełni widzę, jak działa mój kod (nie!). – sebf

+2

Typy wartości * są * wyprowadzane z System.Object. To nie jest złudzenie! –

+0

Pewnie. Gdyby abstrakcje nigdy nie wyciekły. –

Powiązane problemy