用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++下编译、测试通过。