高级语言的反射机制,简单来说就是可以通过字符串型获取对应的类或者函数。
-
基础形式,c语言结构化编程基础实现
1)声明typedef void (*callback)(void);
typedef struct {
const char *name;
callback fn;
}callback_t;void f0();
void f1();
callback_t callbacks[] = {
{ "cmd0", f0},
{"cmd1", f1},
}void f0()
{
}void f1()
{
}
2)调用
void do_callback(const char *name)
{
size_t i;
for (i = 0; i < sizeof(callbacks) / sizeof(callbacks[0]); i++) {
if (!strcmp(callbacks[i].name, name)) {
return callbacks[i].fn();
}
}
}
这种方式的不便之处在于,当需要映射的函数因分散在不同文件时,每增加一个新的映射都需要修改这个数组,以及头文件。
2 利用自定义段
gcc支持通过使用__attribute__((section())),将函数、变量放到指定的数据段中。
也就是说,可以让编译器帮我们完成1中向数组添加成员的动作。
借助此机制,回调函数可以在任意文件申明,不需要修改其他文件。
虽然成员可以自动添加到段中,但由于分散在不同文件中定义,由于文件编译顺序不确定,无法直接得知段的起始结束地址,也就无法实现遍历。
- 方式一
弃用gcc默认的分散加载文件,使用自己的分散加载文件,并在其中显示声明自定义段的起始结束地址。
声明时需要注意地址对齐,由于结构中保存有函数地址,因此需要按CPU字长对齐
如32bit cpu, 需要4字节对齐;64位需要8字节对齐。
分散加载文件可以通过ld --verbose 获取到
gcc的编译参数需要增加-T选项,声明使用指定的分散加载文件
如使用修改过的custom.lds文件
gcc xxx.c -o xxx -Tcustom.lds
方式一的缺陷在于,需要修改分散加载文件,需要修改Makefile,如果开发人员没有足够权威应该是不允许做这种操作的。
2)方式二
在结构中增加标志位,如:
typedef struct {
size_t magic1;
size_t magic2;
const char *name;
callback fn;
}callback_t;
#define CALLBACK __attribute__((section(".callback"), used))
并声明一个用于定位的flag结构。
CALLBACK callback_t flag;
#define MAGIC1 0xa0a0a0a0
#define MAGIC2 0x0a0a0a0a
// 以下两个变量用于记录段起始结束地址
callback_t *start = &flag;
callback_t *end = &flag;
定义一个初始化函数,用于确定段的起始结束地址
void callback_init();
该函数的原理是,
i) 从flag的地址往低地址遍历,如果magic值校验正确则更新起始地址,重复直到校验失败
while (1) {
callback_t *tmp = start - 1;
if (tmp->magic1 == MAGIC1 && tmp->magic2 == MAGIC2) {
start--;
} else {
break;
}
}
ii) 从flag的地址往高地址遍历,如果magic值校验正确则更新结束地址,重复直到校验失败
while (1) {
callback_t *tmp = end + 1;
if (tmp->magic1 == MAGIC1 && tmp->magic2 == MAGIC2) {
end++;
} else {
break;
}
}
到这里,自定义段的地址范围可以确定了。
void do_callback(const char *name)
{
size_t i;
callback_t *tmp = start;
while (tmp != end) {
// 注意跳过flag哦
if (tmp != &flag && !strcmp(tmp->name, name)) {
return tmp->fn();
}
tmp++;
}
}
然而还有一个问题,callback_init()需要手动调用。
抱歉,让我手动调用是不可能的,想也不要想。
一是懒
二是这么个东西放哪都觉着别扭,破坏代码结构,影响美感。
怎么解决?
很简单,还是利用gcc的扩展数据,给callback_init加上__attribute__((constructor))
这样,在main函数前,callback_init就会自动调用完成初始化。
思路提供给大家了,具体代码靠大家自己实现咯