1. 背景

在java中,jvm支持类的动态链接(Class.forName(String className)),用起来也很方便。动态链接是实现IOC(Inversion of Control,控制反转,更形象的称作依赖注入)的关键,用于将类间依赖从程序移到配置文件中。在框架不重新编译的情况下,替换被依赖的类。

在linux下,C++只能通过C的dl API实现动态链接,需要先将动态链接库编译成.so,然后再调用API进行链接。下面看一下如何实现动态链接C++类。和java实现IOC一样,链接类需要依赖于接口,先定义一下框架需要依赖的接口部分:

class UserInterface {
public:
virtual bool func() = 0;
};

动态链接库实现部分继承这个接口,进行实现。

class ConcretInterface: public UserInterface {
public:
virtual bool func() {
printf("A implementition of UserInterface");
}
};

2. dl API

先介绍dl API部分,这些函数需要连接dl动态库,-ldl:

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

用于打开.so,并进行链接,返回链接后的handle。flag表示链接标志:

RTLD_LAZY:延迟加载,在.so中第一次使用某个符号是再进行链接。只对函数符号有效,变量符号会被立即加载。

RTLD_NOW:在dlopen返回后,所有未定义的符号都需要进行链接,如果符号解析失败,dlopen会返回失败。

RTLD_GLOBAL:使用这个标志,则这个库中的符号可以被随后的动态库使用。

RTLD_LOCAL:与RTLD_GLOBAL相反,不可以以被随后动态库使用。

char *dlerror(void);

返回dl相关错误,如果从初始化或者上一次调用到目前为止没有错误则返回NULL。

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

返回符号名为symbol的符号的地址,需要用到dlopen返回的动态链接库的handle。需要使用dlerror来检验dlsym是否出错,不能进通过返回的指针是否为NULL判断是否出错,因为符号地址为NULL属于正常情况。

int dlclose(void *handle);

减少.so的引用计数,如果引用计数为0,则.so会被卸载。

3. 类加载实现

通过上面dl API可知,只能返回动态链接库中的符号的地址,这个符号函数符号或者是变量符号,不能像java一样直接返回一个类。而我们在动态链接类时,实际上只是需要这个类的实例对象,所以可以通过返回创建类对象的函数符号实现类的连接。由于在C++中,可以对new操作符重载,进行其他的一些操作,分配资源等,此时调用delete释放对象,会造成资源泄露。所以为了支持重载new操作符,还需要释放对象的函数。定义如下:

typedef bool (*user_interface_creator_t)(UserInterface **ui);
typedef void (*user_interface_destroyer_t)(UserInterface *ui);

上面定义了两个函数指针,分别是链接类的创建器和销毁器。动态链接库需要实现这两个函数,从而是框架可以创建和销毁对象。如果默认不支持new操作重载,可以去掉destroyer,简化接口。

还有一点比较重要,由于C++支持了命名空间、类以及重载等机制,所以符号的命名规则和C不同。而dl API属于C的部分,使用的C的命名规则,所以在实现上述两个函数时(也就是说需要通过动态链接的C++函数)需要使用C的符号命名规则。这需要实现extern 'C'实现,具体如下:

extern 'C' {

bool my_creator(UserInterface **ui)
{
if (NULL == ui) {
printf("invalid param");
return false;
} *ui = new (std::nothrow) ConcrectInterface;
return true;
} void my_destroyer(UserInterface *ui)
{
if (ui != NULL) {
delete ui;
}
} };

my_creator和my_destroyer就是动态链接库对接口的实现,可以通过函数名调用dlsym找到对应的地址,完成对象的创建和销毁。下面,介绍一下框架,也就是动态链接的部分。

#include <dlfcn.h>
#include <stdio.h> #include "user_interface.hpp" const char *so_path = "./user_impl.so";
const char *creat_func = "my_creator";
const char *destroy_func = "my_destroyer"; int main ()
{
// load .so
void *handle = dlopen(so_path, RTLD_LAZY);
if (NULL == handle) {
printf("failed to open %s, error: %s", so_path,
dlerror());
return 1;
} // reset errors
dlerror(); user_interface_creator_t creator =
(user_interface_creator_t)dlsym(handle, creat_func);
const char* err = dlerror();
if (err != NULL) {
printf("failed to load creator, error: %s", err);
return 1;
} // reset errors
dlerror(); user_interface_destroyer_t destroyer =
(user_interface_destroyer_t)dlsym(handle, destroy_func);
err = dlerror();
if (err != NULL) {
printf("failed to load destroyer, error: %s", err);
return 1;
} UserInterface *ui;
if (!(*creator)(&ui)) {
printf("failed to creat ui");
return 1;
}
ui->func(); (*destroyer)(ui); dlclose(handle);
}

这里需要注意每次调用dl API函数后需要调用dlerror清除error状态。

4. 坑

如果动态链接库和可执行程序依赖同一个库,比如依赖同一个日志库,在编译动态链接库和可执行程序时需要注意一下。要确保对于库的符号引用的解析会指向同一个符号。具体做法:

(1)编译.so时,不链接依赖的库,对于库的符号的引用都是undefined。
(2)可执行程序需要链接依赖的库,并且要指定链接选项-rdynamic,将全局符号可以用于动态链接库的符号引用的解析。

如果没有做到以上两点,会出现同一个符号引用会被解析到两个符号,若符号依赖的是一个变量的符号(比如符号本身就是一个变量,或者是函数符号,但依赖于某个变量),那么就会出现个别古怪、异常的现象。

对于.so依赖一个库,但是可执行程序没有依赖,那么编译.so时必须把这个库链接,否则会出现undefined symbol。

