正如之前的一些答案中所评论的,最好的选择是使用自定义链接器脚本(带有KEEP(*(SORT(.whatever.*)))
输入部分)。
Anyway, 无需修改链接描述文件即可完成(下面的工作示例代码),至少在一些带有 gcc 的平台上(在 xtensa 嵌入式设备和 cygwin 上测试)
假设:
- 我们希望尽可能避免使用 RAM(嵌入式)
- 我们不希望调用模块知道有关带有回调的模块的任何信息(它是一个库)
- 列表没有固定大小(库编译时大小未知)
- 我正在使用海湾合作委员会。该原理可能适用于其他编译器,但我没有测试过
- 此示例中的回调函数没有接收参数,但如果需要的话修改起来非常简单
怎么做:
- 我们需要链接器以某种方式在链接时分配一个指向函数的指针数组
- 由于我们不知道数组的大小,因此我们还需要链接器以某种方式标记数组的末尾
这是非常具体的,因为正确的方法是使用自定义链接器脚本,但如果我们在标准链接器脚本中找到始终“保留”和“排序”的部分,则不这样做恰好是可行的。
通常情况下,这对于.ctors.*
输入部分(标准要求 C++ 构造函数按函数名称顺序执行,并且在标准 ld 脚本中是这样实现的),因此我们可以稍微修改一下并尝试一下。
只是要考虑到它可能不适用于所有平台(我已经在 xtensa 嵌入式架构和 CygWIN 中测试过它,但这是一个黑客技巧,所以......)。
另外,当我们将指针放在构造函数部分时,我们需要使用一个字节的 RAM(对于整个程序)来在 C 运行时 init 期间跳过回调代码。
test.c:
注册一个名为的模块的库test
,并在某个时刻调用它的回调
#include "callback.h"
CALLBACK_LIST(test);
void do_something_and_call_the_callbacks(void) {
// ... doing something here ...
CALLBACKS(test);
// ... doing something else ...
}
呼叫我1.c:
客户端代码为模块注册两个回调test
。生成的函数没有名称(实际上它们确实有名称,但它被神奇地生成为在编译单元内是唯一的)
#include <stdio.h>
#include "callback.h"
CALLBACK(test) {
printf("%s: %s\n", __FILE__, __FUNCTION__);
}
CALLBACK(test) {
printf("%s: %s\n", __FILE__, __FUNCTION__);
}
void callme1(void) {} // stub to be called in the test sample to include the compilation unit. Not needed in real code...
呼叫我2.c:
客户端代码为模块注册另一个回调test
...
#include <stdio.h>
#include "callback.h"
CALLBACK(test) {
printf("%s: %s\n", __FILE__, __FUNCTION__);
}
void callme2(void) {} // stub to be called in the test sample to include the compilation unit. Not needed in real code...
回调.h:
还有魔法...
#ifndef __CALLBACK_H__
#define __CALLBACK_H__
#ifdef __cplusplus
extern "C" {
#endif
typedef void (* callback)(void);
int __attribute__((weak)) _callback_ctor_stub = 0;
#ifdef __cplusplus
}
#endif
#define _PASTE(a, b) a ## b
#define PASTE(a, b) _PASTE(a, b)
#define CALLBACK(module) \
static inline void PASTE(_ ## module ## _callback_, __LINE__)(void); \
static void PASTE(_ ## module ## _callback_ctor_, __LINE__)(void); \
static __attribute__((section(".ctors.callback." #module "$2"))) __attribute__((used)) const callback PASTE(__ ## module ## _callback_, __LINE__) = PASTE(_ ## module ## _callback_ctor_, __LINE__); \
static void PASTE(_ ## module ## _callback_ctor_, __LINE__)(void) { \
if(_callback_ctor_stub) PASTE(_ ## module ## _callback_, __LINE__)(); \
} \
inline void PASTE(_ ## module ## _callback_, __LINE__)(void)
#define CALLBACK_LIST(module) \
static __attribute__((section(".ctors.callback." #module "$1"))) const callback _ ## module ## _callbacks_start[0] = {}; \
static __attribute__((section(".ctors.callback." #module "$3"))) const callback _ ## module ## _callbacks_end[0] = {}
#define CALLBACKS(module) do { \
const callback *cb; \
_callback_ctor_stub = 1; \
for(cb = _ ## module ## _callbacks_start ; cb < _ ## module ## _callbacks_end ; cb++) (*cb)(); \
} while(0)
#endif
main.c:
如果你想尝试一下...这是一个独立程序的入口点(在 gcc-cygwin 上测试并工作)
void do_something_and_call_the_callbacks(void);
int main() {
do_something_and_call_the_callbacks();
}
output:
这是我的嵌入式设备中的(相关)输出。函数名称生成于callback.h
并且可以有重复项,因为函数是静态的
app/callme1.c: _test_callback_8
app/callme1.c: _test_callback_4
app/callme2.c: _test_callback_4
而在 CygWIN 中...
$ gcc -c -o callme1.o callme1.c
$ gcc -c -o callme2.o callme2.c
$ gcc -c -o test.o test.c
$ gcc -c -o main.o main.c
$ gcc -o testme test.o callme1.o callme2.o main.o
$ ./testme
callme1.c: _test_callback_4
callme1.c: _test_callback_8
callme2.c: _test_callback_4
链接器图:
这是链接器生成的映射文件的相关部分
*(SORT(.ctors.*))
.ctors.callback.test$1 0x4024f040 0x0 .build/testme.a(test.o)
.ctors.callback.test$2 0x4024f040 0x8 .build/testme.a(callme1.o)
.ctors.callback.test$2 0x4024f048 0x4 .build/testme.a(callme2.o)
.ctors.callback.test$3 0x4024f04c 0x0 .build/testme.a(test.o)