VB.NET中将结构体保存到文件的方法(转)

时间:2022-10-05 13:59:35
VB.NET中的结构体在使用的时候像是一个“小型的类”,其内部结构与VB6.0相比有许多不同。当需要把一个VB6.0中的结构体升级到对应的.NET版本时,在细节上会有很多区别。

前段时间,在将一个6.0的程序升级到.NET的过程中就遇到了很多麻烦。这个结构体中包含了定长字符串、数组以及其它一些结构,需要将该结构体作为头文件直接保存到一个二进制文件中,同时还要能从文件中读出该结构体。在6.0中,该结构体如下:

  1. Type OptionType  
  2.   ID As String * 11  
  3.   SamplingMode As Integer  
  4.   StartMode As Byte          
  5.   Freq As Integer              
  6.   Time As Single         
  7.   PreTime As Long      
  8.   Samples As Long     
  9.   TestName As String * 24  
  10.   Chan(1 To ADCOCHANS) As ChanType   
  11.   TimeUnit As Integer  
  12.   VectorUSpan As Single  
  13.   VectorISpan As Single  
  14.   Relat As String * 20  
  15.   Addr As String * 20  
  16.   TestDate As Date  
  17.   TestMemo As String * 320  
  18. End Type  
 

其中,ChanType是另一个结构体,如下:

  1. Type ChanType  
  2.   ChanNo As String * 10  
  3.   ChanName As String * 10  
  4.   Unit As String * 6  
  5.   Offon As Integer  
  6.   Ratio As Single   
  7.   Factor As Single   
  8.   Dcac As Integer  
  9.   Lower As Single  
  10.   Upper As Single  
  11.   ZeroVal As Single  
  12.   Color As Long  
  13.   Shift As Integer  
  14. End Type  

 

如果只是简单的将其改写到.NET版本并不难,但若想在.NET中读出以前6.0下保存的二进制文件时,就需要好好的设计一番了。具体涉及到以下几个问题:

1、.NET中已不再直接支持6.0中的定长字符串。不定长的字符串在存储时,需要同时存储一个长度值。

2、.NET中的数据类型有了很大变化,比如Integer是从16位变为32位,DateTime以整数形式保存而不再是以前的Double型

。。。

在此不一一列举,有兴趣的可以参看微软出的相关文档。下面是改写后的结构体:

  1. <StructLayoutAttribute(LayoutKind.Sequential, CharSet:=CharSet.Ansi, Pack:=1)> _  
  2. Public Structure StructOption  
  3.     <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=11)> _  
  4.     Public ID As String  
  5.     Public SamplingMode As Short  
  6.     Public StartMode As Byte  
  7.     Public Freq As Short  
  8.     Public Time As Single  
  9.     Public PreTime As Integer  
  10.     Public Samples As Integer  
  11.     <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=24)> _  
  12.     Public TestName As String  
  13.     <MarshalAs(UnmanagedType.ByValArray, SizeConst:=44)> _  
  14.     Public Chan() As StructChan  
  15.     Public TimeUnit As Short  
  16.     Public VectorUSpan As Single  
  17.     Public VectorISpan As Single  
  18.     <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=20)> _  
  19.     Public Relat As String  
  20.     <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=20)> _  
  21.     Public Addr As String  
  22.     Public TestDate As Date  
  23.     <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=320)> _  
  24.     Public TestMemo As String  
  25.   
  26.     Public Shared Function CreateInstance() As StructOption  
  27.         Dim ret As New StructOption  
  28.         Dim newChan As New StructChan  
  29.         ReDim ret.Chan(ADCOCHANS - 1)  
  30.         For i As Integer = 0 To ADCOCHANS - 1  
  31.             ret.Chan(i) = newChan  
  32.         Next  
  33.         Return ret  
  34.     End Function  
  35. End Structure  

 

 

  1. <StructLayoutAttribute(LayoutKind.Sequential, CharSet:=CharSet.Ansi, Pack:=1)> _  
  2. Public Structure StructChan  
  3.     <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=10)> _  
  4.     Public ChanNo As String  
  5.     <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=10)> _  
  6.     Public ChanName As String  
  7.     <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=6)> _  
  8.     Public Unit As String  
  9.     Public Offon As Short  
  10.     Public Ratio As Single  
  11.     Public factor As Single  
  12.     Public Dcac As Short  
  13.     Public Lower As Single  
  14.     Public Upper As Single  
  15.     Public ZeroVal As Single  
  16.     Public Color As Integer  
  17.     Public Shift As Short  
  18. 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#版本改写)

  1. Private Sub StructToBytes(ByVal objStruct As ObjectByRef bytes() As Byte)  
  2.     '得到结构体长度  
  3.     Dim size As Integer = Marshal.SizeOf(objStruct)  
  4.     '重新定义数组大小  
  5.     ReDim bytes(size)  
  6.     '分配结构体大小的内存空间  
  7.     Dim ptrStruct As IntPtr = Marshal.AllocHGlobal(size)  
  8.     '将结构体放入内存空间  
  9.     Marshal.StructureToPtr(objStruct, ptrStruct, False)  
  10.     '将内存拷贝到字节数组  
  11.     Marshal.Copy(ptrStruct, bytes, 0, size)  
  12.     '释放内存空间  
  13.     Marshal.FreeHGlobal(ptrStruct)  
  14. End Sub  
  15. ''' <summary>  
  16. ''' 字节数组bytes()转化为结构体  
  17.   
  18. Private Function BytesToStruct(ByRef bytes() As ByteByVal mytype As Type) As Object  
  19.     '获取结构体大小  
  20.     Dim size As Integer = Marshal.SizeOf(mytype)  
  21.     'bytes数组长度小于结构体大小  
  22.     If (size > bytes.Length) Then  
  23.         '返回空  
  24.         Return Nothing  
  25.     End If  
  26.     '分配结构体大小的内存空间  
  27.     Dim ptrStruct As IntPtr = Marshal.AllocHGlobal(size)  
  28.     '将数组拷贝到内存  
  29.     Marshal.Copy(bytes, 0, ptrStruct, size)  
  30.     '将内存转换为目标结构体  
  31.     Dim obj As Object = Marshal.PtrToStructure(ptrStruct, mytype)  
  32.     '释放内存  
  33.     Marshal.FreeHGlobal(ptrStruct)  
  34.     Return obj  
  35. End Function  
  36.   
  37. ''' <summary>  
  38. ''' 从文件中读取结构体  
  39. ''' </summary>  
  40. ''' <returns>结构体</returns>  
  41. ''' <remarks></remarks>  
  42. Private Function FileToStruct(ByVal strPath As StringByVal mytype As Type) As Object  
  43.     '获得结构体大小  
  44.     Dim size As Integer = Marshal.SizeOf(mytype)  
  45.     '打开文件流  
  46.     Dim fs As New FileStream(strPath, FileMode.Open)  
  47.     '读取size个字节  
  48.     Dim bytes(size) As Byte  
  49.     fs.Read(bytes, 0, size)  
  50.     '将读取的字节转化为结构体  
  51.     Dim obj As Object = BytesToStruct(bytes, mytype)  
  52.    fs.Close()  
  53.   
  54.     Return obj  
  55. End Function