C#使用struct直接轉(zhuǎn)換下位機(jī)數(shù)據(jù)的示例代碼
編寫上位機(jī)與下位機(jī)通信的時(shí)候,涉及到協(xié)議的轉(zhuǎn)換,比較多會(huì)使用到二進(jìn)制。傳統(tǒng)的方法,是將數(shù)據(jù)整體獲取到byte數(shù)組中,然后逐字節(jié)對(duì)數(shù)據(jù)進(jìn)行解析。這樣操作工作量比較大,對(duì)于較長(zhǎng)數(shù)據(jù)段更容易計(jì)算位置出錯(cuò)。
其實(shí),對(duì)于下位機(jī)給出通訊的數(shù)據(jù)結(jié)構(gòu)的情況下,可以直接使用C#的struct將數(shù)據(jù)直接轉(zhuǎn)換。需要使用到Marshal
。
數(shù)據(jù)結(jié)構(gòu)
假定下位機(jī)(C語(yǔ)言編寫)給到我們的數(shù)據(jù)結(jié)構(gòu)是這個(gè),傳輸方式為小端方式
typedef struct { unsigned long int time; // 4個(gè)字節(jié) float tmpr[3]; // 4*3 個(gè)字節(jié) float forces[6]; // 4*6個(gè)字節(jié) float distance[6]; // 4*6個(gè)字節(jié) } dataItem_t;
方法1
首先需要定義一個(gè)struct:
[StructLayout(LayoutKind.Sequential, Size = 64, Pack = 1)] public struct HardwareData { //[FieldOffset(0)] public UInt32 Time; // 4個(gè)字節(jié) [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)] //[FieldOffset(4)] public float[] Tmpr; // 3* 4個(gè)字節(jié) //[FieldOffset(16)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public float[] Forces; // 6* 4個(gè)字節(jié) //[FieldOffset(40)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)] public float[] Distance; // 6*4個(gè)字節(jié) }
然后使用以下代碼進(jìn)行轉(zhuǎn)換
// code from https://stackoverflow.com/questions/628843/byte-for-byte-serialization-of-a-struct-in-c-sharp/629120#629120 /// <summary> /// converts byte[] to struct /// </summary> public static T RawDeserialize<T>(byte[] rawData, int position) { int rawsize = Marshal.SizeOf(typeof(T)); if (rawsize > rawData.Length - position) throw new ArgumentException("Not enough data to fill struct. Array length from position: " + (rawData.Length - position) + ", Struct length: " + rawsize); IntPtr buffer = Marshal.AllocHGlobal(rawsize); Marshal.Copy(rawData, position, buffer, rawsize); T retobj = (T)Marshal.PtrToStructure(buffer, typeof(T)); Marshal.FreeHGlobal(buffer); return retobj; } /// <summary> /// converts a struct to byte[] /// </summary> public static byte[] RawSerialize(object anything) { int rawSize = Marshal.SizeOf(anything); IntPtr buffer = Marshal.AllocHGlobal(rawSize); Marshal.StructureToPtr(anything, buffer, false); byte[] rawDatas = new byte[rawSize]; Marshal.Copy(buffer, rawDatas, 0, rawSize); Marshal.FreeHGlobal(buffer); return rawDatas; }
注意這里我使用的方式為LayoutKind.Sequential
,如果直接使用LayoutKind.Explicit
并設(shè)置FieldOffset
會(huì)彈出一個(gè)詭異的錯(cuò)誤System.TypeLoadException:“Could not load type 'ConsoleApp3.DataItem' from assembly 'ConsoleApp3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 4 that is incorrectly aligned or overlapped by a non-object field.”。
方法2
提示是對(duì)齊的錯(cuò)誤,這個(gè)和編譯的時(shí)候使用的32bit和64位是相關(guān)的,詳細(xì)數(shù)據(jù)封送對(duì)齊的操作我不就詳細(xì)說了,貼下代碼。
//強(qiáng)制指定x86編譯 [StructLayout(LayoutKind.Explicit, Size = 64, Pack = 1)] public struct DataItem { [MarshalAs(UnmanagedType.U4)] [FieldOffset(0)] public UInt32 time; // 4個(gè)字節(jié) [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3, ArraySubType = UnmanagedType.R4)] [FieldOffset(4)] public float[] tmpr; // 3* 4個(gè)字節(jié) [FieldOffset(16)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6, ArraySubType = UnmanagedType.R4)] public float[] forces; // 6* 4個(gè)字節(jié) [FieldOffset(40)] [MarshalAs(UnmanagedType.ByValArray, SizeConst = 6, ArraySubType = UnmanagedType.R4)] public float[] distance; // 6*4個(gè)字節(jié) }
強(qiáng)制指定x64編譯沒有成功,因?yàn)閿?shù)據(jù)對(duì)齊后和從下位機(jī)上來的數(shù)據(jù)長(zhǎng)度是不符的。
方法3
微軟不是很推薦使用LayoutKind.Explicit
,如果非要用并且不想指定平臺(tái)的話,可以使用指針來操作,當(dāng)然,這個(gè)需要unsafe
。
var item = RawDeserialize<DataItem>(tail.ToArray(), 0); unsafe { float* p = &item.forces; for (int i = 0; i < 6; i++) { Console.WriteLine(*p); p++; } } [StructLayout(LayoutKind.Explicit, Size = 64, Pack = 1)] public struct DataItem { [FieldOffset(0)] public UInt32 time; // 4個(gè)字節(jié) [FieldOffset(4)] public float tmpr; // 3* 4個(gè)字節(jié) [FieldOffset(16)] public float forces; // 6* 4個(gè)字節(jié) [FieldOffset(40)] public float distance; // 6*4個(gè)字節(jié) }
方法4
感覺寫起來還是很麻煩,既然用上了unsafe
,就干脆直接一點(diǎn)。
[StructLayout(LayoutKind.Sequential, Pack = 1)] public unsafe struct DataItem { public UInt32 time; // 4個(gè)字節(jié) public fixed float tmpr[3]; // 3* 4個(gè)字節(jié) public fixed float forces[6]; // 6* 4個(gè)字節(jié) public fixed float distance[6]; // 6*4個(gè)字節(jié) }
這樣,獲得數(shù)組可以直接正常訪問,不再需要unsafe
了。
總結(jié)
數(shù)據(jù)解析作為上下位機(jī)通訊的常用操作,使用struct直接轉(zhuǎn)換數(shù)據(jù)可以大大簡(jiǎn)化工作量。建議還是使用LayoutKind.Sequential
來進(jìn)行封送數(shù)據(jù),有關(guān)于數(shù)據(jù)在托管與非托管中的轉(zhuǎn)換,可以詳細(xì)看看微軟有關(guān)互操作的內(nèi)容。
以上代碼在.NET 5.0下編譯通過并能正常執(zhí)行。
補(bǔ)充
注意上面的前提要求是字節(jié)序?yàn)樾《俗止?jié)序(一般計(jì)算機(jī)都是小端字節(jié)序),對(duì)于大端字節(jié)序發(fā)送過來的數(shù)據(jù),需要進(jìn)行字節(jié)序轉(zhuǎn)換。我找到一處代碼寫的很好:
//CODE FROM https://stackoverflow.com/a/15020402 public static class FooTest { [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct Foo2 { public byte b1; public short s; public ushort S; public int i; public uint I; public long l; public ulong L; public float f; public double d; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)] public string MyString; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct Foo { public byte b1; public short s; public ushort S; public int i; public uint I; public long l; public ulong L; public float f; public double d; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)] public string MyString; public Foo2 foo2; } public static void test() { Foo2 sample2 = new Foo2() { b1 = 0x01, s = 0x0203, S = 0x0405, i = 0x06070809, I = 0x0a0b0c0d, l = 0xe0f101112131415, L = 0x161718191a1b1c, f = 1.234f, d = 4.56789, MyString = @"123456789", // null terminated => only 9 characters! }; Foo sample = new Foo() { b1 = 0x01, s = 0x0203, S = 0x0405, i = 0x06070809, I = 0x0a0b0c0d, l = 0xe0f101112131415, L = 0x161718191a1b1c, f = 1.234f, d = 4.56789, MyString = @"123456789", // null terminated => only 9 characters! foo2 = sample2, }; var bytes_LE = Dummy.StructToBytes(sample, Endianness.LittleEndian); var restoredLEAsLE = Dummy.BytesToStruct<Foo>(bytes_LE, Endianness.LittleEndian); var restoredLEAsBE = Dummy.BytesToStruct<Foo>(bytes_LE, Endianness.BigEndian); var bytes_BE = Dummy.StructToBytes(sample, Endianness.BigEndian); var restoredBEAsLE = Dummy.BytesToStruct<Foo>(bytes_BE, Endianness.LittleEndian); var restoredBEAsBE = Dummy.BytesToStruct<Foo>(bytes_BE, Endianness.BigEndian); Debug.Assert(sample.Equals(restoredLEAsLE)); Debug.Assert(sample.Equals(restoredBEAsBE)); Debug.Assert(restoredBEAsLE.Equals(restoredLEAsBE)); } public enum Endianness { BigEndian, LittleEndian } private static void MaybeAdjustEndianness(Type type, byte[] data, Endianness endianness, int startOffset = 0) { if ((BitConverter.IsLittleEndian) == (endianness == Endianness.LittleEndian)) { // nothing to change => return return; } foreach (var field in type.GetFields()) { var fieldType = field.FieldType; if (field.IsStatic) // don't process static fields continue; if (fieldType == typeof(string)) // don't swap bytes for strings continue; var offset = Marshal.OffsetOf(type, field.Name).ToInt32(); // handle enums if (fieldType.IsEnum) fieldType = Enum.GetUnderlyingType(fieldType); // check for sub-fields to recurse if necessary var subFields = fieldType.GetFields().Where(subField => subField.IsStatic == false).ToArray(); var effectiveOffset = startOffset + offset; if (subFields.Length == 0) { Array.Reverse(data, effectiveOffset, Marshal.SizeOf(fieldType)); } else { // recurse MaybeAdjustEndianness(fieldType, data, endianness, effectiveOffset); } } } internal static T BytesToStruct<T>(byte[] rawData, Endianness endianness) where T : struct { T result = default(T); MaybeAdjustEndianness(typeof(T), rawData, endianness); GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned); try { IntPtr rawDataPtr = handle.AddrOfPinnedObject(); result = (T)Marshal.PtrToStructure(rawDataPtr, typeof(T)); } finally { handle.Free(); } return result; } internal static byte[] StructToBytes<T>(T data, Endianness endianness) where T : struct { byte[] rawData = new byte[Marshal.SizeOf(data)]; GCHandle handle = GCHandle.Alloc(rawData, GCHandleType.Pinned); try { IntPtr rawDataPtr = handle.AddrOfPinnedObject(); Marshal.StructureToPtr(data, rawDataPtr, false); } finally { handle.Free(); } MaybeAdjustEndianness(typeof(T), rawData, endianness); return rawData; } }
參考資料
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
到此這篇關(guān)于C#使用struct直接轉(zhuǎn)換下位機(jī)數(shù)據(jù)的文章就介紹到這了,更多相關(guān)C#下位機(jī)數(shù)據(jù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- C#調(diào)用C類型dll入?yún)閟truct的問題詳解
- C#中的只讀結(jié)構(gòu)體(readonly struct)詳解
- 區(qū)分C# 中的 Struct 和 Class
- 淺析C# 結(jié)構(gòu)體struct
- C#如何從byte[]中直接讀取Structure實(shí)例詳解
- 深入解析C#編程中struct所定義的結(jié)構(gòu)
- C#中結(jié)構(gòu)(struct)的部分初始化和完全初始化實(shí)例分析
- C#中struct和class的區(qū)別詳解
- C# Struct的內(nèi)存布局問題解答
- 深入探討C#中的結(jié)構(gòu)struct
- c# Struct的一些問題分析
相關(guān)文章
C#實(shí)現(xiàn)windows系統(tǒng)重啟和關(guān)機(jī)的代碼詳解
這篇文章主要介紹了C#實(shí)現(xiàn)windows系統(tǒng)重啟和關(guān)機(jī)的的方法,涉及C#調(diào)用windows系統(tǒng)命令實(shí)現(xiàn)控制開機(jī)、關(guān)機(jī)等操作的技巧,非常簡(jiǎn)單實(shí)用,需要的朋友可以參考下2024-02-02C#使用Socket實(shí)現(xiàn)本地多人聊天室
這篇文章主要為大家詳細(xì)介紹了C#使用Socket實(shí)現(xiàn)本地多人聊天室,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02C#圖像處理之圖像目標(biāo)質(zhì)心檢測(cè)的方法
這篇文章主要介紹了C#圖像處理之圖像目標(biāo)質(zhì)心檢測(cè)的方法,可實(shí)現(xiàn)C#計(jì)算圖像質(zhì)心的相關(guān)技巧,需要的朋友可以參考下2015-04-04C# 并發(fā)控制框架之單線程環(huán)境下實(shí)現(xiàn)每秒百萬(wàn)級(jí)調(diào)度
本文介紹了一款專為工業(yè)自動(dòng)化及機(jī)器視覺開發(fā)的C#并發(fā)流程控制框架,通過模仿Go語(yǔ)言并發(fā)模式設(shè)計(jì),支持高頻調(diào)度及復(fù)雜任務(wù)處理,已在多個(gè)項(xiàng)目中驗(yàn)證其穩(wěn)定性和可靠性2024-10-10