(This is in some sense a follow-up question to Extracting structs in the middle of a file into my structures other than accessing the file byte for byte and compose their value.)
I am having a file containing a structure record in its stream:
[Start]...[StructureRecord]...[End]
The contained structure fits into this layout, of which exists a variable:
Public Structure HeaderStruct
Public MajorVersion As Short
Public MinorVersion As Short
Public Count As Integer
End Structure
private grHeaderStruct As HeaderStruct
It is into this variable I want a copy of the structure residing in the file.
Required namespaces:
'File, FileMode, FileAccess, FileShare:
Imports System.IO
'GCHandle, GCHandleType:
Imports System.Runtime.InteropServices
'SizeOf, Copy:
Imports System.Runtime.InteropServices.Marshal
My FileStream
is named oFS
.
Using oFS = File.Open(sFileName, FileMode.Open, FileAccess.Read)
...
Assume, that oFS
is positioned at the start of [StructureRecord]
now.
So there are 8 bytes (HeaderStruct.Length
) to read from the file, and copy these into a record instance of this structure. To do so, I wrap the logic to read from file the required number of bytes and transfer them to my structure record into a generic method ExtractStructure
. The destination is instantiated just before the call to the routine.
grHeaderStruct = New HeaderStruct
ExtractStructure(Of HeaderStruct)(oFS, grHeaderStruct)
...
End Using
(Before suggesting techniques to read just these 8 bytes outside of a dedicated method, you should probably know, that the whole file consists of structures, which depend on each other. The Count
field says, that 3 child structures are to follow, but these contain Count
fields themselves, etc. I think a routine to get them is not a too bad idea.)
However, this is the routine which causes my current head-ache:
'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
The instruction
oFS.Read(abStreamData, 0, iStructLen)
does read the correct number of bytes with the correct values, as per immediate window:
?abstreamdata
{Length=8}
(0): 1
(1): 0
(2): 2
(3): 0
(4): 3
(5): 0
(6): 0
(7): 0
I can not use Marshal.StructureToPtr
here, because, well, a bytes array is not a structure. However, the Marshal
class has also a Copy
method.
Only that I obviously miss a point here, because
Marshal.Copy(abStreamData, 0, oStructAddr, iStructLen)
does not do the intended copy (but it also does not throw an exception):
?ostruct
Count: 0
MajorVersion: 0
MinorVersion: 0
What do I fail to understand, why this copy is not working?
The MS documentation does not tell too much for 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.
Looks as if I have set up right everything, so is it possible that Marshal.Copy
does not copy to the structure, but to some other place?
oStructAddr
surely does look like an address (&H02EB7B64), but where is that?
Edit
Between the instantiation of the parameter receiving the result and the call to the routine
grHeaderStruct = New HeaderStruct
ExtractStructure(Of HeaderStruct)(oFS, grHeaderStruct)
I filled the instantiated record with some test data to see whether it is actually passed correctly:
#Region "Test"
With grMultifileDesc
.MajorVersion = 7
.MinorVersion = 8
.Count = 9
End With
#End Region
In the procedure, I check the value of the record before and after Marshal.Copy
in the immediate window. Both times I obtain:
?ostruct
{MyProject.MyClass.HeaderStruct}
Count: 9
MajorVersion: 7
MinorVersion: 8
(Re-arrangement of the record's fields is the same in the caller as in the callee, which of course is an issue in itself, as the data is read from a file and would be wrongly copied into the structure. However, this is not the topic of the question.)
Conclusion: Data obtained, but no Marshal.Copy
made already in the callee. So it is not just a "returns wrong data" problem.
Edit 2
It turns out,that Marshal.Copy
does not copy data, as stated in the documentation, but a pointer to the data.
Marshal.ReadByte(oStructAddr)
does return the byte array's first byte, Marshal.ReadByte(oStructAddr + 1)
its second byte, etc.
But how do I return this data in the Out
argument?