C语言实现反射

高级语言的反射机制,简单来说就是可以通过字符串型获取对应的类或者函数。

  1. 基础形式,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中向数组添加成员的动作。
借助此机制,回调函数可以在任意文件申明,不需要修改其他文件。

虽然成员可以自动添加到段中,但由于分散在不同文件中定义,由于文件编译顺序不确定,无法直接得知段的起始结束地址,也就无法实现遍历。

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就会自动调用完成初始化。

思路提供给大家了,具体代码靠大家自己实现咯

猜你喜欢

转载自www.cnblogs.com/zl-yang/p/9030058.html