目录

X-MACRO 数据与代码序列化

概述

X-MACRO(X-宏)是一种可靠的维护代码或数据的并行列表技术,其保证这些并行列表以相同(匹配)顺序出现。它可应用于数组、枚举、结构体、列表,以及代码段生成等。X-MACRO 定义的是【汇总集合表】,当使用时可以从它【批量】取出某(几)类元素。

一、原理与语法

点击展开内容
1
2
3
4
5
6
7
8
9
typedef int (*PF)(void *);
const PF pfunc[] =
{
	func_play,
	func_pause,
	func_stop,
	func_play_next,
	func_play_prev
};

对于上面的数组,我们不希望使用pfunc[0]这种方式来访问,我们希望使用宏pfunc[PLAY]方式来访问,这样可以更直观明了。使用枚举enum {PLAY, PAUSE, STOP, PLAY_NEXT, PLAY_PREV}方式定义每个组员编号对应的宏,但是这样需要维护两个表(要匹配它们相同顺序相同个数),极容易出错。那么如何使用预编译为我们生成组员编号对应的宏呢?

语法如下:(点击展开)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//事先编排的宏与组员关系表(注:DEF_X() 还没有定义,后面使用时才定义-用完即时取消定义)
#define XCMD_PFUNC                       \
        DEF_X(PLAY     , func_play     ) \
        DEF_X(PAUSE    , func_pause    ) \
        DEF_X(STOP     , func_stop     ) \
        DEF_X(PLAY_NEXT, func_play_next) \
        DEF_X(PLAY_PREV, func_play_prev)

//利用预编译生成组员编号对应的宏(表):enum {PLAY, PAUSE, STOP, PLAY_NEXT, PLAY_PREV}
typedef enum
{
	#define DEF_X(n,p) n,
	XCMD_PFUNC
	#undef DEF_X
	XCMD_MAX
}tXCMD;

//利用预编译生成指针数组(表):pfunc[] = {func_play, func_pause, func_stop, func_play_next, func_play_prev}
typedef int (*PF)(void *);
const PF pfunc[] =
{
	#define DEF_X(n,p) p,
	XCMD_PFUNC
	#undef DEF_X
};

二、通讯的用法

点击展开内容

当我们设计一个播放器时,通过串口开放给第三方控制时,我们协议一般是定义为命令+参数,通过不同的命令来控制播放器执行不同的动作。对命令解码的写法演变如下:

1、常规写法:(点击展开)

这种写法代码结构单一、执行效率高(前提:命令连续),但可读性差、耦合性过高、难维护。
补充说明:此代码不完整,只是为了展示写法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
switch (cmd)
{
	case 0: //播放
		/*
		略(执行代码 或 调用控制函数)
		*/
		break;
	case 1: //暂停
		/*
		略(执行代码 或 调用控制函数)
		*/
		break;
	case 2: //停止
		/*
		略(执行代码 或 调用控制函数)
		*/
		break;
	case 3: //下一曲
		/*
		略(执行代码 或 调用控制函数)
		*/
		break;
	case 4: //上一曲
		/*
		略(执行代码 或 调用控制函数)
		*/
		break;
	default:
		break;
}
2、查表写法:(点击展开)

这种写法代码结构简单,可读性好、耦合低、容易维护、灵活、支持命令不连续,不过查表会对效率有一定损耗。
补充说明:此代码不完整,只是为了展示写法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#define XCMD_PLAY       0
#define XCMD_PAUSE      1
#define XCMD_STOP       2
#define XCMD_PLAY_NEXT  3
#define XCMD_PLAY_PREV  50

struct _tFunc
{
	unsigned char cmd;    //命令
	int (*pfunc)(void *); //对应执行函数(指针)
};
struct _tFunc pfunc[] = 
{	/*------平常只需维护此表-------*/
	{XCMD_PLAY     , func_play     },
	{XCMD_PAUSE    , func_pause    },
	{XCMD_STOP     , func_stop     },
	{XCMD_PLAY_NEXT, func_play_next},
	{XCMD_PLAY_PREV, func_play_prev}
}

void player_cmd(unsigned char cmd, void *p)
{
	int i;
	for (i=0; i<sizeof(pfunc)/sizeof(pfunc[0]); i++)
	{	//查表,对效率有一定损耗
		if ((pfunc[i].cmd == cmd)
		&&  (pfunc[i].pfunc != NULL))
		{
			(*pfunc[cmd])(p);
			return;
		}
	}
	printf("cmd(%d) invalid!\r\n", cmd);
}
3、X 宏写法:(点击展开)

这种写法代码结构简单、执行效率高,可读性好、耦合低、容易维护,但要求命令必须是连续的。
补充说明:此代码不完整,只是为了展示写法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <stdio.h>

//事先编排的宏与组员关系表:
        /*----------平常只需维护此表----------*/
#define XCMD_PFUNC                            \
        DEF_X(XCMD_PLAY     , func_play     ) \
        DEF_X(XCMD_PAUSE    , func_pause    ) \
        DEF_X(XCMD_STOP     , func_stop     ) \
        DEF_X(XCMD_PLAY_NEXT, func_play_next) \
        DEF_X(XCMD_PLAY_PREV, func_play_prev)

//利用预编译生成函数声明:
#define DEF_X(n,p) extern int p(void *);
XCMD_PFUNC
#undef DEF_X

//利用预编译生成组员编号对应的宏(表):
typedef enum
{
	#define DEF_X(n,p) n,
	XCMD_PFUNC
	#undef DEF_X
	XCMD_MAX
}tXCMD;

//利用预编译生成指针数组(表):
typedef int (*PF)(void *);
const PF pfunc[] =
{
	#define DEF_X(n,p) p,
	XCMD_PFUNC
	#undef DEF_X
};

