前段时间,在将一个6.0的程序升级到.NET的过程中就遇到了很多麻烦。这个结构体中包含了定长字符串、数组以及其它一些结构,需要将该结构体作为头文件直接保存到一个二进制文件中,同时还要能从文件中读出该结构体。在6.0中,该结构体如下:
- Type OptionType
- ID As String * 11
- SamplingMode As Integer
- StartMode As Byte
- Freq As Integer
- Time As Single
- PreTime As Long
- Samples As Long
- TestName As String * 24
- Chan(1 To ADCOCHANS) As ChanType
- TimeUnit As Integer
- VectorUSpan As Single
- VectorISpan As Single
- Relat As String * 20
- Addr As String * 20
- TestDate As Date
- TestMemo As String * 320
- End Type
其中,ChanType是另一个结构体,如下:
- Type ChanType
- ChanNo As String * 10
- ChanName As String * 10
- Unit As String * 6
- Offon As Integer
- Ratio As Single
- Factor As Single
- Dcac As Integer
- Lower As Single
- Upper As Single
- ZeroVal As Single
- Color As Long
- Shift As Integer
- End Type
如果只是简单的将其改写到.NET版本并不难,但若想在.NET中读出以前6.0下保存的二进制文件时,就需要好好的设计一番了。具体涉及到以下几个问题:
1、.NET中已不再直接支持6.0中的定长字符串。不定长的字符串在存储时,需要同时存储一个长度值。
2、.NET中的数据类型有了很大变化,比如Integer是从16位变为32位,DateTime以整数形式保存而不再是以前的Double型
。。。
在此不一一列举,有兴趣的可以参看微软出的相关文档。下面是改写后的结构体:
- <StructLayoutAttribute(LayoutKind.Sequential, CharSet:=CharSet.Ansi, Pack:=1)> _
- Public Structure StructOption
- <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=11)> _
- Public ID As String
- Public SamplingMode As Short
- Public StartMode As Byte
- Public Freq As Short
- Public Time As Single
- Public PreTime As Integer
- Public Samples As Integer
- <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=24)> _
- Public TestName As String
- <MarshalAs(UnmanagedType.ByValArray, SizeConst:=44)> _
- Public Chan() As StructChan
- Public TimeUnit As Short
- Public VectorUSpan As Single
- Public VectorISpan As Single
- <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=20)> _
- Public Relat As String
- <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=20)> _
- Public Addr As String
- Public TestDate As Date
- <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=320)> _
- Public TestMemo As String
- Public Shared Function CreateInstance() As StructOption
- Dim ret As New StructOption
- Dim newChan As New StructChan
- ReDim ret.Chan(ADCOCHANS - 1)
- For i As Integer = 0 To ADCOCHANS - 1
- ret.Chan(i) = newChan
- Next
- Return ret
- End Function
- End Structure
- <StructLayoutAttribute(LayoutKind.Sequential, CharSet:=CharSet.Ansi, Pack:=1)> _
- Public Structure StructChan
- <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=10)> _
- Public ChanNo As String
- <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=10)> _
- Public ChanName As String
- <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=6)> _
- Public Unit As String
- Public Offon As Short
- Public Ratio As Single
- Public factor As Single
- Public Dcac As Short
- Public Lower As Single
- Public Upper As Single
- Public ZeroVal As Single
- Public Color As Integer
- Public Shift As Short
- End Structure
或许你已经注意到了,在改写的结构体中使用了一些属性,必须使用这些属性,才能构造出与VB6一模一样的结构体(至少说保存到文件中时时一样的结构)。
.NET中将结构体保存至文件的方法有两种:
一种是为结构体添加<Serializable()>属性,然后使用System.Runtime.Serialization.Formatters.Binary.BinaryFormatter中的Serialize方法将其串行化为文件流,并可用Deserialize方法将其还原为结构体;
另一种方法便是本文要讨论的,即结构体与字节数组之间的转化,因为在VB6中保存结构体时是按字节存储的,要做到同样效果需要将结构体其转化为字节数组。
再回到改写后的结构体中,为了定义属性,首先需要导入命名空间
Imports System.Runtime.InteropServices
接下来的结构体属性必不可少:
<StructLayoutAttribute(LayoutKind.Sequential, CharSet:=CharSet.Ansi, Pack:=1)>
对于定长字符串,需要添加如下属性:
<MarshalAs(UnmanagedType.ByValTStr, SizeConst:=11)>
注意,这里,字符串只能保存10个字符
对于定长数组,同样需要添加属性:
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=44)>
另外,在使用前,需要对定长数组的长度进行ReDim,且长度必须与SizeConst相等。事实上,如果你定义了更长的长度,它会只截取SizeConst长度进行保存
接下来便是结构体与字节数组之间的转化,以及从文件读取结构体的源代码(注:部分源码根据网络上的C#版本改写)
- Private Sub StructToBytes(ByVal objStruct As Object, ByRef bytes() As Byte)
- '得到结构体长度
- Dim size As Integer = Marshal.SizeOf(objStruct)
- '重新定义数组大小
- ReDim bytes(size)
- '分配结构体大小的内存空间
- Dim ptrStruct As IntPtr = Marshal.AllocHGlobal(size)
- '将结构体放入内存空间
- Marshal.StructureToPtr(objStruct, ptrStruct, False)
- '将内存拷贝到字节数组
- Marshal.Copy(ptrStruct, bytes, 0, size)
- '释放内存空间
- Marshal.FreeHGlobal(ptrStruct)
- End Sub
- ''' <summary>
- ''' 字节数组bytes()转化为结构体
- Private Function BytesToStruct(ByRef bytes() As Byte, ByVal mytype As Type) As Object
- '获取结构体大小
- Dim size As Integer = Marshal.SizeOf(mytype)
- 'bytes数组长度小于结构体大小
- If (size > bytes.Length) Then
- '返回空
- Return Nothing
- End If
- '分配结构体大小的内存空间
- Dim ptrStruct As IntPtr = Marshal.AllocHGlobal(size)
- '将数组拷贝到内存
- Marshal.Copy(bytes, 0, ptrStruct, size)
- '将内存转换为目标结构体
- Dim obj As Object = Marshal.PtrToStructure(ptrStruct, mytype)
- '释放内存
- Marshal.FreeHGlobal(ptrStruct)
- Return obj
- End Function
- ''' <summary>
- ''' 从文件中读取结构体
- ''' </summary>
- ''' <returns>结构体</returns>
- ''' <remarks></remarks>
- Private Function FileToStruct(ByVal strPath As String, ByVal mytype As Type) As Object
- '获得结构体大小
- Dim size As Integer = Marshal.SizeOf(mytype)
- '打开文件流
- Dim fs As New FileStream(strPath, FileMode.Open)
- '读取size个字节
- Dim bytes(size) As Byte
- fs.Read(bytes, 0, size)
- '将读取的字节转化为结构体
- Dim obj As Object = BytesToStruct(bytes, mytype)
- fs.Close()
- Return obj
- End Function