字节序转换 + 模板

📅 2026/7/3 18:47:31 👁️ 阅读次数 📝 编程学习
字节序转换 + 模板

这里有一个结构体:

// 类型别名对齐协议 typedef quint8 uchar; typedef qint8 schar; typedef quint16 ushort; typedef qint16 sshort; typedef quint32 uint; typedef qint32 sint; #pragma pack (push,1) // 报文头 typedef struct tagHead { unsigned char m_Iofo; unsigned short m_MesLength; unsigned short m_Send; unsigned short m_Rece; unsigned char m_TaskAttr; unsigned char m_CommType; unsigned int m_Serial; void init(quint8 Iden, quint16 len, quint16 sender, quint16 recv, quint8 task, quint8 label) { m_Iofo = Iden; m_MesLength = len; m_Send = sender; m_Rece = recv; m_TaskAttr = task; m_CommType = label; } }HEAD; typedef struct tagMainCtrlBitReplyMes { HEAD m_head; unsigned char m_MainCtrl; unsigned char m_Ser; unsigned char m_CheckSum; // 校验和 }MainCtrlBitReplyMes; #pragma pack (pop)

在网络通信中,因为我和对方的电脑都是小端序,之前我们直接通过内存映射来将这段信息转为字节流。

MultiDataMes msg; QByteArray ba((const char *)&msg,sizeof(tagMultiDataMes));

但现在要求网络传输使用大端序,这里就涉及到字节序转换了。

小端 ---> 大端(网络字节序) ---> 小端

先实现小端 ---> 大端(网络字节序)

我们的代码涉及到需要转换的基础数据类型:

int,unsigned int

short,unsigned short

#include <QByteArray> #include <QDataStream> QByteArray HeadToNet(const HEAD& head) { QByteArray buf; QDataStream ds(&buf, QIODevice::WriteOnly); // 强制网络大端序 ds.setByteOrder(QDataStream::BigEndian); // 按结构体顺序依次写入 ds << head.m_Iofo; ds << head.m_MesLength; ds << head.m_Send; ds << head.m_Rece; ds << head.m_TaskAttr; ds << head.m_CommType; ds << head.m_Serial; return buf; }

上面的代码把结构体Head对象以大端序转为字节流存储

