首页 > 编程语言 >c动态加载c/c++ so并调用其中的函数或者子类实现

c动态加载c/c++ so并调用其中的函数或者子类实现

时间:2024-08-04 09:05:36浏览次数:13  
标签:__ int 子类 void c++ so https dlopen

  在不少服务器应用中,会采用插件化或者模块化的体系实现具体的业务功能,比如mysql支持插件化体系,nginx采用模块化体系。总得来说,很多时候,因为扩展性,系统会采用动态加载so的方式扩展业务功能,而主框架不需要每次新增功能就不得不重新编译,很多时候,对于二进制发行的应用来说,不可能这么做。

  最近抽空研究了下,Linux提供了一套dlXXX的API来动态装载库。

- dlopen,打开一个库,并为使用该库做些准备。

void *dlopen(const char *filename, int flag);

dlopen打开模式如下:

  RTLD_LAZY 暂缓决定,等有需要时再解出符号 
  RTLD_NOW 立即决定,返回前解除所有未决定的符号。


- dlsym,在打开的库中查找符号的值。

void *dlsym(void *handle, const char *symbol);

- dlclose,关闭库。
- dlerror,返回一个描述最后一次调用dlopen、dlsym,或dlclose的错误信息的字符串。

  C/C++语言用户需要包含头文件dlfcn.h(该头文件实际上是c语言编写的,不是c++,所以下面会提到,so中的函数需要增加链接指示extern "C",否则在加载so的时候,会提示找不到符号表Undefined symbols when loading shared library with dlopen())才能使用上述API。glibc还增加了两个POSIX标准中没有的API:
- dladdr,从函数指针解析符号名称和所在的文件。
- dlvsym,与dlsym类似,只是多了一个版本字符串参数。

  在Linux上,使用动态链接的主应用程序需要和库libdl.so一起链接,也就是使用选项-ldl。首先看个例子:

dynso.cpp

//申明结构体类型
typedef struct __test {
    int i;
    void(*echo_fun)(struct __test *p);
}Test_struct;

static void __printf(Test_struct *p) {
    printf("i = %dn", p->i);
}

//动态库申请一个全局变量空间
//这种 ".成员"的赋值方式为c99标准
static Test_struct config = {
    .i = 0,
    .echo_fun = __printf,
};