再介绍下调试动态链接库时用到的工具,因为经常会出现各种未定义的符号,所以需要查看.so或者可执行程序的符号信息,可以使用nm,比如,类型U或者类型T的符号。还可以使用c++filt将符号名解析成可读的字符串。

Linux下实现C++类的动态链接的更多相关文章

  1. linux 下 用phpmailer类smtp发送邮件始终不成功,提示:ERROR: Failed to co

    https://zhidao.baidu.com/question/509191264.html?fr=iks&word=PHPMailerSMTP+connect()+failed& ...

  2. Linux下Qt创建共享库与链接共享库详解

    随着程序写的逐渐变多,或多或少的我们都会使用别人写好的库:或者我们不想让别人看到我们的一些核心程序,可以将核心程序封装成库.本次和大家分享的是在Ubuntu下使用Qt生成共享库以及在Qt中链接共享库的 ...

  3. linux下的静态库和动态库

    一.linux下的静态库   静态库中的被调用的函数的代码会在编译时一起被复制到可执行文件中去的!!可执行文件在运行不需要静态库的存在!   二.linux下动态库的构建和使用 1.动态库的构建   ...

  4. Linux下的静态库与动态库的生成与调用

    静态库与动态库 静态函数库 这类库的名字一般是libxxx.a,xxx为库的名字.利用静态函数库编译成的文件比较大,因为整个函数库的所有数据都会被整合进目标代码中,他的优点就显而易见了,即编译后的执行 ...

  5. linux下添加动态链接库路径、动态库加载等方法

    linux下添加动态链接库路径的方法 2017年01月20日 10:08:17 阅读数:5596   Linux共享库路径配置 Linux下找不到共享库文件的典型现象为明明已经安装某个软包(如libn ...

  6. linux下的共享库(动态库)和静态库

    1.什么是库在windows平台和linux平台下都大量存在着库.本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行.由于windows和linux的本质不同,因此二者库的二进制是不 ...

  7. 【Maven】解决linux下安装maven update-alternative --display mvn链接层数过多

    问题描述: 今天首次在linux上安装配置maven,编辑/etc/profile 配置好环境变量之后 使用mvn -v 显示出mvn配置信息,此时以为可以顺利的构建maven项目. 结果中间构建时, ...

  8. Linux下的定时器类实现(select定时+线程)

    更好的计时器类实现:LINUX RTC机制实现计时器类(原创) 很多时候需要在LINUX下用到定时器,但像setitimer()和alarm()这样的定时器有时会和sleep()函数发生冲突,这样就给 ...

  9. (笔记)Linux下的静态库和动态库使用详解

    库从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行.库分静态库和动态库两种. 一.静态库和动态库的区别 1. 静态函数库 这类库的名字一般是libxxx.a:利用静态函数库编译成的文件比 ...

随机推荐

  1. L009-oldboy-mysql-dba-lesson09

    L009-oldboy-mysql-dba-lesson09 mysql> grant replication salve,replication client on *.* to ‘repl_ ...

  2. window Jconsole链接到CenOS 监控Tomcat

    前提条件 windows环境已安装JDK CentOS 环境已安装Tomcat 进入目录 /usr/local/TC6_A/bin 目录下修改 catalina.sh vi catalina.sh 添 ...

  3. Cookie和Seesion的区别

    一.Cookie对象: 1.Cookie是由网络服务器发送出来,存在在浏览器上,它是个存储在浏览器目录中的文本文件.当浏览该cookie对应的站点时,cookie作为http头部文件的一部分在浏览器和 ...

  4. double array trie 插入结点总结

    双数组Trie树索引的可操作性研究.pdf 提示:任一状态点的移动,会影响其Trie树中父节点的base值的选择以及兄弟结点位置的变动,而兄弟结点的移动又须变更相应的子节点的check值. 设待插入的 ...

  5. jooml二次开发---添加文章组件

    在写一个joomla组件的时候需要手动添加excel表格,并把表格当做文章的内容添加到前台文章中, 开始不知道怎么下手,索性先把一个基本的组件写出来,在joomla网站上测试是可以访问这个组件的,在p ...

  6. 【BZOJ 2321】 [BeiJing2011集训]星器

    Description Magic Land上的时间又过了若干世纪…… 现在,人们谈论着一个传说:从前,他们的祖先来到了一个位于东方的岛屿,那里简直就是另外一个世界.善于分析与构造的Magic Lan ...

  7. Oracle的spool命令

    在控制台上使用spool 路径+文件名 命令可以将整个过程写入指定的文件中, 结束使用spool off 命令, 当执行spool off后文件中的内容才能看见; ed命令修改当前缓冲区的上一条命令;

  8. iOS 跳转到系统的设置界面-b

    在项目中,我们经常会碰到使用位置的需求.当用户设置app不允许使用位置的时候,最好的用户体验就是直接调转到系统的位置设置界面,进行设置. 本人已经测试,在5c iOS8.3系统 和 5s iOS7.1 ...

  9. 把Ubuntu打造成Mac Macbuntu

    Macbuntu:把 Ubuntu 一键打造成完整的 Mac 风格 23八 2010 # 作者:riku/ 本文采用CC BY-NC-SA 2.5协议授权,转载请注明本文链接. Macbuntu 是一 ...

  10. LocalStorage 本地存储

    首先自然是检测浏览器是否支持本地存储.在HTML5中,本地存储是一个window的属性,包括localStorage和sessionStorage,从名字应该可以很清楚的辨认二者的区别,前者是一直存在 ...