Marshal.Copy не копирует из массива байтов в структуру, начиная с адреса, указанного в IntPtr

(В некотором смысле это вопрос, ответ на который задают Извлечение структур в середине файла в мои структуры, кроме доступа к файлу байта за байтом и составления их значения.)

У меня есть файл, содержащий запись структуры в своем потоке:

[Start]...[StructureRecord]...[End]

Содержащаяся структура вписывается в этот макет, для которого существует переменная:

Public Structure HeaderStruct
    Public MajorVersion As Short
    Public MinorVersion As Short
    Public Count As Integer         
End Structure

private grHeaderStruct As HeaderStruct

Именно в эту переменную мне нужна копия структуры, находящейся в файле.

Требуемые пространства имен:

'File, FileMode, FileAccess, FileShare:
Imports System.IO

'GCHandle, GCHandleType:
Imports System.Runtime.InteropServices

'SizeOf, Copy:
Imports System.Runtime.InteropServices.Marshal

Мой FileStream называется oFS.

Using oFS = File.Open(sFileName, FileMode.Open, FileAccess.Read)
    ...

Предположим, что oFS сейчас находится в начале [StructureRecord].

Итак, есть 8 байтов (HeaderStruct.Length) для чтения из файла и копирования их в экземпляр записи этой структуры. Для этого я оборачиваю логику чтения из файла необходимого количества байтов и передаю их в свою структурную запись в универсальный метод ExtractStructure. Пункт назначения создается непосредственно перед вызовом процедуры.

    grHeaderStruct = New HeaderStruct
    ExtractStructure(Of HeaderStruct)(oFS, grHeaderStruct)
    ...
End Using

(Прежде чем предлагать методы чтения только этих 8 байтов вне специального метода, вы, вероятно, должны знать, что весь файл состоит из структур, которые зависят друг от друга. В поле Count указано, что за ними следуют 3 дочерние структуры, но эти содержат сами поля Count и т. д. Я думаю, что процедура их получения - неплохая идея.)

Однако сейчас у меня болит голова от этого распорядка:

'Expects to find a structure of type T at the actual position of the 
'specified filestream oFS. Reads this structure into a byte array and copies
'it to the structure variable specified in oStruct.
Public Shared Sub ExtractStructure(Of T As Structure) _
    (oFS As FileStream, ByRef oStruct As T)

    Dim oGCHandle As GCHandle
    Dim oStructAddr As IntPtr
    Dim iStructLen As Integer
    Dim abStreamData As Byte()

    'Obtain a handle to the structure, pinning it so that the garbage
    'collector does not move the object. This allows the address of the 
    'pinned structure to be taken. Requires the use of Free when done.
    oGCHandle = GCHandle.Alloc(oStruct, GCHandleType.Pinned)

    Try
        'Retrieve the address of the pinned structure, and its size in bytes.
        oStructAddr = oGCHandle.AddrOfPinnedObject
        iStructLen = SizeOf(oStruct)

        'From the file's current position, obtain the number of bytes 
        'required to fill the structure.
        ReDim abStreamData(0 To iStructLen - 1)
        oFS.Read(abStreamData, 0, iStructLen)

        'Now both the source data is available (abStreamData) as well as an 
        'address to which to copy it (oStructAddr). Marshal.Copy will do the
        'copying.
        Marshal.Copy(abStreamData, 0, oStructAddr, iStructLen)
    Finally
        'Release the obtained GCHandle.
        oGCHandle.Free()
    End Try
End Sub

Инструкция

oFS.Read(abStreamData, 0, iStructLen)

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

?abstreamdata
{Length=8}
    (0): 1
    (1): 0
    (2): 2
    (3): 0
    (4): 3
    (5): 0
    (6): 0
    (7): 0

Я не могу использовать здесь Marshal.StructureToPtr, потому что, ну, массив байтов - это не структура. Однако класс Marshal также имеет метод Copy.

Только то, что я здесь явно упускаю из виду, потому что

Marshal.Copy(abStreamData, 0, oStructAddr, iStructLen)

выполняет ли нет предполагаемую копию (но также не генерирует исключение):

?ostruct
    Count: 0
    MajorVersion: 0
    MinorVersion: 0

Что я не понимаю, почему эта копия не работает?

Документация MS не слишком много говорит о Маршале. Копия: https://msdn.microsoft.com/en-us/library/ms146625(v=vs.110).aspx:

source - Type: System.Byte() The one-dimensional array to copy from.

startIndex - Type: System.Int32 The zero-based index in the source array where copying should start.

destination - Type: System.IntPtr The memory pointer to copy to.

