【发布时间】:2018-04-20 05:16:21
【问题描述】:
(这在某种意义上是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)
确实根据即时窗口读取具有正确值的正确字节数:
?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 文档并没有为 Marshal.Copy 提供太多信息:https://msdn.microsoft.com/en-us/library/ms146625(v=vs.110).aspx:
source - 类型:System.Byte() 要从中复制的一维数组。
startIndex - 类型:System.Int32 应开始复制的源数组中从零开始的索引。
目标 - 类型:System.IntPtr 要复制到的内存指针。
长度 - 类型:System.Int32 要复制的数组元素的数量。
看起来我已经设置了正确的一切,那么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 参数中返回这些数据?
【问题讨论】:
-
写得很好的问题。当您将
HeaderStruct设为Class而不是Structure时,您能否检查它是否有效?结构可能驻留在堆栈上,我不确定堆栈上是否有可能有GCHandle。 (只是临时检查,绝对不是永久解决方案)。 -
@NicoSchertler,谢谢。这在尝试使用
GCHandle.Alloc获取oStruct的句柄时已经失败(异常'对象不包含原始数据')。它在将GCHandleType更改为Normal(不再固定它!)时执行,但是我将无法使用oGCHandle.AddrOfPinnedObject。使用oStructAddr = CType(oGCHandle, IntPtr)代替工作,但随后SizeOf抱怨,该对象不能被封送为非托管结构。 -
@NicoSchertler :固定结构应该可以工作,我自己已经做了几次,没有问题。 -- Herb:基于错误我设法找到了这个:stackoverflow.com/a/15549992 - 显然,当你的结构中有一个非原始变量(即指向一个类的变量)时,它会被抛出。
-
@VisualVincent,结构如OP所示:它包含两个short和一个整数,实际填充为1、2、3(在文件中表示为
0x01 00 02 00 03 00 00 00)。根本没有对对象的引用(除了一切)。我用一些进一步的发现更新了 OP。显然,Copy 不是按照文档复制数据,而是一个指针。 (我可以使用ReadByte从该位置检索副本,但不知道如何填充 out 参数。) -
"Copy 不是按照文档复制数据,而是指针"是什么意思?
Marshal.Copy()将指定范围的字节复制到指定的目标内存地址。但是,您需要使用GCHandleType.Pinned创建GCHandle并使用AddrOfPinnedObject才能使其正常工作。
标签: .net vb.net copy marshalling intptr