美文网首页
CPP技巧整理 —— 关于动态库中调用外部符号

CPP技巧整理 —— 关于动态库中调用外部符号

作者: DayDayUpppppp | 来源:发表于2022-12-31 13:57 被阅读0次

在开发中,经常有这样的场景,把一些功能打包为一个动态库,这个库作为工具人,可以被其他不同进程链接,然后把自己的功能提供给对应链接的目标程序。这也是动态库最常见的用法,如下所示:

// test_lib.cc
#include <stdio.h>
int do_something_in_libso(int num)
{
    printf("do_something_in_libso, num %d\n", num);
    return num;
}

// g++ -fPIC --shared -o test_lib.so test_lib.cc
// main.cc
#include <stdio.h>

int do_something_in_libso(int num);

int main()
{
    do_something_in_libso(100);
    return 0;
}

// g++ -o main main.cc ./test_lib.so
./main
do_something_in_libso, num 100

当然,动态库的调用还有另外一种操作,比如在运行时,使用dlopen去调用动态库里面的符号。比如,如下所示:

// main2.cc
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>

typedef int (*pfunc)(int);

int main(int argc, char *agv[])
{
    // 打开动态库
    void *handle = dlopen("./test_lib.so",  RTLD_NOW);
    if (handle)
    {
        // 查找动态库中的函数
        pfunc func = (pfunc) dlsym(handle, "_Z21do_something_in_libsoi");    // 不同的g++生成的符号也许有所不同,使用nm命令可以查看
        if (func)
        {
            int ret = func(100);
            printf("ret = %d \n", ret);
        }
        else
        {
            printf("dlsym failed !!! \n");
        }
        dlclose(handle);
    }
    else
    {
        printf("dlopen failed! \n");
    }
    return 0;
}

// g++ -ldl -rdynamic -o main2 main2.cc 
nm test_lib.so  | grep do_something_in_libso
0000000000001119 T _Z21do_something_in_libsoi

铺垫完毕,进入主题。如果在动态库中调用一个外部的符号,会怎么样呢? 这里就有一个潜在的要求,调用动态库的进程必须要实现这个符号,如果没实现的话,就会出现找不到符号报错。(如果是通过dlopen的方式去调用动态库也是一样的,如果没有实现这个符号,会提示dlopen failed!

// main.cc
#include <stdio.h>

int do_something_in_libso(int num);

// 实现这个符号的定义
int do_something_in_main(int num)
{
    printf("do_something_in_main, num %d\n", num);
    return num;
}

int main()
{
    do_something_in_libso(100);
    return 0;
}
// g++ -ldl -rdynamic -o main main.cc  ./test_lib.so

但是,在一些场景中,开发动态库的时候,并没有办法去控制这个动态库被使用在什么地方,什么场合,以及那些进程中。如果有些调用的进程忘记实现这个符号呢?或者对方根本不知道还需要使用前 需要实现一个这种的符号。那么,一种比较好的方式是,我们需要的是动态库里面提供一个默认的实现(或者是空的实现),如果链接动态库的外部进程实现了这个函数,那么会覆盖它。如果没有实现,那么调用默认的。对于,这种场景要怎么实现呢?

有几种办法可以解决这个问题:

  • 思路一:在动态库里面提供一个函数指针,函数指针指向默认的任务函数。外部可以显示的调用它,去修改指针,指向不同的任务函数。这也是一个sdk提供服务的常见套路,提供默认的实现,但是也允许sdk的使用方自定义去注册一些功能。
typedef int (*PFUNC)(int);

int do_something_in_main_default(int num)
{
    printf("do_something_in_main_default, num %d\n", num);
    return 0;
}

// 函数指针指向默认的函数
PFUNC p_default = do_something_in_main_default;

// 同时提供了修改函数指针的接口
int set_do_somethine_in_main(PFUNC p)
{
    p_default = p;
    return 0;
}

int do_something_in_libso(int num)
{
    printf("do_something_in_libso, num %d\n", num);

    // 调用外部符号
    if (p_default)
    {
        p_default(num);    // 根据函数指针,访问对应的函数
    }
    return num;
}
  • 思路二:利用弱符号的思路__attribute__((weak)) ,在动态库中定义弱符号,当在外部遇到了强符号,那么编译器会自动用强符号覆盖弱符号。那么就自动替换了。当在外部没有遇到其他强符号,那么编译器就认为弱符号是它唯一的定义。
#include <stdio.h>

__attribute__((weak)) int do_something_in_main(int num)
{
    printf("do_something_in_main_default, num %d\n", num);
    return 0;
}


int do_something_in_libso(int num)
{
    printf("do_something_in_libso, num %d\n", num);

    // 调用外部符号
    do_something_in_main(num);
    return num;
}

这里实现main.cc,在main中如果不定义int do_something_in_main(int num)符号,那么会自动用动态库中的
__attribute__((weak)) int do_something_in_main(int num)

#include <stdio.h>

int do_something_in_libso(int num);

int main()
{
    do_something_in_libso(100);
    return 0;
}

// g++ -ldl -rdynamic -o main main.cc  ./test_lib.so
./main                                           
do_something_in_libso, num 100
do_something_in_main_default, num 100

如果main中定义了,那么会自动调用main中的实现。程序实现的非常简洁,对使用方也没有什么负担。

#include <stdio.h>

int do_something_in_libso(int num);

int do_something_in_main(int num)    // 这里定义了自己的实现,会自动覆盖动态库中的实现
{
    printf("do_something_in_main, num %d\n", num);
    return num;
}

int main()
{
    do_something_in_libso(100);
    return 0;
}

// g++ -ldl -rdynamic -o main main.cc  ./test_lib.so

小结:
本文介绍了cpp开发中动态库中调用外部符号的一些技巧。

相关文章

网友评论

      本文标题:CPP技巧整理 —— 关于动态库中调用外部符号

      本文链接:https://www.haomeiwen.com/subject/qgktcdtx.html