前段时间,在将一个6.0的程序升级到.NET的过程中就遇到了很多麻烦。这个结构体中包含了定长字符串、数组以及其它一些结构,需要将该结构体作为头文件直接保存到一个二进制文件中,同时还要能从文件中读出该结构体。在6.0中,该结构体如下:
- TypeOptionType
- IDAsString*11
- SamplingModeAsInteger
- StartModeAsByte
- FreqAsInteger
- TimeAsSingle
- PreTimeAsLong
- SamplesAsLong
- TestNameAsString*24
- Chan(1ToADCOCHANS)AsChanType
- TimeUnitAsInteger
- VectorUSpanAsSingle
- VectorISpanAsSingle
- RelatAsString*20
- AddrAsString*20
- TestDateAsDate
- TestMemoAsString*320
- EndType
其中,ChanType是另一个结构体,如下:
TypeChanType
ChanNoAsString*10
ChanNameAsString*10
UnitAsString*6
OffonAsInteger
RatioAsSingle
FactorAsSingle
DcacAsInteger
LowerAsSingle
UpperAsSingle
ZeroValAsSingle
ColorAsLong
ShiftAsInteger
EndType
如果只是简单的将其改写到.NET版本并不难,但若想在.NET中读出以前6.0下保存的二进制文件时,就需要好好的设计一番了。具体涉及到以下几个问题:
1、.NET中已不再直接支持6.0中的定长字符串。不定长的字符串在存储时,需要同时存储一个长度值。
2、.NET中的数据类型有了很大变化,比如Integer是从16位变为32位,DateTime以整数形式保存而不再是以前的Double型
。。。
在此不一一列举,有兴趣的可以参看微软出的相关文档。下面是改写后的结构体:
<StructLayoutAttribute(LayoutKind.Sequential,CharSet:=CharSet.Ansi,Pack:=1)>_
PublicStructureStructOption
<MarshalAs(UnmanagedType.ByValTStr,SizeConst:=11)>_
PublicIDAsString
PublicSamplingModeShort
PublicStartModeByte
PublicFreqPublicTimeSingle
PublicPreTimeInteger
PublicSamplesInteger
<MarshalAs(UnmanagedType.ByValTStr,SizeConst:=24)>_
PublicTestNameString
<MarshalAs(UnmanagedType.ByValArray,SizeConst:=44)>_
PublicChan()AsStructChan
PublicTimeUnitPublicVectorUSpanPublicVectorISpanSingle
<MarshalAs(UnmanagedType.ByValTStr,SizeConst:=20)>_
PublicRelatString
PublicAddrPublicTestDateDate
<MarshalAs(UnmanagedType.ByValTStr,SizeConst:=320)>_
PublicTestMemoString
SharedFunctionCreateInstance()AsStructOption
DimretNewStructOption
DimnewChanNewStructChan
ReDimret.Chan(ADCOCHANS-1)
ForiInteger=0ToADCOCHANS-1
ret.Chan(i)=newChan
Next
Returnret
EndFunction
Structure
StructureStructChan
<MarshalAs(UnmanagedType.ByValTStr,SizeConst:=10)>_
PublicChanNoString
<MarshalAs(UnmanagedType.ByValTStr,153); background-color:inherit">PublicChanNamePublicUnitPublicOffonPublicRatioPublicfactorPublicDcacShort
PublicLowerPublicUpperPublicZeroValPublicColorInteger
PublicShiftStructure
或许你已经注意到了,在改写的结构体中使用了一些属性,必须使用这些属性,才能构造出与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#版本改写)
PrivateSubStructToBytes(ByValobjStructObject,ByRefbytes()Byte)
'得到结构体长度
DimsizeInteger=Marshal.SizeOf(objStruct)
'重新定义数组大小
ReDimbytes(size)
'分配结构体大小的内存空间
DimptrStructAsIntPtr=Marshal.AllocHGlobal(size)
'将结构体放入内存空间
Marshal.StructureToPtr(objStruct,ptrStruct,False)
'将内存拷贝到字节数组
Marshal.Copy(ptrStruct,bytes,size)
'释放内存空间
Marshal.FreeHGlobal(ptrStruct)
Sub
'''<summary>
'''字节数组bytes()转化为结构体
FunctionBytesToStruct(Byte,153); background-color:inherit">ByValmytypeAsType)Object
'获取结构体大小
Integer=Marshal.SizeOf(mytype)
'bytes数组长度小于结构体大小
If(size>bytes.Length)Then
'返回空
ReturnNothing
If
'将数组拷贝到内存
Marshal.Copy(bytes,0); background-color:inherit">'将内存转换为目标结构体
DimobjObject=Marshal.PtrToStructure(ptrStruct,mytype)
'释放内存
Marshal.FreeHGlobal(ptrStruct)
Returnobj
Function
'''从文件中读取结构体
'''</summary>
'''<returns>结构体</returns>
'''<remarks></remarks>
FunctionFileToStruct(ByValstrPathString,0); background-color:inherit">'获得结构体大小
'打开文件流
DimfsNewFileStream(strPath,FileMode.Open)
'读取size个字节
Dimbytes(size)Byte
fs.Read(bytes,0); background-color:inherit">'将读取的字节转化为结构体
Object=BytesToStruct(bytes,248); margin:0px!important; padding:0px 3px 0px 10px!important"> fs.Close()
Function