//命令解码函数
int func_play(void *p)
{
	printf("%s() decode\r\n", __FUNCTION__);
}
int func_pause(void *p)
{
	printf("%s() decode\r\n", __FUNCTION__);
}
int func_stop(void *p)
{
	printf("%s() decode\r\n", __FUNCTION__);
}
int func_play_next(void *p)
{
	printf("%s() decode\r\n", __FUNCTION__);
}
int func_play_prev(void *p)
{
	printf("%s() decode\r\n", __FUNCTION__);
}

//应用与演示
void player_cmd(tXCMD xcmd, void *p)
{
//	xcmd -= 20; //偏移量移到0点位置
	if (xcmd < XCMD_MAX)
	{	//直接访问,高效
		(*pfunc[xcmd])(p);
	}
	else
	{
		printf("xcmd(%d) invalid!\r\n", xcmd);
	}
}
int main(int argc, char *argv[])
{
	player_cmd(XCMD_PLAY, (void *)0);
	player_cmd(XCMD_PAUSE, (void *)0);
	player_cmd(XCMD_PLAY_NEXT, (void *)0);
}

三、数据的重构

点击展开内容

在通讯应用中,我们往往使用字节对齐方式定义数据结构体。某特殊情况下,为了提高数据处理效率,我们会把以单字节对齐数据结构体转成默认的多字节对齐数据结构体。利用 X-Macros 实现数据重构,可方便以后代码维护。注意:对有嵌套的结构体可能不支持,但你可以增加宏参数指出特别处理,因为它的灵活性超乎你的想像!

语法如下:(点击展开)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#define XSTRUCT_DATA                 \
        DEF_XDATA(x, unsigned char)  \
        DEF_XDATA(y, unsigned short) \
        DEF_XDATA(z, int)

//多字节对齐---------------------------------------
typedef struct
{
	#define DEF_XDATA(var, type) type var;
	XSTRUCT_DATA
	#undef DEF_XDATA
}tXDATA1;

//单字节对齐---------------------------------------
#pragma pack(push,1) //(push)与(pop)要配对, 可以嵌套
typedef struct
{
	#define DEF_XDATA(var, type) type var;
	XSTRUCT_DATA
	#undef DEF_XDATA
}tXDATA2;
#pragma pack(pop)

//重构 tXDATA2 数据到 tXDATA1 结构体
void xdata2_to_xdata1(tXDATA1 *const pxdat1, const tXDATA2 *const pxdat2)
{
	#define DEF_XDATA(var, type)       \
	        memcpy(&(pxdat1->var), &(pxdat2->var), sizeof(pxdat1->var));
	XSTRUCT_DATA
	#undef DEF_XDATA
}

//重构 tXDATA1 数据到 buffer[] 缓冲
void xdata1_to_buf(unsigned char *buffer, const tXDATA1 *const pxdat1)
{
	#define DEF_XDATA(var, type)                                 \
	        memcpy(buffer, &(pxdat1->var), sizeof(pxdat1->var)); \
	        buffer += sizeof(pxdat1->var);
	XSTRUCT_DATA
	#undef DEF_XDATA
}

//重构 buffer[] 数据到 tXDATA1 缓冲
void buf_to_xdata1(tXDATA1 *const pxdat1, const unsigned char *buffer)
{
	#define DEF_XDATA(var, type)                                 \
	        memcpy(&(pxdat1->var), buffer, sizeof(pxdat1->var)); \
	        buffer += sizeof(pxdat1->var);
	XSTRUCT_DATA
	#undef DEF_XDATA
}

四、知识的扩展

点击展开内容

从上面的例子可以看到,宏定义每行都使用了续行符,正常情况是没有什么影响的,但当要想使用#if这些预编译判断语句时,这样就出现问题了(因为#if要求必须是单独占一行,不能与其它语句共用一行)。那么如何解决这个限制问题?用#include包含功能来解决!以播放器为例说明,它有两个版本,新版本才支持上一曲控制:

语法如下:(点击展开)
  1. 新建《xmacro.h》文件,在里面添加内容:
1
2
3
4
5
6
7
8
	//《xmacro.h》
	DEF_X(PLAY     , func_play     )
	DEF_X(PAUSE    , func_pause    )
	DEF_X(STOP     , func_stop     )
	DEF_X(PLAY_NEXT, func_play_next)
	#if (SWF_VER == 2)
	DEF_X(PLAY_PREV, func_play_prev)
	#endif
  1. 使用#include来解决续行符限制问题:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
typedef enum
{
	#define DEF_X(n,p) n,
	#include "xmacro.h"
	#undef DEF_X
	XCMD_MAX
}tXCMD;

typedef int (*PF)(void *);
const PF pfunc[] =
{
	#define DEF_X(n,p) p,
	#include "xmacro.h"
	#undef DEF_X
};

上面只是一些基本功能应用,其实还有很多就高级应用。X-MACRO 里面的内容你尽情利用,你可以用它来生成函数名称、变量声明等,它只是保证为你生成一段相同顺序的东西(数据、代码、声明等)。下面简单做了几个例子,更多应用等你发掘。

例子如下:(点击展开)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
//打印出所有函数名称
void print_finc(void)
{
	#define DEF_X(n,p) printf("int %s(void *)\n", #p);
	XCMD_PFUNC
	#undef DEF_X
}

//利用预编译生成函数声明:
#define DEF_X(n,p) extern int p(void *);
XCMD_PFUNC
#undef DEF_X

//生成另一组功能函数声明:
#define DEF_X(n,p) extern int callback_##p(void *);
XCMD_PFUNC
#undef DEF_X