用C/C++寫背景服務程式的工程師也許經常會遇到這樣的問題:總有一些重要的資料是通過字元串的方式輸出,比如jsonp、xml、pvlog等等。比如日志格式,往往都是通過snprintf/std::cout的接口,将格式化的資訊輸出到檔案或終端。
這樣做确實友善,但随着業務日漸複雜,又很容易出問題。我們能否将日志格式做成可配置的,但對性能影響又很小?
我将這個問題轉換為:能否讓程式員将要輸出的内容填充到一個結構體裡,然後通過配置中指定的方式将這個結構體序列化為字元串?
例如:
struct A
{
int i;
char* s;
};
假如,結構體A的對象就是我們要序列化的pvlog,我們能否按照這樣的定義:
int=%x
char*=%s
A=${i}|${s}
這樣,當有一個對象A a = {10, "123"};時,我們能按上述定義,将其序列化為字元串:
a|123
(整數‘10’被%x格式化為‘a’)
當然業務日志可能要複雜得多,裡面可能:
(1) 對于某些int類型的變量以%d方式輸出,而另外一些int類型的變量卻要以%x的方式輸出;
(2) 有指向其他結構體的指針,甚至是指針的指針(……N重指針);
(3) 含有連結清單、數組等容器,需要周遊、挨個序列化;
(4) ……
(5) (其是沒列出來的,就是沒有考慮到的,也沒有實作的:)
于是我設計了一套接口,用于封裝上述功能。但對于程式員來說,隻需要完成這麼幾步:
(1) 注冊資料結構(register_struct):告訴程式你的資料結構裡有哪些成員,這些成員是什麼類型的。
(2) 注冊序列化格式(register_typing_format):告訴程式一種類型的序列化方式。
(3) 也可以将所有類型的序列化方式寫在一個文本檔案裡,由load_typing_format接口逐行進行(2)号接口調用。
(4) 接下來,就可以将需要用到的“序列化句柄”儲存下來(get_typing_handler),以備後續重複使用。
(5) 最後,處理業務時,便可将構造好的結構體對向,按照上述句柄指定的方式進行序列化了(object_snprintf)。
舉一個例子:
struct A
{
int i;
char* s;
};
struct B
{
double* d; // 指針
string str;
};
struct S
{
int a;
string b; // stl string
char* c;
A** sA; // 2重指針
vector<B> vB; // 數組容器
};
我們可以進行如下序列化的格式定義:
int:1=%x
char:1=%c
double:1=%f
string:1=%s
char*:1=%s # 此處為char*類型定義了2種序列化格式
char*:2=%6s # 可以按需要使用
A:1=${i}|${s:1}
B:1=${d:1}
B:2=${str:1}
S:1=${a:1}|${b:1}|${c:1}|${sA:1}|$<vB:1,",">|$<vB:2,";">
(其中${sA:1}表示将S對象中的sA成員(類型A的對象),按照A:1定義的格式進行序列化;$<vB:1,",">表示将對象S對象中的vB成員(類型B的vector)中的每一個元素,按照B:1定義的格式進行序列化,vector元素之間用”,”号分割。)
這樣,當程式有這樣的S結構體的對象時:
A a = {1, "hello!"};
A* pA = &a;
double d1 = 3.1415f;
double d2 = 6.1853f;
Bb1 = {&d1, "world!"};
Bb2 = {&d2, "china!"};
vector<B> vb;
vb.push_back(b1);
vb.push_back(b2);
S sObj = {3,"4", "5", &pA, vb};
可以這樣進行序列化:
const size_t MAXLENTH = 1024;
char buff[MAXLENTH] = {0};
ret = object_snprintf(buff, MAXLENTH, pvHandl, (void *)&sObj);
std::cout<< buff << std::endl;
輸出為:
3|4|5|1|hello!|3.141500,6.185300|world!;china!
具體的接口定義如下:
typedef void* TYPINGHANDLER;
typedef int (*ObjectFormatFunc)(char*,size_t, TYPINGHANDLER, void*, const char*);
// 類型描述
struct StructMemberDescription
{
const char* memberName; // 成員名稱
DATATYPEENUM dataType; // 類型名稱
size_t offset; // 成員偏移
int plevel; // 指針層級
// 0:非指針或char*;
// 1:有1層指針或char**
// n:有n層指針或char*(*n*)
const char* structName; // 結構名稱
// dataType=DATATYPE_STRUCT 時表示類型名稱
// dataType=DATATYPE_CONTAINER 時表示容器内元素的名稱
ObjectFormatFunc formatFunc; // 對象格式化列印函數(dataType=DATATYPE_CONTAINER 時有效)
};
/*
* 類型注冊
* param:
* name : struct name
* stDesc : member description of the struct
* n : number of member description in stDesc
* return:
* 0(SUCESS), -1(FAIL)
* note:
* 1.非線程安全函數
*/
int register_struct(const char* name,const StructMemberDescription* stDesc, const size_t n);
/*
* 注冊類型的序列化格式
* param:
* typingname : typing name, "type:name"
* typingfmt : typing format string, "format"
* return:
* 0(SUCESS), -1(FAIL)
* note:
* 1.需要先注冊類型(register_struct),後注冊類型的序列化格式(register_typing_format)
* 2.序列化格式串中涉及到的資料類型及對應的序列化格式必須已定義
*/
int register_typing_format(const char*typingname, const char* typingfmt);
/*
* 加載格式化定義檔案
* note:
* 1.按行注冊類型的序列化格式(register_typing_format)
*/
int load_typing_format(const char*conf);
/*
* 擷取處理句柄
* param:
* typingname : typing format name
* return:
* return the TYPINGHANDLER of typingname
*/
TYPINGHANDLER get_typing_handler(constchar* typingname);
/*
* 對象序列化
* param:
* buff : destination buffer
* bufflen : size of buffer
* typinghandler : typing format handler
* pData : object's address
* rc : err code
* return:
* return the number of characters printed
*/
int object_snprintf(char *buff, size_tbufflen, TYPINGHANDLER typinghandler, void *pData, int *rc=NULL);
來看一個具體的使用示例:
//
// file: main.cpp
//
#include <iostream>
#include <vector>
#include <stddef.h>
#include <sys/time.h>
#include "inifile.h"
#include "template_format.h"
struct A
{
int i;
char* s;
};
struct B
{
double* d;
string str;
};
struct S
{
int a;
string b;
char* c;
A** sA;
vector<B> vB;
};
struct X
{
S* pS;
A a;
B* pB;
};
StructMemberDescriptiong_struct_member_desc_A[] = {
// memberName, dataType, offset, plevel, structName, template_snprint
{"i", DATATYPE_INT, offsetof(A,i), 0, NULL, NULL},
{"s", DATATYPE_CHARP, offsetof(A,s), 0, NULL, NULL},
};
StructMemberDescription g_struct_member_desc_B[]= {
// memberName, dataType, offset, plevel, structName, template_snprint
{"d", DATATYPE_DOUBLE, offsetof(B,d), 1, NULL, NULL},
{"str", DATATYPE_STLSTRING, offsetof(B, str), 0, NULL, NULL},
};
StructMemberDescriptiong_struct_member_desc_S[] = {
// memberName, dataType, offset, plevel, structName, template_snprint
{"a", DATATYPE_INT, offsetof(S,a), 0, NULL, NULL},
{"b", DATATYPE_STLSTRING, offsetof(S,b), 0, NULL, NULL},
{"c", DATATYPE_CHARP, offsetof(S,c), 0, NULL, NULL},
{"sA", DATATYPE_STRUCT, offsetof(S,sA), 2, "A", NULL},
{"vB", DATATYPE_CONTAINER, offsetof(S,vB), 0, "B", VectorFormaterFunc(vector<B>)},
};
StructMemberDescriptiong_struct_member_desc_X[] = {
// memberName, dataType, offset, plevel, structName, template_snprint
{"pS", DATATYPE_STRUCT, offsetof(X,pS), 1, "S", NULL},
{"pB", DATATYPE_STRUCT, offsetof(X,pB), 1, "B", NULL},
};
struct StructDesc {
const char* name;
const StructMemberDescription* stDesc;
const size_t n;
};
StructDesc g_self_def_struct[] = {
{"A", g_struct_member_desc_A, sizeof(g_struct_member_desc_A)/sizeof(StructMemberDescription)},
{"B", g_struct_member_desc_B, sizeof(g_struct_member_desc_B)/sizeof(StructMemberDescription)},
{"S", g_struct_member_desc_S, sizeof(g_struct_member_desc_S)/sizeof(StructMemberDescription)},
{"X", g_struct_member_desc_X, sizeof(g_struct_member_desc_X)/sizeof(StructMemberDescription)},
};
int reg_my_structs() {
size_t i = 0;
size_t n = sizeof(g_self_def_struct)/sizeof(StructDesc);
for(i=0; i<n; i++){
if(0!=register_struct(g_self_def_struct[i].name,
g_self_def_struct[i].stDesc,
g_self_def_struct[i].n)){
return -1;
}
}
return 0;
};
int main(int argc, char* argv[])
{
int ret = 0;
ret = reg_my_structs(); // 注冊結構體
ret = load_typing_format( “format.ini” ); // 定義各種類型的序列化格式
TYPINGHANDLER pvHandl = get_typing_handler(“S:1”); // 根據序列化名稱擷取序列化句柄
TYPINGHANDLER clkHandl = get_typing_handler(“X”);
TYPINGHANDLER shHandl = get_typing_handler(“shlog”);
A a = {1, "hello!"};
A* pA = &a;
double d1 = 3.1415f;
double d2 = 6.1853f;
B b1 = {&d1, "world!"};
B b2 = {&d2, "china!"};
vector<B> vb;
vb.push_back(b1);
vb.push_back(b2);
S sObj = {3, "4", "5", &pA, vb};
X xObj = {&sObj, a, &b2};
const size_t MAXLENTH = 1024;
char buff[MAXLENTH] = {0};
// 按照指定句柄序列化
ret = object_snprintf(buff, MAXLENTH, pvHandl, (void *)&sObj);
std::cout << buff << std::endl;
// std::cout <<3|4|5|1|hello!|3.141500,6.185300|world!;china!
ret = object_snprintf(buff, MAXLENTH, clkHandl, (void *)&xObj);
std::cout << buff << std::endl;
// std::cout << 3|4|5|1|hello!|3.141500,6.185300|world!;china!|china!
}
其中format.ini:
int:1=%x
char:1=%c
double:1=%f
string:1=%s
char*:1=%s
A:1=${i}|${s:1}
B:1=${d:1}
B:2=${str:1}
S:1=${a:1}|${b:1}|${c:1}|${sA:1}|$<vB:1,",">|$<vB:2,";">
X=${pS:1}|${pB:2}
(完)
完整代碼示例,點選這裡下載下傳,g++下編譯、測試通過。