C#使用struct直接转换下位机数据的示例代码
编写上位机与下位机通信的时候,涉及到协议的转换,比较多会使用到二进制。传统的方法,是将数据整体获取到byte数组中,然后逐字节对数据进行解析。这样操作工作量比较大,对于较长数据段更容易计算位置出错。
其实,对于下位机给出通讯的数据结构的情况下,可以直接使用C#的struct将数据直接转换。需要使用到Marshal。
数据结构
假定下位机(C语言编写)给到我们的数据结构是这个,传输方式为小端方式
typedefstruct{ unsignedlonginttime;//4个字节 floattmpr[3];//4*3个字节 floatforces[6];//4*6个字节 floatdistance[6];//4*6个字节 }dataItem_t;
方法1
首先需要定义一个struct:
[StructLayout(LayoutKind.Sequential,Size=64,Pack=1)] publicstructHardwareData { //[FieldOffset(0)] publicUInt32Time;//4个字节 [MarshalAs(UnmanagedType.ByValArray,SizeConst=3)] //[FieldOffset(4)] publicfloat[]Tmpr;//3*4个字节 //[FieldOffset(16)] [MarshalAs(UnmanagedType.ByValArray,SizeConst=6)] publicfloat[]Forces;//6*4个字节 //[FieldOffset(40)] [MarshalAs(UnmanagedType.ByValArray,SizeConst=6)] publicfloat[]Distance;//6*4个字节 }
然后使用以下代码进行转换
//codefromhttps://stackoverflow.com/questions/628843/byte-for-byte-serialization-of-a-struct-in-c-sharp/629120#629120 //////convertsbyte[]tostruct /// publicstaticTRawDeserialize(byte[]rawData,intposition) { intrawsize=Marshal.SizeOf(typeof(T)); if(rawsize>rawData.Length-position) thrownewArgumentException("Notenoughdatatofillstruct.Arraylengthfromposition:"+(rawData.Length-position)+",Structlength:"+rawsize); IntPtrbuffer=Marshal.AllocHGlobal(rawsize); Marshal.Copy(rawData,position,buffer,rawsize); Tretobj=(T)Marshal.PtrToStructure(buffer,typeof(T)); Marshal.FreeHGlobal(buffer); returnretobj; } /// ///convertsastructtobyte[] /// publicstaticbyte[]RawSerialize(objectanything) { intrawSize=Marshal.SizeOf(anything); IntPtrbuffer=Marshal.AllocHGlobal(rawSize); Marshal.StructureToPtr(anything,buffer,false); byte[]rawDatas=newbyte[rawSize]; Marshal.Copy(buffer,rawDatas,0,rawSize); Marshal.FreeHGlobal(buffer); returnrawDatas; }
注意这里我使用的方式为LayoutKind.Sequential,如果直接使用LayoutKind.Explicit并设置FieldOffset会弹出一个诡异的错误System.TypeLoadException:“Couldnotloadtype'ConsoleApp3.DataItem'fromassembly'ConsoleApp3,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null'becauseitcontainsanobjectfieldatoffset4thatisincorrectlyalignedoroverlappedbyanon-objectfield.”。
方法2
提示是对齐的错误,这个和编译的时候使用的32bit和64位是相关的,详细数据封送对齐的操作我不就详细说了,贴下代码。
//强制指定x86编译 [StructLayout(LayoutKind.Explicit,Size=64,Pack=1)] publicstructDataItem { [MarshalAs(UnmanagedType.U4)] [FieldOffset(0)] publicUInt32time;//4个字节 [MarshalAs(UnmanagedType.ByValArray,SizeConst=3,ArraySubType=UnmanagedType.R4)] [FieldOffset(4)] publicfloat[]tmpr;//3*4个字节 [FieldOffset(16)] [MarshalAs(UnmanagedType.ByValArray,SizeConst=6,ArraySubType=UnmanagedType.R4)] publicfloat[]forces;//6*4个字节 [FieldOffset(40)] [MarshalAs(UnmanagedType.ByValArray,SizeConst=6,ArraySubType=UnmanagedType.R4)] publicfloat[]distance;//6*4个字节 }
强制指定x64编译没有成功,因为数据对齐后和从下位机上来的数据长度是不符的。
方法3
微软不是很推荐使用LayoutKind.Explicit,如果非要用并且不想指定平台的话,可以使用指针来操作,当然,这个需要unsafe。
varitem=RawDeserialize(tail.ToArray(),0); unsafe { float*p=&item.forces; for(inti=0;i<6;i++) { Console.WriteLine(*p); p++; } } [StructLayout(LayoutKind.Explicit,Size=64,Pack=1)] publicstructDataItem { [FieldOffset(0)] publicUInt32time;//4个字节 [FieldOffset(4)] publicfloattmpr;//3*4个字节 [FieldOffset(16)] publicfloatforces;//6*4个字节 [FieldOffset(40)] publicfloatdistance;//6*4个字节 }
方法4
感觉写起来还是很麻烦,既然用上了unsafe,就干脆直接一点。
[StructLayout(LayoutKind.Sequential,Pack=1)] publicunsafestructDataItem { publicUInt32time;//4个字节 publicfixedfloattmpr[3];//3*4个字节 publicfixedfloatforces[6];//6*4个字节 publicfixedfloatdistance[6];//6*4个字节 }
这样,获得数组可以直接正常访问,不再需要unsafe了。
总结
数据解析作为上下位机通讯的常用操作,使用struct直接转换数据可以大大简化工作量。建议还是使用LayoutKind.Sequential来进行封送数据,有关于数据在托管与非托管中的转换,可以详细看看微软有关互操作的内容。
以上代码在.NET5.0下编译通过并能正常执行。
补充
注意上面的前提要求是字节序为小端字节序(一般计算机都是小端字节序),对于大端字节序发送过来的数据,需要进行字节序转换。我找到一处代码写的很好:
//CODEFROMhttps://stackoverflow.com/a/15020402 publicstaticclassFooTest { [StructLayout(LayoutKind.Sequential,Pack=1)] publicstructFoo2 { publicbyteb1; publicshorts; publicushortS; publicinti; publicuintI; publiclongl; publiculongL; publicfloatf; publicdoubled; [MarshalAs(UnmanagedType.ByValTStr,SizeConst=10)] publicstringMyString; } [StructLayout(LayoutKind.Sequential,Pack=1)] publicstructFoo { publicbyteb1; publicshorts; publicushortS; publicinti; publicuintI; publiclongl; publiculongL; publicfloatf; publicdoubled; [MarshalAs(UnmanagedType.ByValTStr,SizeConst=10)] publicstringMyString; publicFoo2foo2; } publicstaticvoidtest() { Foo2sample2=newFoo2() { b1=0x01, s=0x0203, S=0x0405, i=0x06070809, I=0x0a0b0c0d, l=0xe0f101112131415, L=0x161718191a1b1c, f=1.234f, d=4.56789, MyString=@"123456789",//nullterminated=>only9characters! }; Foosample=newFoo() { b1=0x01, s=0x0203, S=0x0405, i=0x06070809, I=0x0a0b0c0d, l=0xe0f101112131415, L=0x161718191a1b1c, f=1.234f, d=4.56789, MyString=@"123456789",//nullterminated=>only9characters! foo2=sample2, }; varbytes_LE=Dummy.StructToBytes(sample,Endianness.LittleEndian); varrestoredLEAsLE=Dummy.BytesToStruct(bytes_LE,Endianness.LittleEndian); varrestoredLEAsBE=Dummy.BytesToStruct (bytes_LE,Endianness.BigEndian); varbytes_BE=Dummy.StructToBytes(sample,Endianness.BigEndian); varrestoredBEAsLE=Dummy.BytesToStruct (bytes_BE,Endianness.LittleEndian); varrestoredBEAsBE=Dummy.BytesToStruct (bytes_BE,Endianness.BigEndian); Debug.Assert(sample.Equals(restoredLEAsLE)); Debug.Assert(sample.Equals(restoredBEAsBE)); Debug.Assert(restoredBEAsLE.Equals(restoredLEAsBE)); } publicenumEndianness { BigEndian, LittleEndian } privatestaticvoidMaybeAdjustEndianness(Typetype,byte[]data,Endiannessendianness,intstartOffset=0) { if((BitConverter.IsLittleEndian)==(endianness==Endianness.LittleEndian)) { //nothingtochange=>return return; } foreach(varfieldintype.GetFields()) { varfieldType=field.FieldType; if(field.IsStatic) //don'tprocessstaticfields continue; if(fieldType==typeof(string)) //don'tswapbytesforstrings continue; varoffset=Marshal.OffsetOf(type,field.Name).ToInt32(); //handleenums if(fieldType.IsEnum) fieldType=Enum.GetUnderlyingType(fieldType); //checkforsub-fieldstorecurseifnecessary varsubFields=fieldType.GetFields().Where(subField=>subField.IsStatic==false).ToArray(); vareffectiveOffset=startOffset+offset; if(subFields.Length==0) { Array.Reverse(data,effectiveOffset,Marshal.SizeOf(fieldType)); } else { //recurse MaybeAdjustEndianness(fieldType,data,endianness,effectiveOffset); } } } internalstaticTBytesToStruct (byte[]rawData,Endiannessendianness)whereT:struct { Tresult=default(T); MaybeAdjustEndianness(typeof(T),rawData,endianness); GCHandlehandle=GCHandle.Alloc(rawData,GCHandleType.Pinned); try { IntPtrrawDataPtr=handle.AddrOfPinnedObject(); result=(T)Marshal.PtrToStructure(rawDataPtr,typeof(T)); } finally { handle.Free(); } returnresult; } internalstaticbyte[]StructToBytes (Tdata,Endiannessendianness)whereT:struct { byte[]rawData=newbyte[Marshal.SizeOf(data)]; GCHandlehandle=GCHandle.Alloc(rawData,GCHandleType.Pinned); try { IntPtrrawDataPtr=handle.AddrOfPinnedObject(); Marshal.StructureToPtr(data,rawDataPtr,false); } finally { handle.Free(); } MaybeAdjustEndianness(typeof(T),rawData,endianness); returnrawData; } }
参考资料
https://www.developerfusion.com/article/84519/mastering-structs-in-c/
https://stackoverflow.com/a/15020402
https://stackoverflow.com/questions/628843/byte-for-byte-serialization-of-a-struct-in-c-sharp/629120
https://stackoverflow.com/questions/2871/reading-a-c-c-data-structure-in-c-sharp-from-a-byte-array/41836532
到此这篇关于C#使用struct直接转换下位机数据的文章就介绍到这了,更多相关C#下位机数据内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!