extern "C" 
{
    int dyn_so(char* dest)
    {
        strcat(dest, "abc");
        return 1;
    }

    int object_cpp();

    //申明注册函数原型
    void __register(Test_struct *p);

    //加载动态库的自动初始化函数
    void _init(void) {
        printf("init dynso.cpp\n");
        //调用主程序的注册函数
        __register(&config);
    }

}

  使用g++ -fpic -shared选项编译。因为__register只有声明,没有定义,因此正常编译的时候会报undefined reference to `__register(__test*)'。要解决这个问题,就要给链接器加上参数-E将主程序中所有全局符号放到动态符号表中即可, 由于生成可执行文件一般都是gcc直接生成, 因此可以使用gcc -Wl,-E来将-E参数传给ld来完成创建一个可以被动态链接的可执行文件,参见下面主程序的编译部分。TODO待解决

参考http://www.cppblog.com/markqian86/archive/2017/09/27/215269.html。

再看主程序:

#include <dlfcn.h>

//申明结构体
typedef struct __test {
    int i;
    void(*echo_fun)(struct __test *p);
}Test;

//供动态库使用的注册函数
void __register(Test *p) {
    p->i = 1;
    p->echo_fun(p);
}

int main(int argc, const char ** argv) { // 动态so加载 void *handle = dlopen("/root/projects/dynso/bin/x64/Debug/libdynso.so.1.0", RTLD_NOW); if (!handle) { printf("open libdynso error ,dlerror=%s\n", dlerror()); return -1; } // call func typedef int(*fnc_ptr)(char* dest); fnc_ptr v_fnc_ptr = (fnc_ptr)dlsym(handle, "dyn_so"); if (!v_fnc_ptr) { printf("not found dyn_so function ,dlerror= %s \n", dlerror()); dlclose(handle); return -1; } char dest[100] = {0}; int ret_code = v_fnc_ptr(dest); cout << "call dyn loaded so,result=" << dest << endl; }

  使用g++ -ldl  -rdynamic编译。

   -rdynamic类似于-g选项,只不过相比-g选项, -rdynamic 却是一个 连接选项 ,它将指示连接器把所有符号(而不仅仅只是程序已使用到的外部符号)都添加到动态符号表(即.dynsym表)里,以便那些通过 dlopen() 或 backtrace() (这一系列函数使用.dynsym表内符号)这样的函数使用。

  当一个库通过dlopen()动态打开或以共享库的形式打开时,如果_init在该库中存在且被输出出来,则_init函数(如果使用g++编译,需要使用extern "C"使得对外可见)会被调用。如果一个库通过dlclose()动态关闭或因为没有应用程序引用其符号而被卸载时,_fini函数会在库卸载前被调用。当使用你自己的_init和_fini函数时,需要注意不要与系统启动文件一起链接。可以使用GCC选项 -nostartfiles 做到这一点。

  上面是动态调用so中函数的方式,还有一种典型的用法是主框架定义了接口,具体的so实现特定的接口以扩展主程序的功能,也就是插件化体系。

动态模块包含c++ 11特性

g++ -std=c++11 -g -I./include -fPIC -shared -nostartfiles -o libdynso_cpp.so dynso.cpp
/tmp/ccoMSNmQ.o: In function `__static_initialization_and_destruction_0(int, int)':
/usr/include/c++/4.8.2/iostream:74: undefined reference to `__dso_handle'
/usr/bin/ld: /tmp/ccoMSNmQ.o: relocation R_X86_64_PC32 against undefined hidden symbol `__dso_handle' can not be used when making a shared object
/usr/bin/ld: final link failed: Bad value
collect2: error: ld returned 1 exit status
make: *** [all] Error 1

https://stackoverflow.com/questions/57957080/relocation-r-x86-64-pc32-against-undefined-hidden-symbol-dso-handle-can-not

https://blog.csdn.net/weixin_30882895/article/details/95461913

真正解决方法:在源文件前面加上 extern "C"{ void * __dso_handle = 0 ;}

如果不生效,那就去掉 -nostartfiles,原因待查,参见https://gcc.gnu.org/onlinedocs/gcc/gcc-command-options/options-for-linking.html。

ldd, nm,readelf,ld

如果包含c++ 14特性,还得加个编译选项-fno-use-cxa-atexit。如下:

g++ -std=c++14 -g -Wall -fno-use-cxa-atexit -I./include -fPIC -shared -nostartfiles -o libdiff_two_dir.so diff_two_dir.cpp

参考:

http://www.tuicool.com/articles/EvIzUn

https://www.cnblogs.com/pcdack/p/16041098.html c函数本身调用的代价

ld本身是不能基于c主程序链接c++ object文件的,原因可以参见:https://jingyan.baidu.com/article/3c343ff7e9f1840d377963ea.html。

https://www.jb51.net/article/101744.htm

https://www.cnblogs.com/Anker/p/3746802.html

https://blog.csdn.net/tgdzsjh/article/details/41695881

https://blog.csdn.net/haifengid/article/details/51732600

http://blog.sina.com.cn/s/blog_664ffc6b01014ctj.html(特别感谢,运行的时候报找不到符号表就是通过该帖子的提示解决)

http://pubs.opengroup.org/onlinepubs/009695399/functions/dlopen.html

https://linux.die.net/man/3/dladdr

https://blog.csdn.net/xqhrs232/article/details/51305421

http://www.itkeyword.com/doc/0427885252816876x572/c-undefined-symbols-when-loading-shared-library-with-dlopen(使用基类指针方式调用)

https://my.oschina.net/saly/blog/130920