length - Type: System.Int32 The number of array elements to copy.

Похоже, я все правильно настроил, так возможно ли, что Marshal.Copy копирует не в структуру, а в какое-то другое место?

oStructAddr действительно выглядит как адрес (& H02EB7B64), но где это?

Редактировать

Между созданием параметра, получающего результат, и вызовом подпрограммы

    grHeaderStruct = New HeaderStruct
    ExtractStructure(Of HeaderStruct)(oFS, grHeaderStruct)

Я заполнил созданную запись некоторыми тестовыми данными, чтобы проверить, правильно ли она передана:

#Region "Test"
    With grMultifileDesc
        .MajorVersion = 7
        .MinorVersion = 8
        .Count = 9
    End With
#End Region

В этой процедуре я проверяю значение записи до и после Marshal.Copy в непосредственном окне. Оба раза получаю:

?ostruct
{MyProject.MyClass.HeaderStruct}
    Count: 9
    MajorVersion: 7
    MinorVersion: 8

(Переупорядочение полей записи в вызывающей и вызываемой стороне такое же, что, конечно, само по себе является проблемой, поскольку данные считываются из файла и будут ошибочно скопированы в структуру. Однако это не так. тема вопроса.)

Заключение: данные получены, но Marshal.Copy не произвел уже в вызываемом. Так что это не просто проблема «возвращает неверные данные».

Редактировать 2

Оказывается, Marshal.Copy копирует не данные, как указано в документации, а указатель на данные.

Marshal.ReadByte(oStructAddr) возвращает первый байт массива байтов, Marshal.ReadByte(oStructAddr + 1) - второй байт и т. д.

Но как мне вернуть эти данные в аргумент Out?

Очень хорошо написанный вопрос. Не могли бы вы проверить, работает ли это, если вы сделаете HeaderStructClass вместо Structure ?. Структуры могут находиться в стеке, и я не уверен, можно ли иметь GCHandle для чего-то в стеке. (Так же временная проверка, определенно не постоянное решение).

Nico Schertler 20.04.2018 08:21

@NicoSchertler, спасибо. Это не удается уже при попытке получить дескриптор для oStruct с помощью GCHandle.Alloc (исключение «Объект не содержит примитивных данных.»). Он запускается при замене GCHandleType на Normal (который больше не закрепляет!), Но тогда я не смогу использовать oGCHandle.AddrOfPinnedObject. Использование oStructAddr = CType(oGCHandle, IntPtr) вместо этого работает, но затем SizeOf жалуется, что объект не может быть маршалирован как неуправляемая структура.

Herb 20.04.2018 09:40

@NicoSchertler: Закрепление структуры должно работать, я сам пару раз делал это без проблем. - Herb: Основываясь на ошибке, которую мне удалось найти: stackoverflow.com/a/15549992 - По-видимому, это выбрасывается, когда у вас есть непримитивная переменная в вашей структуре (то есть переменная, указывающая на класс).

Visual Vincent 20.04.2018 10:38

@VisualVincent, структура такая, как показано в OP: она содержит два шорта и одно целое число, в фактическом случае заполненное цифрами 1, 2, 3 (представленное в файле как 0x01 00 02 00 03 00 00 00). Никаких ссылок на объекты (кроме всего прочего). Я обновил ОП, добавив некоторые дополнительные сведения. Очевидно, что Copy копирует не данные согласно документации, а указатель. (Я могу получить копию с помощью ReadByte из этого места, но понятия не имею, как заполнить аргумент out.)

Herb 21.04.2018 08:33

Что вы имеете в виду под «Копировать не копирует данные согласно документации, но указатель»? Marshal.Copy() копирует указанный диапазон байтов в указанный адрес памяти назначения. Однако вам необходимо создать GCHandle с GCHandleType.Pinned и использовать AddrOfPinnedObject, чтобы это работало.

Visual Vincent 21.04.2018 10:21

Если эта часть работает для вашей структуры, пробовали ли вы изменить метод ExtractStructure() на функцию и заставить ее возвращать результирующую структуру вместо использования аргумента out?

Visual Vincent 21.04.2018 10:29
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
1 545
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

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

Dim lenBuffer As Int32 = Marshal.SizeOf(Of HeaderStruct)
Dim buffer As Byte() = New Byte(0 To lenBuffer - 1) {}
Dim gchBuffer As GCHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned)
Dim hs As New HeaderStruct With {.MajorVersion = 4, .MinorVersion = 2, .Count = 5}

' copy to pinned byte array 
Marshal.StructureToPtr(hs, gchBuffer.AddrOfPinnedObject, True)
' buffer = {4, 0, 2, 0, 5, 0, 0, 0} ' array can be written to file

