Почему я не могу использовать Marshal.Copy () для обновления структуры? - PullRequest
5 голосов
/ 27 января 2012

У меня есть некоторый код, предназначенный для получения структуры из байтового массива:

    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;
    }

Однако структура d никогда не изменяется и всегда возвращает значение по умолчанию.

Я искал «правильный» способ сделать это и использую его вместо этого, но мне все еще любопытно, поскольку я не могу понять, почему вышеупомянутое не должно работать.

Это настолько просто, насколько это возможно: выделить немного памяти, d, получить указатель на него, скопировать несколько байтов в память, указанную этим, вернуть.Не только это, , но когда я использую подобный код, но с d, являющимся массивом T, он работает нормально. Если sh.AddrOfPinnedObject () не указывает на d, но тогдасмысл этого?

Может кто-нибудь сказать мне, почему выше не работает?

Ответы [ 3 ]

9 голосов
/ 27 января 2012
    GCHandle sh = GCHandle.Alloc(d, GCHandleType.Pinned);

Вот тут и началась ваша проблема.Структура имеет тип значения , GCHandle.Alloc () может выделять дескрипторы только для ссылочных типов .Вид, чьи объекты расположены в куче мусора.И такие, которые делают пиннинг разумным.Компилятор C # здесь слишком полезен, он автоматически генерирует преобразование в бокс, чтобы упаковать значение и заставить оператор работать.Что обычно очень приятно и создает иллюзию, что типы значений являются производными от System.Object.Печатание типа «Крякает как утка».

Проблема в том, что Marshal.Copy () обновит коробочную копию значения. Не ваша переменная.Таким образом, вы не видите, что это изменится.

Непосредственное обновление значения структуры возможно только с Marshal.PtrToStructure ().Он содержит необходимые смарт-коды для преобразования опубликованного макета структуры (атрибут StructLayout) во внутренний макет.Который не то же самое и в противном случае не обнаруживается.

5 голосов
/ 27 января 2012

Предупреждение о деталях реализации, это может быть не так в будущих версиях .Net.

structs являются типами значений и (как правило) хранятся в стеке (*), а не в куче. Адрес структуры не имеет смысла, так как они передаются по значению, а не по ссылке. Массив struct является ссылочным типом, то есть указателем на память в куче, поэтому адрес в памяти является совершенно допустимым.

Смысл AddrOfPinnedObject состоит в том, чтобы получить адрес объекта , который закреплен в памяти, а не struct .

Кроме того, Эрик Липперт написал серию очень хороших постов в блоге на тему ссылочных типов и типов значений.

(*), если:

1 Это поля в классе
2 Они в штучной упаковке
3 Они являются «захваченными переменными»
4 Они находятся в блоке итератора

(точки 3 и 4 являются следствиями пункта 1)

1 голос
/ 27 января 2012

Вот рабочий пример:

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);
        }
    }
}

Но я бы использовал здесь явное расположение из-за выравнивания структуры .

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

    [FieldOffset(1)]
    public short y;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...