Marshal.Copy not copying from bytes array into structure starting at the address provided in an IntPtr
(从某种意义上讲,这是将文件中间的结构提取到我的结构中的后续问题,而不是逐字节访问文件字节并组成其值。)
我在其流中有一个包含结构记录的文件:
1 | [Start]...[StructureRecord]...[End] |
所包含的结构适合此布局,其中存在一个变量:
1 2 3 4 5 6 7 | Public Structure HeaderStruct Public MajorVersion As Short Public MinorVersion As Short Public Count As Integer End Structure private grHeaderStruct As HeaderStruct |
在此变量中,我需要文件中驻留的结构的副本。
所需的名称空间:
1 2 3 4 5 6 7 8 | 'File, FileMode, FileAccess, FileShare: Imports System.IO 'GCHandle, GCHandleType: Imports System.Runtime.InteropServices 'SizeOf, Copy: Imports System.Runtime.InteropServices.Marshal |
我的
1 2 | Using oFS = File.Open(sFileName, FileMode.Open, FileAccess.Read) ... |
假设
因此,要从文件读取8个字节(
1 2 3 4 | grHeaderStruct = New HeaderStruct ExtractStructure(Of HeaderStruct)(oFS, grHeaderStruct) ... End Using |
(在建议使用一种专用方法之外的方法来仅读取这8个字节的技术之前,您可能应该知道,整个文件由相互依赖的结构组成。
但是,这是导致我目前头痛的例程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | '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 |
说明
1 | oFS.Read(abStreamData, 0, iStructLen) |
按照即时窗口读取具有正确值的正确字节数:
1 2 3 4 5 6 7 8 9 10 | ?abstreamdata {Length=8} (0): 1 (1): 0 (2): 2 (3): 0 (4): 3 (5): 0 (6): 0 (7): 0 |
在这里我不能使用
只有我显然在这里遗漏了一点,因为
1 | Marshal.Copy(abStreamData, 0, oStructAddr, iStructLen) |
不执行预期的复制(但也不会引发异常):
1 2 3 4 | ?ostruct Count: 0 MajorVersion: 0 MinorVersion: 0 |
我不明白什么,为什么此副本不起作用?
对于Marshal而言,MS文档并不能说明太多.Copy:https://msdn.microsoft.com/zh-cn/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.
看起来好像我已经正确设置了所有内容,所以
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | '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 |
Works。
我不能肯定地说为什么复制到固定地址的更新字节未反映在原始结构中,但是我怀疑interop-marshaller进行了复制。请参阅:复制和固定。
我确实知道,如果您固定一个字节数组,则使用Marshal.Copy进行的更改将传播回托管数组。话虽如此,这是一个示例,您应该可以根据需要使用它。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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() |
Edit:基于评论,似乎OP认为上面显示的技术不能实现为用于读取/写入流的通用方法。因此,这是一个复制/粘贴示例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | 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 |
Saved structure: MajorVersion = 4, MinorVersion = 2, Count = 5
Retrieved structure: MajorVersion = 4, MinorVersion = 2, Count = 5