https://www.169it.com/tech-qa-linux/article-11063969113097593705.html c++中_init未被调用

标签:__,int,子类,void,c++,so,https,dlopen
From: https://www.cnblogs.com/lightdb/p/9307765.html

相关文章

  • C++ //练习 15.31 已知s1、s2、s3和s4都是string,判断下面的表达式分别创建了什么样的
    C++Primer(第5版)练习15.31练习15.31已知s1、s2、s3和s4都是string,判断下面的表达式分别创建了什么样的对象:(a)Query(s1)|Query(s2)&~Query(s3);(b)Query(s1)|(Query(s2)&~Query(s3));(c)(Query(s1)&(Query(s2))|(Query(s3)&Query(s4)));......
  • C++ //练习 16.14 编写Screen类模板,用非类型参数定义Screen的高和宽。
    C++Primer(第5版)练习16.14练习16.14编写Screen类模板,用非类型参数定义Screen的高和宽。环境:LinuxUbuntu(云服务器)工具:vim 代码块template<intH,intW>classScreen{public:usingpos=string::size_type;Screen()=default;Screen(cha......
  • C++ //练习 16.16 将StrVec类(参见13.5节,第465页)重写为模板,命名为Vec。
    C++Primer(第5版)练习16.16练习16.16将StrVec类(参见13.5节,第465页)重写为模板,命名为Vec。环境:LinuxUbuntu(云服务器)工具:vim 代码块#include<iostream>#include<memory>#include<utility>usingnamespacestd;template<typenameT>classVec{ public:......
  • 【C++】多态 - 含3个案例
    目录一、多态分类二、多态区别三、多态基本语法四、多态原理五、案例1:计算机类六、纯虚函数和抽象类七、案例2:制作饮品八、虚析构和纯虚析构九、案例3:电脑组装需求分析及实现多态是C++面向对象三大特性之一一、多态分类①静态多态:函数重载、运算符重载、复用函......
  • C++__位运算符:异或运算符 ^
    目的:     了解异或运算符的定义、性质及用法。定义:    二元运算符,符号为^,与位与、位或不同的是,它在二进制中为相同为0,不同为1。而且它还满足这几种运算规则:        1、任何数^0都等于它本身;    2、两个相同的数异或结果为0;    ......
  • C++自定义接口类设计器之模板代码生成四
    关键代码QStringListmultis=templateStr.split('\n');boolstartConfig=false;boolstartVar=false;boolstartTemplate=false;for(constauto&line:multis){if(startConfig){if(line.trimmed().st......
  • c++__位运算符:位与运算符&
    目的:了解位与运算符并加深对它的运用定义:一种二元运算符,符号为&,运用于二进制数中,特性为有0为0。#include<iostream>usingnamespacestd;intmain(){inta=0b1010;//10intb=0b0110;//6//a&b=0b0010;2cout<<(a&b)<<endl;}应用:1、判断奇偶性......
  • C++实现静态链表
    #include<iostream>usingnamespacestd;//定义静态链表的最大容量constintMAX_SIZE=100;//节点类classNode{public:intdata;//节点存储的数据intnext;//节点指向下一个节点的索引(在数组中的位置)//默认构造函数Node():data(0......
  • c++三国杀
    废话不多说,直接上代码!!!#include<iostream>#include<time.h>#include<stdio.h>#include<stdlib.h>usingnamespacestd;structpai{intpaifu;inthuase;intyanse;intdianshu;intleixing;intchangdu;voidKanpai(){if(paifu==0||paifu==......
  • 【iOS】——NSOperation和NSOperationQueue学习总结
    NSOperation、NSOperationQueue简介NSOperation、NSOperationQueue是基于GCD更高一层的封装,完全面向对象。但是比GCD更简单易用、代码可读性也更高。NSOperation、NSOperationQueue的优点可添加完成的代码块,在操作完成后执行。添加操作之间的依赖关系,方便的控制......