Marshal.Copy 不从字节数组复制到从 IntPtr 中提供的地址开始的结构
Marshal.Copy not copying from bytes array into structure starting at the address provided in an IntPtr
(从某种意义上说,这是 Extracting structs in the middle of a file into my structures other than accessing the file byte for byte and compose their value 的后续问题。)
我有一个在其流中包含结构记录的文件:
[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)
确实按照立即数 window:
读取具有正确值的正确字节数
?abstreamdata
{Length=8}
(0): 1
(1): 0
(2): 2
(3): 0
(4): 3
(5): 0
(6): 0
(7): 0
我不能在这里使用 Marshal.StructureToPtr
,因为字节数组不是结构。但是,Marshal
class 也有一个 Copy
方法。
只是我在这里显然漏掉了一点,因为
Marshal.Copy(abStreamData, 0, oStructAddr, iStructLen)
不执行预期的复制(但它也不会抛出异常):
?ostruct
Count: 0
MajorVersion: 0
MinorVersion: 0
我怎么没看懂,为什么这个副本打不开?
MS 文档没有为 Marshal.Copy 提供太多信息: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
在程序中,我在立即数window中检查了Marshal.Copy
前后记录的值。两次我都获得:
?ostruct
{MyProject.MyClass.HeaderStruct}
Count: 9
MajorVersion: 7
MinorVersion: 8
(记录字段的重新排列在调用者和被调用者中是相同的,这本身就是一个问题,因为数据是从文件中读取的,并且会被错误地复制到结构中。不过,这不是问题的主题。)
结论:已获取数据,但Marshal.Copy
已在被调用方中制作。所以这不仅仅是一个 "returns wrong data" 问题。
编辑 2
事实证明,Marshal.Copy
并不复制数据,如文档中所述,而是指向数据的指针。
Marshal.ReadByte(oStructAddr)
return 字节数组的第一个字节,Marshal.ReadByte(oStructAddr + 1)
第二个字节,等等
但是我如何 return Out
参数中的这个数据?
我不能确定为什么复制到固定地址的更新字节没有反映在原始结构中,但我怀疑互操作编组器进行了复制。参见:Copying and Pinning.
我确实知道,如果您固定一个字节数组,使用 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 似乎认为上面显示的技术不能作为 reading/writing 到流的通用方法来实现。所以这是一个 copy/paste 示例。
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
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
有效。
(从某种意义上说,这是 Extracting structs in the middle of a file into my structures other than accessing the file byte for byte and compose their value 的后续问题。)
我有一个在其流中包含结构记录的文件:
[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)
确实按照立即数 window:
读取具有正确值的正确字节数?abstreamdata
{Length=8}
(0): 1
(1): 0
(2): 2
(3): 0
(4): 3
(5): 0
(6): 0
(7): 0
我不能在这里使用 Marshal.StructureToPtr
,因为字节数组不是结构。但是,Marshal
class 也有一个 Copy
方法。
只是我在这里显然漏掉了一点,因为
Marshal.Copy(abStreamData, 0, oStructAddr, iStructLen)
不执行预期的复制(但它也不会抛出异常):
?ostruct
Count: 0
MajorVersion: 0
MinorVersion: 0
我怎么没看懂,为什么这个副本打不开?
MS 文档没有为 Marshal.Copy 提供太多信息: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
在程序中,我在立即数window中检查了Marshal.Copy
前后记录的值。两次我都获得:
?ostruct
{MyProject.MyClass.HeaderStruct}
Count: 9
MajorVersion: 7
MinorVersion: 8
(记录字段的重新排列在调用者和被调用者中是相同的,这本身就是一个问题,因为数据是从文件中读取的,并且会被错误地复制到结构中。不过,这不是问题的主题。)
结论:已获取数据,但Marshal.Copy
已在被调用方中制作。所以这不仅仅是一个 "returns wrong data" 问题。
编辑 2
事实证明,Marshal.Copy
并不复制数据,如文档中所述,而是指向数据的指针。
Marshal.ReadByte(oStructAddr)
return 字节数组的第一个字节,Marshal.ReadByte(oStructAddr + 1)
第二个字节,等等
但是我如何 return Out
参数中的这个数据?
我不能确定为什么复制到固定地址的更新字节没有反映在原始结构中,但我怀疑互操作编组器进行了复制。参见:Copying and Pinning.
我确实知道,如果您固定一个字节数组,使用 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 似乎认为上面显示的技术不能作为 reading/writing 到流的通用方法来实现。所以这是一个 copy/paste 示例。
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
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
有效。