Marshal.Copy不会从字节数组复制到从IntPtr

时间:2018-04-20 05:16:21

标签: .net vb.net copy marshalling 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)

根据即时窗口读取正确的字节数,具有正确的值:

?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   源数组中从零开始的索引,应该开始复制。

     

destination - 类型:System.IntPtr   要复制到的内存指针。

     

length - 类型: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

(重新安排记录' s字段在调用者中与在被调用者中相同,这当然是一个问题,因为数据是从文件中读取的,并且会被错误地复制到文件中但是,这不是问题的主题。)

结论:获得了数据,但没有Marshal.Copy已将置于被叫方。所以它不只是一个"返回错误的数据"问题。

修改2

事实证明,Marshal.Copy不会复制数据,如文档中所述,而是指向数据的指针。

Marshal.ReadByte(oStructAddr)确实返回字节数组的第一个字节,Marshal.ReadByte(oStructAddr + 1)第二个字节等。

但是如何在Out参数中返回此数据?

2 个答案:

答案 0 :(得分:0)

我不能肯定地说为什么复制到固定地址的更新字节没有反映在原始结构中,但我怀疑interop-marshaller会复制。见: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似乎认为上面显示的技术无法实现为读取/写入流的通用方法。所以这是一个复制/粘贴示例。

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的输出:

  

保存的结构:MajorVersion = 4,MinorVersion = 2,Count = 5

     

检索结构:MajorVersion = 4,MinorVersion = 2,Count = 5

答案 1 :(得分:0)

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

作品。