' change the data - simulates array read from file
buffer(0) = 1 ' MajorVersion = 1
buffer(2) = 3 ' MinorVersion = 3
buffer(4) = 2 '.Count = 2

' read from pinned byte array
Dim hs2 As HeaderStruct = Marshal.PtrToStructure(Of HeaderStruct)(gchBuffer.AddrOfPinnedObject)
gchBuffer.Free()

Обновлено: на основании комментария кажется, что OP считает, что показанный выше метод не может быть реализован в качестве общих методов для чтения / записи в поток. Итак, вот пример копирования / вставки.

Sub Example()
    Using ms As New IO.MemoryStream
        Dim hs As New HeaderStruct With {.MajorVersion = 4, .MinorVersion = 2, .Count = 5}
        Debug.Print($"Saved structure:  {hs}")
        WriteStruct(ms, hs)  'write structure to stream

        ms.Position = 0 ' reposition stream to start 
        Dim hs2 As New HeaderStruct ' target for reading from stream
        ReadStruct(ms, hs2)
        Debug.Print($"Retrieved structure: {hs2}")
    End Using
End Sub

Sub WriteStruct(Of T As {Structure})(strm As IO.Stream, ByRef struct As T)
    Dim lenBuffer As Int32 = Marshal.SizeOf(Of T)
    Dim buffer As Byte() = New Byte(0 To lenBuffer - 1) {}
    Dim gchBuffer As GCHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned)
    Marshal.StructureToPtr(struct, gchBuffer.AddrOfPinnedObject, True)
    strm.Write(buffer, 0, lenBuffer)
    gchBuffer.Free()
End Sub

Sub ReadStruct(Of T As {Structure})(strm As IO.Stream, ByRef struct As T)
    Dim lenBuffer As Int32 = Marshal.SizeOf(Of T)
    Dim buffer As Byte() = New Byte(0 To lenBuffer - 1) {}
    strm.Read(buffer, 0, buffer.Length)
    Dim gchBuffer As GCHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned)
    struct = Marshal.PtrToStructure(Of T)(gchBuffer.AddrOfPinnedObject)
    gchBuffer.Free()
End Sub

Public Structure HeaderStruct
    Public MajorVersion As Short
    Public MinorVersion As Short
    Public Count As Integer
    Public Overrides Function ToString() As String
        Return $"MajorVersion = {MajorVersion}, MinorVersion = {MinorVersion}, Count = {Count}"
    End Function
End Structure

Вывод Example:

Saved structure: MajorVersion = 4, MinorVersion = 2, Count = 5

Retrieved structure: MajorVersion = 4, MinorVersion = 2, Count = 5

Как указано в (теперь обновленном) вопросе, задействовано много разных структур. Ваш подход приведет к созданию класса для индивидуальной обработки каждого типа структуры, поэтому я мог бы вообще не объявлять структуры и создавать классы с самого начала.

Herb 21.04.2018 08:37

@herb, этот ответ демонстрирует технику, которая ответила на ваш вопрос. Тот факт, что в примере использовался жестко закодированный тип, не позволяет записать его как универсальный. См. Обновление для общего примера.

TnTinMn 21.04.2018 14:42
Ответ принят как подходящий

Метод ExtractStructure нужно переписать так:

'Expects to find a structure of type T at the actual position of the 
'specified filestream oFS. Reads this structure into a byte array and copies
'it to the structure variable specified in oStruct.
Public Shared Sub ExtractStructure(Of T As Structure) _
    (oFS As FileStream, ByRef oStruct As T)

    Dim iStructLen As Integer
    Dim abStreamData As Byte()
    Dim hStreamData As GCHandle
    Dim iStreamData As IntPtr

    'From the file's current position, read the number of bytes required to
    'fill the structure, into the byte array abStreamData.
    iStructLen = Marshal.SizeOf(oStruct)
    ReDim abStreamData(0 To iStructLen - 1)
    oFS.Read(abStreamData, 0, iStructLen)

    'Obtain a handle to the byte array, pinning it so that the garbage
    'collector does not move the object. This allows the address of the 
    'pinned structure to be taken. Requires the use of Free when done.
    hStreamData = GCHandle.Alloc(abStreamData, GCHandleType.Pinned)
    Try
        'Obtain the byte array's address.
        iStreamData = hStreamData.AddrOfPinnedObject()

        'Copy the byte array into the record.
        oStruct = Marshal.PtrToStructure(Of T)(iStreamData)
    Finally
        hStreamData.Free()
    End Try
End Sub

Работает.

Другие вопросы по теме