C++宏的黑科技用法《结构体字节序自动转换》

背景

慢慢的自己管理项目已经一年了,这也是我管理的第一个项目,期间触及了挺多的知识盲区,开始慢慢的摸索,慢慢的熟悉,将压力转化为了动力。我在这里想总结下作为新人的我在这一年里收获了什么。

开始优化

可能源于自己管理项目,我开始放飞自我,但凡自己感觉看不顺眼的地方就改改改(还好没改出什么大问题出来)。开始每优化一个代码我都会反复去测,生怕出问题。通过不同的写法来简化代码,期间用的最多的应该是宏吧!这套代码最基础的框架年代稍微也有些久远,写的人越来越多,花里胡哨的,抄代码的啥都有,经常给人一直繁琐冗乱的感觉,特别是抄代码的时候,会出现大量类似代码,为了方便其他项目人员编写,提高效率,我做了挺多封装,特别是使用宏跟lambda,不过也是源于C++11后,之前很多没法优化的东西现在都可以搞了。这些都是题外话,进入主题吧!

为何需要自动转换?

为何要自己搞自动转换?用Protobuf不香吗?这东西确实好,毕竟Google出品,必属精品。但我没选,毕竟还是想兼容下当前项目(后期做Protobuf扩展),目前项目使用socket通信,与前端通信时使用网络字节序跟本地字节序转换,现状是需要对每个字段单独本地字节序解析的方式接收!!!然而接收时任然是要对包体总大小做检测的。所以我想以最简单的方式实现,毕竟当前服务端socket协议传输数据量不大,几大基础类型就能胜任,于是有了现在这东西,目前这个虽然大体能支持我服务端的要求但是还是有不足,扩展性不好,但用起来简单。
希望有更好解决办法的小伙伴给我指点下。

/*
 * Author		: Guojie Zhang
 * Email        :[email protected]
 * Description	: 用于结构体字节序解析	  
 *				  能解析的最大参数受此处定义的宏所限制(暂时没有想到更好的解决办法)
 */

#ifndef _PROTOCOL_BODY_H
#define _PROTOCOL_BODY_H

#include "Common.h"

#define TOH(t) Protocol::NTOH(t)
#define TON(t) Protocol::HTON(t)

#pragma pack(push)
#pragma pack(1)

namespace Protocol
{

	// 主机字节序转换
	template<class T> T NTOH(const T& t) {
		T num = t;
		switch (sizeof(T) * 8)
		{
		case 16:
			num = ntohs(t);
			break;
		case 32:
			num = ntohl(t);
			break;
		case 64:
			num = ntohll(t);
			break;
		default:
			break;
		}
		return num;
	}

