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~ ,想噴的話,請各位同學噴的輕些。

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