天天看點

C/C++結構體序列化配置模闆化

用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++下編譯、測試通過。

繼續閱讀