	// 网络字节序转换
	template<class T> T HTON(const T& t) {
		T num = t;
		switch (sizeof(T) * 8)
		{
		case 16:
			num = htons(t);
			break;
		case 32:
			num = htonl(t);
			break;
		case 64:
			num = htonll(t);
			break;
		default:
			break;
		}
		return num;
	}


////////////////////////////////////////////////////////////////////////////////////

#define ParseParam1						\
	auto& _param = GetProtoParam(1);	\
	_param = HTON(_param);				\
	startSize += GetProtoSize(1);
#define ParseParam2						\
	auto& _param = GetProtoParam(2);	\
	_param = HTON(_param);				\
	startSize += GetProtoSize(2);
#define ParseParam3						\
	auto& _param = GetProtoParam(3);	\
	_param = HTON(_param);				\
	startSize += GetProtoSize(3);
#define ParseParam4						\
	auto& _param = GetProtoParam(4);	\
	_param = HTON(_param);				\
	startSize += GetProtoSize(4);
#define ParseParam5						\
	auto& _param = GetProtoParam(5);	\
	_param = HTON(_param);				\
	startSize += GetProtoSize(5);
#define ParseParam6						\
	auto& _param = GetProtoParam(6);	\
	_param = HTON(_param);				\
	startSize += GetProtoSize(6);
#define ParseParam7						\
	auto& _param = GetProtoParam(7);	\
	_param = HTON(_param);				\
	startSize += GetProtoSize(7);
#define ParseParam8						\
	auto& _param = GetProtoParam(8);	\
	_param = HTON(_param);				\
	startSize += GetProtoSize(8);
#define ParseParam9						\
	auto& _param = GetProtoParam(9);	\
	_param = HTON(_param);				\
	startSize += GetProtoSize(9);


#define PRO_PARAM_MAX_SIZE 9

#define GetProParamSizeByCount(count)		\
	if (1 == count) { ParseParam1 }			\
	else if (2 == count) { ParseParam2 }	\
	else if (3 == count) { ParseParam3 }	\
	else if (4 == count) { ParseParam4 }	\
	else if (5 == count) { ParseParam5 }	\
	else if (6 == count) { ParseParam6 }	\
	else if (7 == count) { ParseParam7 }	\
	else if (8 == count) { ParseParam8 }	\
	else if (9 == count) { ParseParam9 }

// 这里是主要的解析逻辑
#define ParseProtoToH						\
	void ProtocolNtoH()						\
	{										\
		uint32 maxSize = sizeof(*this);		\
		uint32 startSize = 0;				\
		uint32 count = 1;					\
		while (startSize < maxSize			\
			&& count <= PRO_PARAM_MAX_SIZE)	\
		{									\
			GetProParamSizeByCount(count);	\
			++count;						\
		}									\
	}

////////////////////////////////////////////////////////////////////////////////////


#define SetProtoSizeFun(index, name) \
	const uint32 GetPos##index() { return sizeof(this->name); };

#define GetProtoSize(index)	\
	GetPos##index();


#define SetProtoParamFun(index, name) \
	decltype(auto) GetPosData##index() { return this->name; }

#define GetProParame(index) \
	GetPosData##index();

#define SetProtoParamFun2(type, index, name) \
	type& GetNewPosData##index() { return this->name; }

#define GetProtoParam(index) \
	GetNewPosData##index()


#define SetParamNameStrFun(index, name)	\
	std::string GetParamNameStr##index() { return #name; }

#define GetParamNameStr(index) \
	GetParamNameStr##index();


#define SetParamTypeStrFun(index, type) \
	std::string GetParamTypeStr##index() { return #type; }

#define GetparamTypeStr(index) \
	GetParamTypeStr##index();


#define SetProtoData(type, name, index)		\
	type name;								\
	SetProtoSizeFun(index, name);			\
	SetProtoParamFun2(type, index, name);	\

//  获取类型跟变量名暂时用不上 注释
//	SetParamNameStrFun(index, name);		\
//	SetParamTypeStrFun(index, type);

////////////////////////////////////////////////////////////////////////////////////

// 用于处理未定义函数导致的报错报错
#define SetProtoParamBaseFun(index) \
	uint8& GetNewPosData##index() { static uint8 i = 0; return i; } \
	const uint8 GetPos##index() { return 0; };

struct CSTestBase
{
	SetProtoParamBaseFun(1);
	SetProtoParamBaseFun(2);
	SetProtoParamBaseFun(3);
	SetProtoParamBaseFun(4);
	SetProtoParamBaseFun(5);
	SetProtoParamBaseFun(6);
	SetProtoParamBaseFun(7);
	SetProtoParamBaseFun(8);
	SetProtoParamBaseFun(9);
};

//!! 接收并解析
#define PARSE_RECV_PACKET(NAME) \
	CHECK_PACKET_SIZE(recvPacket, sizeof(Protocol::CS##NAME)); \
	Protocol::CS##NAME* const p##NAME = (Protocol::CS##NAME*)recvPacket.contents(); \
	p##NAME->ProtocolNtoH();

////////////////////////////////////////////////////////////////////////////////////

	// 自动解析结构体字节序转换 只支持基础数据类型
	// 必须继承 CSTestBase 用于处理未定义函数导致的报错
	// 目前参数最多为-> 9

	struct CSTest : CSTestBase
	{
		SetProtoData(uint32, type, 1);
		SetProtoData(uint32, pos, 2);
		SetProtoData(uint32, pos1, 3);

		ParseProtoToH;					// 解析函数必须添加
	};
	
	struct CSRForge : CSTestBase
	{
		SetProtoData(uint8, slot, 1);
		SetProtoData(uint8, type, 2);
		SetProtoData(uint32, lockFlag, 3);

		ParseProtoToH;
	};
}

#pragma pack(pop)

#endif

从这段代码可以看出存在很多不足,参数的数量我没法做成动态的扩展(这也是希望小伙伴们有好办法能告诉我的)代码我做了挺多注释,应该挺容易看得。

下面是使用示例:
// recv_data 是传输过来的包
// 开始可能这么接收
CHECK_PACKET_SIZE(recv_data, 6);
uint8 slot = 0;
uint8 type = 0;
uint32 lockFlag = 0;
recv_data.Pop(slot);
recv_data.Pop(type);
recv_data.Pop(lockFlag);


// 现在是这么接收
PARSE_RECV_PACKET(RForge)
// 通过下面代码可以看出我是通过宏来判断包大小并接收了参数最后转换为主机字节序。

#define PARSE_RECV_PACKET(NAME) \
	CHECK_PACKET_SIZE(recvPacket, sizeof(Protocol::CS##NAME)); \
	Protocol::CS##NAME* const p##NAME = (Protocol::CS##NAME*)recvPacket.contents(); \
	p##NAME->ProtocolNtoH();

旧的方式是每一个Pop都进行主机字节序转换,但这有个问题就是会导致检测包大小变得困难,特别是协议号改变时,毕竟不是结构体了。新方式我通过宏的特性来给每个变量做编号,每个变量都有对应的函数,到需要转换的时候我通过获取函数来进行解析。当然这是消耗一丢丢性能来实现的小优化,但可以提高后续小伙伴的开发效率,少踩一些不必要的坑。

创作不易,分享的话请注明出处,想点赞的话就尽情点吧O(∩_∩)O~ ,想喷的话,请各位同学喷的轻些。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章