概述
插件技术的目的是为了更好的扩展性.动态链接库是其中 一种实现方式.
这里主要论述几个问题.
1)linux上关于这些api的描述.看完linux上关于dlopen等函数的描述基本就可以写出简单的动态链接库使用.
2)关于c++使用动态链接库的一些问题和注意事项.
3)扩展,编译器的各选项,动态链接库和静态链接库.
linux api:dlopen,dlsym,dlerror,dlclose
摘自ubuntu kylin 14.04,内核3.13.0-32generic
#include<dlfcn.h>
void *dlopen(const char *filename,int flag);
char *dlerror(void);
void *dlsym(void *handle,const char *symbol);
int dlclose(void *handle);
链接时后面加 -ldl 选项.//这里注意,-ldl一定放在编译的最后,之前写程序时如果不加在最后会报错的.
描述:
这四个函数实现动态链接库加载接口.
dlerror():
返回可读字符串,返回dlopen,dlsym或dlclose的错误.dlerror只是保存最近一次调用时返回的错误信息.
dlopen():
dlopen通过filename加载动态链接库文件,返回void*类型的handle指向该动态链接库.如果filename是NULL,在返回handle指向的该主程序.如果filename包含"/",则解释为绝对或相对路径.否则,动态链接器按照以下方式搜索库.
//ELF(Executable and Linking Format)是一种对象文件的格式,用于定义不同类型的对象文件(Object files)中都放了什么东西、以及都以什么样的格式去放这些东西.//目前自己没涉及这么深的东西,只是简单的应用.可以暂时略过关于ELF的内容
1)(ELF ONLY)如果可执行文件包含DT_RPATH标签,但不包含DT_RPATH标签,则会在DT_RPATH标签列出的目录里面搜索.
2)如果环境变量LD_LIBRARY_PATH已定义且包含了冒号分割的目录列表,则搜索目录列表.
3)(ELF only)如果可执行程序包含DT_RUNPATH标签,则搜索标签中列出的目录
4)检查缓存文件/etc/ld.so.cache是否包含filename的库
5)按顺序搜索目录/lib/ 和/usr/lib
如果filename库依赖于其他共享库,这么库也会动态加载进来,按照上述搜索方式查找.
flag参数:
RTLD_LAZY:执行懒惰式绑定,只有当指向符号的代码执行时,才会解析符号.如果符号一直没有指向,则一直不会被解析.lazy binding只是针对函数引用时才生效,当加载库时,指向变量的引用经常立即受限制.
RTLD_NOW:如果该值指定,或环境变量LD_BIND_NOW是非空值,所有在库中未定义的符号在dlopen返回前都会被解析.如果执行未完成,则返回错误.
下面的参数可以通过or在flag中 指定.
RTLD_GLOBAL:动态库中定义的符号可被其后打开的其它库解析.
RTLD_LOCAL: 与RTLD_GLOBAL作用相反,动态库中定义的符号不能被其后打开的其它库重定位。如果没有指明是RTLD_GLOBAL还是RTLD_LOCAL,则缺省为RTLD_LOCAL。
RTLD_NODELETE(glibc2.2以后): 在dlclose()期间不卸载库,并且在以后使用dlopen()重新加载库时不初始化库中的静态变量。这个flag不是POSIX-2001标准。
RTLD_NOLOAD(glibc2.2以后): 不加载库。可用于测试库是否已加载(dlopen()返回NULL说明未加载,否则说明已加载),也可用于改变已加载库的flag,如:先前加载库的flag为RTLD_LOCAL,dlopen(RTLD_NOLOAD|RTLD_GLOBAL)后flag将变成RTLD_GLOBAL。这个flag不是POSIX-2001标准。
RTLD_DEEPBIND(glibc2.3.4以后):在搜索全局符号前先搜索库内的符号,避免同名符号的冲突。这个flag不是POSIX-2001标准。
如果filename是一个NULL指针,返回主程序的handle.当传递给dlsym函数调用时,handle将会查找主程序中的符号,查找程序启动时的所有共享库,以及查找dlopen加载的带RTLD_GLOBAL的库.
库中的外部引用使用库以及库依赖的列表以及其他之前带有RTLD_GLOBAL标示打开的库解析.如果可行执行文件连接时使用-rdynamic或--export-dynamic,则可执行文件中的全局符号可以用来解析动态加载的库.意味着动态加载的库,可以引用可执行文件中的符号.后文会再涉及这个-rdynamic参数.
如果相同的库,使用dlopen再次加载,相同的handle会返回.dl库维护handle的计数引用,一个动态库不会解除,直到dlclose函数被调用.如果存在_init()流程,只调用一次.但随后RTLD_NOW调用可能强制早些使用RTLD_LAZY加载的库进行符号解析.
如果dlopen失败,返回NULL.
dlsys()
dlclose()
废弃的符号_init()和_fini()
gcc扩展:dladdr()和dlvsym()
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <dlfcn.h>
int dladdr(void *addr, Dl_info *info);
void *dlvsym(void *handle, char *symbol, char *version);
The function dladdr() takes a function pointer and tries to resolve name and file where it is located. Information is stored in the Dl_info
structure:
typedef struct {
const char *dli_fname; /* Pathname of shared object that
contains address */
void *dli_fbase; /* Address at which shared object
is loaded */
const char *dli_sname; /* Name of nearest symbol with address
lower than addr */
void *dli_saddr; /* Exact address of symbol named
in dli_sname */
} Dl_info;
如果符号地址addr
没有找到,则dli_sname和dli_saddr设置为NULL.
EXAMPLE
Load the math library, and print the cosine of 2.0:
#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h>
int
main(int argc, char **argv)
{
void *handle;
double (*cosine)(double);
char *error;
handle = dlopen("libm.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "%s\n", dlerror());
exit(EXIT_FAILURE);
}
dlerror(); /* Clear any existing error */
<span style="color:#ff0000;">/* Writing: cosine = (double (*)(double)) dlsym(handle, "cos");
would seem more natural, but the C99 standard leaves
casting from "void *" to a function pointer undefined.
The assignment used below is the POSIX.1-2003 (Technical
Corrigendum 1) workaround; see the Rationale for the
POSIX specification of dlsym(). */
*(void **) (&cosine) = dlsym(handle, "cos");</span>
if ((error = dlerror()) != NULL) {
fprintf(stderr, "%s\n", error);
exit(EXIT_FAILURE);
}
printf("%f\n", (*cosine)(2.0));
dlclose(handle);
exit(EXIT_SUCCESS);
}
c plus plus 编写动态链接库
导致的原因
解决方案
//----------
//main.cpp:
//----------
#include "polygon.hpp"
#include <iostream>
#include <dlfcn.h>
int main() {
using std::cout;
using std::cerr;
// load the triangle library
void* triangle = dlopen("./triangle.so", RTLD_LAZY);
if (!triangle) {
cerr << "Cannot load library: " << dlerror() << '\n';
return 1;
}
// reset errors
dlerror();
// load the symbols
create_t* create_triangle = (create_t*) dlsym(triangle, "create");
const char* dlsym_error = dlerror();
if (dlsym_error) {
cerr << "Cannot load symbol create: " << dlsym_error << '\n';
return 1;
}
destroy_t* destroy_triangle = (destroy_t*) dlsym(triangle, "destroy");
dlsym_error = dlerror();
if (dlsym_error) {
cerr << "Cannot load symbol destroy: " << dlsym_error << '\n';
return 1;
}
// create an instance of the class
polygon* poly = create_triangle();
// use the class
poly->set_side_length(7);
cout << "The area is: " << poly->area() << '\n';
// destroy the class
destroy_triangle(poly);
// unload the triangle library
dlclose(triangle);
}
//----------
//polygon.hpp:
//----------
#ifndef POLYGON_HPP
#define POLYGON_HPP
class polygon {
protected:
double side_length_;
public:
polygon()
: side_length_(0) {}
virtual ~polygon() {}
void set_side_length(double side_length) {
side_length_ = side_length;
}
virtual double area() const = 0;
};
// the types of the class factories
typedef polygon* create_t();
typedef void destroy_t(polygon*);
#endif
//----------
//triangle.cpp:
//----------
#include "polygon.hpp"
#include <cmath>
class triangle : public polygon {
public:
virtual double area() const {
return side_length_ * side_length_ * sqrt(3) / 2;
}
};
// the class factories
extern "C" polygon* create() {
return new triangle;
}
extern "C" void destroy(polygon* p) {
delete p;
}
注意事项:
扩展
编译器选项
动态库和静态库
共享库可以被多个应用程序共享,实在程序运行的时候进行动态的加载。