关于.net:Marshal.Copy不能从字节数组复制到从IntPtr中提供的地址开始的结构中

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

我的FileStream被命名为oFS

1
2
Using oFS = File.Open(sFileName, FileMode.Open, FileAccess.Read)
    ...

假设oFS现在位于[StructureRecord]的开头。

因此,要从文件读取8个字节(HeaderStruct.Length),并将其复制到此结构的记录实例中。为此,我package了逻辑以从文件中读取所需的字节数,并将它们传输到结构记录中的通用方法ExtractStructure中。在调用例程之前实例化了目的地。

1
2
3
4
    grHeaderStruct = New HeaderStruct
    ExtractStructure(Of HeaderStruct)(oFS, grHeaderStruct)
    ...
End Using

(在建议使用一种专用方法之外的方法来仅读取这8个字节的技术之前,您可能应该知道,整个文件由相互依赖的结构组成。Count字段表示,有3个子结构是可以遵循,但是它们本身包含Count字段,等等。我认为获取它们的例程并不是一个坏主意。)

但是,这是导致我目前头痛的例程:

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

在这里我不能使用Marshal.StructureToPtr,因为字节数组不是结构。但是,Marshal类也具有Copy方法。

只有我显然在这里遗漏了一点,因为

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.

看起来好像我已经正确设置了所有内容,所以Marshal.Copy可能不会复制到结构中,而是复制到其他地方吗?

oStructAddr确实看起来像一个地址(


ExtractStructure方法需要这样重写:

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

Example的输出:

Saved structure: MajorVersion = 4, MinorVersion = 2, Count = 5

Retrieved structure: MajorVersion = 4, MinorVersion = 2, Count = 5