(В некотором смысле это вопрос, ответ на который задают Извлечение структур в середине файла в мои структуры, кроме доступа к файлу байта за байтом и составления их значения.)
У меня есть файл, содержащий запись структуры в своем потоке:
[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?
@NicoSchertler, спасибо. Это не удается уже при попытке получить дескриптор для oStruct с помощью GCHandle.Alloc (исключение «Объект не содержит примитивных данных.»). Он запускается при замене GCHandleType на Normal (который больше не закрепляет!), Но тогда я не смогу использовать oGCHandle.AddrOfPinnedObject. Использование oStructAddr = CType(oGCHandle, IntPtr) вместо этого работает, но затем SizeOf жалуется, что объект не может быть маршалирован как неуправляемая структура.
@NicoSchertler: Закрепление структуры должно работать, я сам пару раз делал это без проблем. - Herb: Основываясь на ошибке, которую мне удалось найти: stackoverflow.com/a/15549992 - По-видимому, это выбрасывается, когда у вас есть непримитивная переменная в вашей структуре (то есть переменная, указывающая на класс).
@VisualVincent, структура такая, как показано в OP: она содержит два шорта и одно целое число, в фактическом случае заполненное цифрами 1, 2, 3 (представленное в файле как 0x01 00 02 00 03 00 00 00). Никаких ссылок на объекты (кроме всего прочего). Я обновил ОП, добавив некоторые дополнительные сведения. Очевидно, что Copy копирует не данные согласно документации, а указатель. (Я могу получить копию с помощью ReadByte из этого места, но понятия не имею, как заполнить аргумент out.)
Что вы имеете в виду под «Копировать не копирует данные согласно документации, но указатель»? Marshal.Copy() копирует указанный диапазон байтов в указанный адрес памяти назначения. Однако вам необходимо создать GCHandle с GCHandleType.Pinned и использовать AddrOfPinnedObject, чтобы это работало.
Если эта часть работает для вашей структуры, пробовали ли вы изменить метод ExtractStructure() на функцию и заставить ее возвращать результирующую структуру вместо использования аргумента out?





Я не могу точно сказать, почему обновленные байты, скопированные на закрепленный адрес, не отражаются в исходной структуре, но я подозреваю, что маршаллер взаимодействия делает копию. см .: Копирование и закрепление.
Я знаю, что если вы закрепите массив байтов, эти изменения, сделанные с помощью 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, этот ответ демонстрирует технику, которая ответила на ваш вопрос. Тот факт, что в примере использовался жестко закодированный тип, не позволяет записать его как универсальный. См. Обновление для общего примера.
Метод 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
Работает.
Очень хорошо написанный вопрос. Не могли бы вы проверить, работает ли это, если вы сделаете
HeaderStructClassвместоStructure?. Структуры могут находиться в стеке, и я не уверен, можно ли иметьGCHandleдля чего-то в стеке. (Так же временная проверка, определенно не постоянное решение).