参考:
《程序员的自我修养 ---- 链接,装载与库》
《C++ dlopen mini HOWTO》http://www.tldp.org/HOWTO/pdf/C++-dlopen.pdf
1,使用 c 生成动态链接库mylib.so的简单示例
声明文件mylib.h
-
#ifndef __MY_LIB_H__ #define __MY_LIB_H__ void foobar(int i); #endif /* __MY_LIB_H__ */
-
实现文件mylib.c
-
#include <stdio.h> #include "mylib.h" void foobar(int i) { printf("Printing from mylib.so %d\n", i); }
-
使用 gcc 将 mylib.c 编译成一个共享对象文件,如下命令:
[steven@sasd c++]$ gcc-fPIC -shared -o mylib.so mylib.c
[steven@sasd c++]$ ll
total 16
-rw-rw-r--. 1 steven steven 109 Apr 13 09:27 mylib.c
-rw-rw-r--. 1 steven steven 90 Apr 13 09:26 mylib.h
-rwxrwxr-x. 1 steven steven 6210 Apr 1309:28 mylib.so
-
注:
-
2这里的共享对象其实全称是动态共享对象文件(Dynamic Shared Objects,简写为DSO);
2-fPIC:地址无关代码(Position-Independent Code),该技术主要用于解决SO中对绝对地址的重定位问题;
-
2,动态链接.so
-
下面在我们的c++程序中链接并使用此.so文件,在C++中,是使用 linkage directive 方式指出任意非C++函数所用的语言,也就是用extern “C”来指示对应的声明是使用C来实现的,这样对于该声明中的文件的编译会采用对应的C编译器来编译处理,如下为使用示例:
-
// use_mylib.cpp #include <iostream> #include <string> using namespace std; #ifdef __cplusplus extern "C" { #endif #include "mylib.h" // for foobar() #ifdef __cplusplus } #endif int main() { foobar(100); return 0; }
-
编译及运行结果:
-
[steven@sasd c++]$ g++ -o use_mylibuse_mylib.cpp ./mylib.so
[steven@sasd c++]$ ./use_mylib
Printing from mylib.so 100
[steven@sasd c++]$
-
3,运行时动态加载.so
-
动态库的运行时加载时通过一系列由动态链接器(dynamic linker)提供的API来实现的,这几个API的实现都是在libdl.so中的,且相关的声明和常量定义在头文件<dlfcn.h>中。
-
dlopen():主要用来打开一个.so,并将其加载到进程的地址空间,完成初始化过程,原型如下:
-
void *dlopen(const char *filename, int flag);
-
filename:the dynamic library file named by the null-terminated string;对于路径设置为相对路径或绝对路径情形下,dlopen()是如何查找该动态库文件的,这个直接参考man;
-
flag:用于表示函数符号的解析方式,如:RTLD_LAZY表示使用延迟绑定,只有在函数第一次被使用时才会去绑定,即PLT机制;相对应的为RTLD_NOW表示当模块被加载时即完成所有的函数绑定工作,如果有任何未定义的符号引用的绑定工作无法完成,dlopen都直接返回错误;其他的flag标识可以直接参考man;
-
返回值是被加载的模块的句柄,该句柄会被其他的几个API使用到,如果加载失败,则返回NULL。
-
dlsym():通过此函数来找到所需要的符号,定义如下:
-
void *dlsym(void *handle, const char *symbol);
-
第一个参数handle就是dlopen返回的句柄,第二个参数symbol表示要查找的符号的名字,一个以‘\0’ 结尾的字符串;
如果dlsym找到了相应的符号,则返回该符号的值;如果没有找到,则返回NULL。
对于不同类型的符号,返回的指针意义不同:如果为函数,则为函数地址;如果为变量,则为变量的地址;如果为常量,则应该为常量的值;
-
dlerror():在调用dlopen,dlsym,dlclose之后,都可以通过dlerror()来判断上一次调用是否成功,dlerror的返回值为char*,如果返回NULL,则表示调用成功;否则返回对应的错误消息;函数原型声明如下:
-
char *dlerror(void);
-
dlclose():卸载一个已经加载的模块。
-
在系统内部会维持一个加载引用计数器,每次调用dlopen()来加载模块时,相应的计数器加1;每次调用dlclose()卸载某模块时,相应计数器减1。当计数器等于0时,该模块才会真正被卸载掉。
-
下面直接上示例:
-
// ex_dload_so.cpp #include <iostream> using namespace std; #include <dlfcn.h> int main() { // 为上面生成的.so文件的绝对路径 const char *MYLIB_PATH = "/home/steven/test/c++/mylib.so"; // 加载指定的 .so 文件 void *handle = dlopen(MYLIB_PATH, RTLD_NOW); if (handle == NULL) { cerr << "Open library " << MYLIB_PATH << " error: " << dlerror() << endl; return -1; } // 查找函数foobar,并返回函数指针 void (*fn_foobar)(int); fn_foobar = (void (*)(int))dlsym(handle, "foobar"); char *error = NULL; if ((error = dlerror()) != NULL) { cerr << "Symbol foobar not found: " << error << endl; dlclose(handle); return -2; } // 调用对应的foobar函数打印输出 fn_foobar(123); dlclose(handle); return 0; }
-
编译运行效果:
-
[steven@sasd c++]$ g++-ldl -o ex_dload_so ex_dload_so.cpp
[steven@sasd c++]$ ./ex_dload_so
Printing from mylib.so 123
[steven@sasdc++]$-
4,使用c++创建和使用含有class的.so,采用多态机制实现接口发布,示例摘自《C++ dlopen mini HOWTO》
-
1)生成动态链接库triangle.so文件
-
// polygon.h #ifndef __POLYGON_H__ #define __POLYGON_H__ class Polygon { protected: double m_side_len; public: Polygon() : m_side_len(0) {} virtual ~Polygon() {} void set_side_len(double side_len) { m_side_len = side_len; } virtual double area() const = 0; }; typedef Polygon* create_t(); typedef void destroy_t(Polygon*); #endif /* __POLYGON_H__ */
-
// triangle.cpp #include "polygon.h" #include <cmath> class Triangle: public Polygon { public: virtual double area() const { return m_side_len * m_side_len * sqrt(3) / 2; } }; extern "C" Polygon* create() { return new Triangle; } extern "C" void destroy(Polygon *p) { delete p; }
-
编译生成 triangle.so
-
[steven@sasd c++]$ g++ -fPIC -shared -o triangle.so triangle.cpp
-
2) 使用该动态链接库
-
// ex_dload_cppso.cpp #include <iostream> #include <string> #include <dlfcn.h> using namespace std; #include "polygon.h" int main() { // load triangle library void *triangle = dlopen("./triangle.so", RTLD_LAZY); if (!triangle) { cerr << "Can not load library: " << dlerror() << endl; return -1; } // reset errors dlerror(); // load the symbols "create" create_t *create_triangle = (create_t*) dlsym(triangle, "create"); const char *dlsym_err = dlerror(); if (dlsym_err) { cerr << "Can not load symbol create: " << dlsym_err << endl; return -2; } // load the symbols "destroy" destroy_t* destroy_triangle = (destroy_t*) dlsym(triangle, "destroy"); dlsym_err = dlerror(); if (dlsym_err) { cerr << "Can not load symbol destroy: " << dlsym_err << endl; return -2; } // create an instance of class <Trangle> Polygon *poly = create_triangle(); // set side length of <Triagle> poly->set_side_len(8); cout << "The area is " << poly->area() << endl; // destroy the class destroy_triangle(poly); // unload the triangle.so library dlclose(triangle); return 0; }
-
编译运行:
-
[steven@sasd c++]$ g++ -ldl -o ex_dload_cppso ex_dload_cppso.cpp
[steven@sasd c++]$ ./ex_dload_cppso
The area is 55.4256
[steven@sasd c++]$