C语言:链接器(linker)魔法:在单例设计的库中实现编译期回调函数注册

在写一个小小的C的库,设计上只需要单例运行(一个进程中只包含这个库的一个“对象”,不能产生多个实例),尝试了一种合理的设计。核心代码看起来是这样的:

库中的一个xxx.c:

int parse_host_msg_from_buffer(void *in, size_t length) {
    ...
    xxxx_event_callback();
    ...
    return 0;
}

/* dummy definitions to avoid symbol not found */
__attribute__((weak))
void xxxx_event_callback() {}

库的头文件xxx.h:

int parse_host_msg_from_buffer(void *in, size_t length);

void xxxx_event_callback(); // not necessary, just want to show the function signature

在项目的main.c中:

#include <stdio.h>
#include <xxx.h>

int main() {
    uint8_t buffer[64];
    size_t buf_size = sizeof(buffer);
    parse_host_msg_from_buffer(buffer, buf_size);
}

void xxxx_event_callback() {
    printf("xxxx_event_callback\n");
}

这就完成了。现在编译main.c,并链接到库上,并运行,就会发现,虽然虽然在main()里完全没有直接调用这个xxxx_event_callback(),但是里面的printf()被执行了。

这就是个weak symbol在链接库中的小trick。在main里定义的这个xxxx_event_callback()是个strong symbol,会把之前已经定义在库里边的那个同签名的定义给顶掉,所以库里边在调这个方法时,实际调用的是main.c里的版本。

如果不在链接库里用那个weak symbol,而是直接不定义的话,库编译的时候就会因为找不到xxxx_event_callback()的符号而不通过。(库和main.c实际是两个项目,分开编译后链接到一起)

基于这种方法,就可以搞出单例的回调函数,在编译期就可以注册进去,算是一个小应用。