QByteArray MainCtrlReplyToNet(const MainCtrlBitReplyMes& msg) { QByteArray buf; QDataStream ds(&buf, QIODevice::WriteOnly); ds.setByteOrder(QDataStream::BigEndian); // 1. 先写入报文头全部字段 QByteArray headBuf = HeadToNet(msg.m_head); ds.writeRawData(headBuf.constData(), headBuf.size()); // 2. 写入报文体 ds << msg.m_MainCtrl; ds << msg.m_Ser; ds << msg.m_CheckSum; return buf; }

我们的结构体是两层嵌套的,上面的代码把结构体MainCtrlReplyToNet对象以大端序转为字节流存储

QByteArray headBuf = HeadToNet(msg.m_head); ds.writeRawData(headBuf.constData(), headBuf.size());

这里需要使用writeRawData函数

不可以直接写成

ds << headBuf;

这里headBuf为: "9e000f02011001a12000000000"

但是 ds前面多出来0000000d

原因:

QDataStream 序列化 QByteArray 时,默认先写入 4 字节长度头(大端 int),再写数组内容,你看到前面多的0000000d就是长度字段(0x0d = 13,HEAD 刚好 13 字节)。


大端序 ---> 结构体

依然使用QDataStream

// 返回true解析成功,false数据长度不足/解析失败 bool NetToHead(QDataStream& ds, HEAD& outHead) { // 逐个读取字段 ds >> outHead.m_Iofo; ds >> outHead.m_MesLength; ds >> outHead.m_Send; ds >> outHead.m_Rece; ds >> outHead.m_TaskAttr; ds >> outHead.m_CommType; ds >> outHead.m_Serial; // 校验流状态,读取出错返回false return ds.status() == QDataStream::Ok; }
bool NetToMainCtrlReply(const QByteArray& netData, MainCtrlBitReplyMes& outMsg) { QDataStream ds(netData); ds.setByteOrder(QDataStream::BigEndian); // 读取HEAD头 NetToHead(ds,outMsg.m_head); // 读取应答数据域 ds >> outMsg.m_MainCtrl; ds >> outMsg.m_Ser; ds >> outMsg.m_CheckSum; return ds.status() == QDataStream::Ok; }

实际上关键就是QDataStream:

我们使用它进行 结构体 ---> 大端序字节流 大端序字节流 ---> 结构体


进一步来讲:

我的软件涉及到可能30个类似的报文,我不想手动一个个去写,我希望可以自动化一些。

例:

// 混合数值报文示例(包含全部 short/ushort/int/uint/char/uchar) typedef struct tagMultiDataMes { HEAD m_head; // 单字节 schar m_charVal; uchar m_ucharVal; // 两字节 short sshort m_shortVal; ushort m_ushortVal; // 四字节 int sint m_intVal; uint m_uintVal; // 末尾校验字节(和你原有报文风格统一) uchar m_CheckSum; } MultiDataMes;

自动化思路:实际上,对于每个结构体来说,Head部分都是相同的代码,我们只需要特化数据部分。

HEAD相关函数:

#include <QByteArray> #include <QDataStream> QByteArray HeadToNet(const HEAD& head) { QByteArray buf; QDataStream ds(&buf, QIODevice::WriteOnly); // 强制网络大端序 ds.setByteOrder(QDataStream::BigEndian); // 按结构体顺序依次写入 ds << head.m_Iofo; ds << head.m_MesLength; ds << head.m_Send; ds << head.m_Rece; ds << head.m_TaskAttr; ds << head.m_CommType; ds << head.m_SerialNum; return buf; } // 返回true解析成功,false数据长度不足/解析失败 bool NetToHead(QDataStream& ds, HEAD& outHead) { // 逐个读取字段 ds >> outHead.m_Iofo; ds >> outHead.m_MesLength; ds >> outHead.m_Send; ds >> outHead.m_Rece; ds >> outHead.m_TaskAttr; ds >> outHead.m_CommType; ds >> outHead.m_SerialNum; // 校验流状态,读取出错返回false return ds.status() == QDataStream::Ok; } QDataStream& operator<<(QDataStream& ds, const HEAD& head) { QByteArray msg = HeadToNet(head); ds.writeRawData(msg.constData(), msg.size()); return ds; } QDataStream& operator>>(QDataStream& ds, HEAD& head) { NetToHead(ds,head); return ds; }

HEAD配套完整 QDataStream 重载

这时候可以优化一下之前的代码:

QByteArray MainCtrlReplyToNet(const MainCtrlBitReplyMes& msg) { QByteArray buf; QDataStream ds(&buf, QIODevice::WriteOnly); ds.setByteOrder(QDataStream::BigEndian); // 1. 先写入报文头全部字段 ds << msg.m_head; // 2. 写入报文体 ds << msg.m_MainCtrl; ds << msg.m_Ser; ds << msg.m_CheckSum; return buf; } bool NetToMainCtrlReply(const QByteArray& netData, MainCtrlBitReplyMes& outMsg) { QDataStream ds(netData); ds.setByteOrder(QDataStream::BigEndian); // 读取HEAD头 ds >> outMsg.m_head; // 读取应答数据域 ds >> outMsg.m_MainCtrl; ds >> outMsg.m_Ser; ds >> outMsg.m_CheckSum; return ds.status() == QDataStream::Ok; }

注意:我想使用脚本来完成这部分工作:

输入是我们的结构体,输出是下面的文本:

QDataStream& operator<<(QDataStream& ds, const MainCtrlBitReplyMes& msg) { ds << msg.m_head; ds << msg.m_MainCtrl; ds << msg.m_Ser; ds << msg.m_CheckSum; return ds; } QDataStream& operator>>(QDataStream& ds, MainCtrlBitReplyMes& msg) { ds >> msg.m_head; ds >> msg.m_MainCtrl; ds >> msg.m_Ser; ds >> msg.m_CheckSum; return ds; }

那么代码可以进一步优化:

QByteArray MainCtrlReplyToNet(const MainCtrlBitReplyMes& msg) { QByteArray buf; QDataStream ds(&buf, QIODevice::WriteOnly); ds.setByteOrder(QDataStream::BigEndian); ds << msg; return buf; } bool NetToMainCtrlReply(const QByteArray& netData, MainCtrlBitReplyMes& outMsg) { QDataStream ds(netData); ds.setByteOrder(QDataStream::BigEndian); // 读取HEAD头 ds >> outMsg; return ds.status() == QDataStream::Ok; }

再添加一个结构体:

typedef struct tagTestDataMes { HEAD m_head; int m_i32; unsigned int m_ui32; short m_i16; unsigned short m_ui16; char m_c8; unsigned char m_uc8; }TestDataMes; // char 输出重载 QDataStream& operator<<(QDataStream& ds, char c) { ds << static_cast<qint8>(c); return ds; } // char 输入重载 QDataStream& operator>>(QDataStream& ds, char& c) { qint8 tmp; ds >> tmp; c = static_cast<char>(tmp); return ds; } // ========== TestDataMes serialize ========== QDataStream& operator<<(QDataStream& ds, const TestDataMes& msg) { ds << msg.m_head; ds << msg.m_i32; ds << msg.m_ui32; ds << msg.m_i16; ds << msg.m_ui16; ds << msg.m_c8; ds << msg.m_uc8; return ds; } // ========== TestDataMes deserialize ========== QDataStream& operator>>(QDataStream& ds, TestDataMes& msg) { ds >> msg.m_head; ds >> msg.m_i32; ds >> msg.m_ui32; ds >> msg.m_i16; ds >> msg.m_ui16; ds >> msg.m_c8; ds >> msg.m_uc8; return ds; }

需要注意的是,需要重载char类型

QDataStream<<>>只支持 Qt 标准整数类型:qint8/quint8/qint16/quint16/qint32/quint32

代码里直接用了原生charQDataStream没有重载char,编译直接报二元运算符错误。

所以,我们只需要生成结构体对应的QDataStream 重载函数。

我们再继续优化,编写模板函数:

#include <QByteArray> #include <QDataStream> // 通用:结构体 → 网络字节数组 template<typename T> QByteArray StructToNet(const T& msg) { QByteArray buf; QDataStream ds(&buf, QIODevice::WriteOnly); ds.setByteOrder(QDataStream::BigEndian); ds << msg; return buf; } // 通用:网络字节数组 → 结构体 template<typename T> bool NetToStruct(const QByteArray& netData, T& outMsg) { QDataStream ds(netData); ds.setByteOrder(QDataStream::BigEndian); ds >> outMsg; return ds.status() == QDataStream::Ok; }

测试程序1:

int main(int argc, char *argv[]) { QApplication a(argc, argv); MainCtrlBitReplyMes msg; memset(&msg,0,sizeof(msg)); msg.m_head.init(0x9e,sizeof(MainCtrlBitReplyMes) - 1,0x0201,0x1001,0xA1,0x20); msg.m_MainCtrl = 0x01; msg.m_Ser = 0x10; msg.m_CheckSum = 0x20; QByteArray ba; ba.append((const char *)&msg,sizeof(msg)); qDebug() << ba.toHex(); QByteArray changed = StructToNet(msg); qDebug() << changed.toHex(); MainCtrlBitReplyMes msg1; NetToStruct(changed,msg1); { QByteArray ba; ba.append((const char *)&msg1,sizeof(msg1)); qDebug() << ba.toHex(); qDebug() << __FILE__ << __LINE__ << msg1.m_Ser; } return a.exec(); }

"9e0f0001020110a12000000000011020"
"9e000f02011001a12000000000011020"
"9e0f0001020110a12000000000011020"
..\Test\main.cpp 211 16

测试程序2:

int main(int argc, char *argv[]) { QApplication a(argc, argv); TestDataMes msg; memset(&msg,0,sizeof(msg)); msg.m_head.init(0x9e,sizeof(TestDataMes) - 1,0x0201,0x1001,0xA1,0x20); msg.m_i32 = 1234; msg.m_uc8 = 3; QByteArray ba; ba.append((const char *)&msg,sizeof(msg)); qDebug() << ba.toHex(); QByteArray changed = StructToNet(msg); qDebug() << changed.toHex(); TestDataMes msg1; NetToStruct(changed,msg1); { QByteArray ba; ba.append((const char *)&msg1,sizeof(msg1)); qDebug() << ba.toHex(); qDebug() << __FILE__ << __LINE__ << msg1.m_i32 << msg.m_uc8; } return a.exec(); }

输出:

"9e1a0001020110a12000000000d204000000000000000000000003"
"9e001a02011001a12000000000000004d200000000000000000003"
"9e1a0001020110a12000000000d204000000000000000000000003"
..\Test\main.cpp 210 1234 3

生成QDataStream<<>>重载函数的脚本文件gen_struct_code.py

import re # ========== 在这里粘贴你的所有结构体文本 ========== struct_src = """ typedef struct tagMainCtrlBitReplyMes { HEAD m_head; // 报文头 unsigned char m_MainCtrlStatus; // 主控板状态(00H:正常 11H:异常) unsigned char m_SerStatus; // 伺服板状态(00H:正常 11H:异常) unsigned char m_CheckSum; // 校验和 }MainCtrlBitReplyMes; typedef struct tagTestDataMes { HEAD m_head; int m_i32; unsigned int m_ui32; short m_i16; unsigned short m_ui16; char m_c8; unsigned char m_uc8; }TestDataMes; """ # ============================================== def parse_structs(text): pat = re.compile(r"typedef struct\s+(\w+)\s*\{(.*?)\}\s*(\w+);", re.S) res = [] for m in pat.finditer(text): struct_tag = m.group(1) body = m.group(2).strip() struct_name = m.group(3) fields = [] lines = [line.strip() for line in body.splitlines() if line.strip()] for line in lines: line_no_comment = re.sub(r"//.*", "", line).strip() if not line_no_comment: continue pure = line_no_comment.rstrip(";") parts = pure.split() var = parts[-1] ty = " ".join(parts[:-1]) fields.append((ty, var)) res.append((struct_name, fields)) return res def gen_cpp_code(struct_list): output = [] for name, fields in struct_list: output.append(f"// ==================== {name} 序列化反序列化 ====================") # // 输出 operator << output.append(f"QDataStream& operator<<(QDataStream& ds, const {name}& msg)") output.append("{") for _, var in fields: output.append(f" ds << msg.{var};") output.append(" return ds;") output.append("}\n") # // 输出 operator >> output.append(f"QDataStream& operator>>(QDataStream& ds, {name}& msg)") output.append("{") for _, var in fields: output.append(f" ds >> msg.{var};") output.append(" return ds;") output.append("}\n\n") return "\n".join(output) if __name__ == "__main__": structs = parse_structs(struct_src) code = gen_cpp_code(structs) print("=============== 生成完成,复制下面全部代码到Qt头文件 ===============") print(code) # 自动输出到文本文件 with open("serial_code.h", "w", encoding="utf-8") as f: f.write(code) print("\n文件已自动生成:serial_code.h(和脚本同一文件夹)")

执行:

python gen_struct_code.py

打印:

=============== 生成完成,复制下面全部代码到Qt头文件 =============== // ==================== MainCtrlBitReplyMes 序列化反序列化 ==================== QDataStream& operator<<(QDataStream& ds, const MainCtrlBitReplyMes& msg) { ds << msg.m_head; ds << msg.m_MainCtrlStatus; ds << msg.m_SerStatus; ds << msg.m_CheckSum; return ds; } QDataStream& operator>>(QDataStream& ds, MainCtrlBitReplyMes& msg) { ds >> msg.m_head; ds >> msg.m_MainCtrlStatus; ds >> msg.m_SerStatus; ds >> msg.m_CheckSum; return ds; } // ==================== TestDataMes 序列化反序列化 ==================== QDataStream& operator<<(QDataStream& ds, const TestDataMes& msg) { ds << msg.m_head; ds << msg.m_i32; ds << msg.m_ui32; ds << msg.m_i16; ds << msg.m_ui16; ds << msg.m_c8; ds << msg.m_uc8; return ds; } QDataStream& operator>>(QDataStream& ds, TestDataMes& msg) { ds >> msg.m_head; ds >> msg.m_i32; ds >> msg.m_ui32; ds >> msg.m_i16; ds >> msg.m_ui16; ds >> msg.m_c8; ds >> msg.m_uc8; return ds; } 文件已自动生成:serial_code.h(和脚本同一文件夹)