API概念
要使用tpl,您需要知道调用API函数的顺序,以及格式字符串、数组和索引号的背景概念。
方法的顺序
创建tpl始终是第一步,释放它是最后一步。在此期间,您可以打包并转储tpl(如果您正在序列化数据),或者加载tpl映像并解压缩它(如果您正在反序列化数据)。
格式字符串
当使用tpl_map()创建tpl时,其数据类型表示为格式字符串。格式字符串中的每个字符都有一个特定类型的关联参数。例如,格式字符串和它的参数是这样传递给tpl_map的:
tpl_node *tn;
char c;
int i[10];
tn = tpl_map("ci#", &c, i, 10); /* ci# is our format string */
明确的尺寸
数据类型(如long和double)的大小因平台而异。必须记住这一点,因为大多数tpl格式字符都需要一个指向上面列出的特定大小类型的指针参数。您可以在程序中使用显式大小的类型,如int32_t(在inttypes.h中定义),如果您觉得这很有帮助的话。
双重的麻烦
不幸的是,没有标准的显式大小的浮点类型——例如,没有float64_t。如果您计划在您的平台上使用tpl的f格式字符序列化double,首先要确保您的double是64位的。其次,如果您计划在不同类型的CPU上反序列化它,请确保两个CPU使用相同的浮点表示,例如IEEE 754。
数组
数组有两种类型:定长数组和变长数组。直观地说,它们可以被看作是传统的C数组和链表。一般来说,尽可能使用固定长度的数组,必要时使用可变长度的数组。变长数组支持更复杂的数据类型,并逐个向程序提供或接收元素。
定长数组与变长数组
符号
固定长度数组表示为i#(简单类型后面跟着一个或多个#符号),但可变长度数组表示为a (i)。
元素处理
固定长度数组的所有元素都被一次打包或拆包。但是变长数组的元素是一个接一个地打包或拆包的。
数组长度
固定长度数组中的元素数量是在使用之前指定的——在打包任何数据之前。但是变长数组没有固定的元素计数。它们可以包含任意数量的元素。在解包可变长度数组时,将逐个解包,直到用尽为止。
元素类型
固定长度数组的元素可以是整型、字节型、双精度型、字符串型或结构型。(不包括格式字符BA)。固定长度的数组也可以像i##一样是多维的。变长数组可以包含简单或复杂的元素——例如,整型数组A(i),整型/双精度对数组A(if),甚至是嵌套数组A(A(if))。
在解释所有概念之前,先来看看这两种数组是如何使用的。我们把0到9的整数用两种方式打包。
将0-9打包为固定长度的数组
将0-9打包成一个变长数组
索引号
索引号在格式字符串中标识一个特定的变长数组。格式字符串中的每个A(…)都有自己的索引号。索引号从左到右从1开始分配。例子:
特殊索引号0
特殊索引号0指定不在A(…)内的所有格式字符。索引0指定(或不指定)的示例:
将索引号传递给tpl_pack和tpl_unpack,以指定要处理哪个变长数组(或者索引号为0的情况下的非数组)。
整数
上面的数组示例演示了如何打包整数。我们将在这里展示一些进一步的解包整数和处理多维数组的示例。同样的程序可以用来演示处理字节、16位短格式、32位或64位带符号和无符号整数,只需要更改数据类型和格式字符。
从固定长度的数组中拆包0-9
从可变长度数组中解包0-9
多维数组
多维整数矩阵可以像固定长度的数组一样打包和解包。
int xy[XDIM][YDIM];
...
tn = tpl_map("i##", xy, XDIM, YDIM);
tpl_pack(tn, 0);
这个对tpl_pack的调用打包了整个矩阵。
字符串
Tpl可以序列化C字符串。char和char[]使用了不同的格式,如下所述。让我们先看看char:
打包字符串
#include "tpl.h"
int main() {
tpl_node *tn;
char *s = "hello, world!";
tn = tpl_map("s", &s);
tpl_pack(tn,0); /* copies "hello, world!" into the tpl */
tpl_dump(tn,TPL_FILE,"string.tpl");
tpl_free(tn);
}
char*必须指向以空结束的字符串,或者是NULL指针。
当反序列化(解包)一个C字符串时,它的空间将被自动分配,但你负责释放它(除非它是NULL):
反序列化字符串
#include "tpl.h"
int main() {
tpl_node *tn;
char *s;
tn = tpl_map("s", &s);
tpl_load(tn,TPL_FILE,"string.tpl");
tpl_unpack(tn,0); /* allocates space, points s to "hello, world!" */
printf("unpacked %s\n", s);
free(s); /* our responsibility to free s */
tpl_free(tn);
}
Char * vs Char []
s格式字符仅用于char*类型。在上面的例子中,s是一个char *。如果它是一个字符 s[14],我们将使用c#格式字符来打包或解包它,作为一个固定长度的字符数组。(这将“就地”解包字符,而不是将其解包到动态分配的缓冲区中)。同样,c#描述的固定长度缓冲区不需要以空结束。
字符串数组
您可以在tpl中使用固定长度或可变长度的字符串数组。下面展示了一个包装固定长度的二维字符串数组的示例。
char *labels[2][3] = { {"one", "two", "three"},
{"eins", "zwei", "drei" } };
tpl_node *tn;
tn = tpl_map("s##", labels, 2, 3);
tpl_pack(tn,0);
tpl_dump(tn,TPL_FILE,filename);
tpl_free(tn);
之后,当解包这些字符串时,程序员必须记住在不再需要它们之后,逐个释放它们。
char *olabels[2][3];
int i,j;
tn = tpl_map("s##", olabels, 2, 3);
tpl_load(tn,TPL_FILE,filename);
tpl_unpack(tn,0);
tpl_free(tn);
for(i=0;i<2;i++) {
for(j=0;j<3;j++) {
printf("%s\n", olabels[i][j]);
free(olabels[i][j]);
}
}
二进制缓冲区
打包一个任意长度的二进制缓冲区(tpl格式字符B)利用了tpl_bin结构。您必须声明这个结构,并用要打包的二进制缓冲区的地址和长度填充它。
//序列化二进制缓冲区
#include "tpl.h"
#include <sys/time.h>
int main() {
tpl_node *tn;
tpl_bin tb;
/* we'll use a timeval as our guinea pig */
struct timeval tv;
gettimeofday(&tv,NULL);
tn = tpl_map( "B", &tb );
tb.sz = sizeof(struct timeval); /* size of buffer to pack */
tb.addr = &tv; /* address of buffer to pack */
tpl_pack( tn, 0 );
tpl_dump(tn, TPL_FILE, "bin.tpl");
tpl_free(tn);
}
当您解包二进制缓冲区时,tpl将自动分配它,并将用它的地址和长度填充tpl_bin结构。您负责最终释放缓冲区。
//反序列化二进制缓冲区
#include "tpl.h"
int main() {
tpl_node *tn;
tpl_bin tb;
tn = tpl_map( "B", &tb );
tpl_load( tn, TPL_FILE, "bin.tpl" );
tpl_unpack( tn, 0 );
tpl_free(tn);
printf("binary buffer of length %d at address %p\n", tb.sz, tb.addr);
free(tb.addr); /* our responsibility to free it */
}
结构
可以使用tpl打包和解包结构和结构数组。
//序列化结构数组
struct ci {
char c;
int i;
};
struct ci s = {'a', 1};
tn = tpl_map("S(ci)", &s); /* pass structure address */
tpl_pack(tn, 0);
tpl_dump(tn, TPL_FILE, "struct.tpl");
tpl_free(tn);
如图所示,省略括号内格式字符的单个参数。固定长度数组例外;当S(…)包含#字符时,需要其长度参数:tpl_map(“S(f#i)”, &s, 10);
当使用S(…)格式时,括号内允许的唯一字符是iujvcsfiu# $()。
结构数组
结构数组与简单数组相同。支持固定长度或可变长度的数组。
struct ci sa[100], one;
tn = tpl_map("S(ci)#", sa, 100); /* fixed-length array of 100 structures */
tn = tpl_map("A(S(ci))", &one); /* variable-length array (one at a time) */
固定长度数组和可变长度数组之间的区别将在数组一节中解释。
嵌套结构
当处理嵌套结构时,最外层的结构使用S格式字符,而内部的嵌套结构使用$ format。tpl_map只给最外层结构的地址。
struct inner_t {
char a;
}
struct outer_t {
char b;
struct inner_t i;
}
tpl_node *tn;
struct outer_t outer = {'b', {'a'}};
tn = tpl_map("S(c$(c))", &outer);
结构可以嵌套到任何级别。目前tpl不支持内部结构上的固定长度数组后缀。然而,最外层的结构可以有一个长度后缀,即使它包含一些嵌套结构。
链表
虽然tpl没有针对链表的特定数据类型,但这里将说明打包链表的技术。首先将列表元素描述为格式字符串,然后用a(…)包围它,将其描述为可变长度数组。然后,使用临时变量遍历每个列表元素,将其复制到临时变量并打包。
struct element {
char c;
int i;
struct element *next;
}
struct element *list, *i, tmp;
tpl_node *tn;
/* add some elements to list.. (not shown) */
tn = tpl_map("A(S(ci))", &tmp);
for(i = list; i != NULL; i=i->next) {
tmp = *i;
tpl_pack(tn, 1);
}
tpl_dump(tn,TPL_FILE,"list.tpl");
tpl_free(tn);
拆包也是类似的。for循环被替换为:
while( tpl_unpack(tn,1) > 0) {
struct element *newelt = malloc(sizeof(struct element));
*newelt = tmp;
add_to_list(list, newelt);
}
正如您所看到的,tpl不会立即恢复整个列表——一次只恢复一个元素。您需要手动链接这些元素。tpl的未来版本可能会支持指针切换,以使这更容易。