高級語言的反射機制,簡單來說就是可以通過字符串型獲取對應的類或者函數。
-
基礎形式,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就會自動調用完成初始化。
思路提供給大家了,具體代碼靠大家自己實現咯