在我们日常的Linux下C/C++编程中,一般我们是通过直接在编译阶段就把动态库和静态库给打到了编译目标文件中,但是这种就需要我们编译时保证依赖的SO版本是正确的,不然编译出来的SO就会存在问题。当然了,Linux也存在一种动态加载SO的方式,这样编译目标So时是独立开来,并且也就有很好的隔离作用,不用关心依赖SO,只需要保证运行的时候So正确就行,这种就动态加载的方式,本文就简单的介绍动态加载的基本知识和使用示例。
2、接口定义动态加载的过程包含几个对于So操作的函数,dladdr、dlclose、dlerror、dlopen、dlsym、dlvsym这些。他们的头文件如下:
#include
接口函数:
//打开依赖的SO文件,文件目录和打开方式。 //在dlopen的()函数以指定模式打开指定的动态连接库文件,并返回一个句柄给调用进程。 //使用dlclose()来卸载打开的库。 void * dlopen(const char * filename,int flag); //返回打开So的错误信息 char * dlerror(void); //这个是将引用的函数指针和So中函数做映射关系 void * dlsym(void * handle,const char * symbol); //关闭So操作句柄,卸载打开的库。 int dlclose(void * handle);
注意:编译时候要加入 -ldl (指定dl库)
总体来说,上面的接口函数流程是这样的,函数dlopen()加载由以null结尾的字符串文件名命名的动态库文件,并且需要保证权限正确,才能打开成功,并且返回一个不透明的句柄。如果filename包含一个斜线(“/”),则它被解释为(相对或绝对)路径名。否则,动态链接器将按如下方式搜索库。之后就是打开的方式如下:
| 模式 | 解释 |
|---|---|
| RTLD_LAZY | 只有在执行引用它们的代码时才解析符号。如果符号从未被引用,那么它永远不会被解析.(懒惰绑定仅针对函数引用执行;对于变量的引用总是在加载该库时立即绑定) |
| RTLD_NOW | 如果指定了该值,或者环境变量LD_BIND_NOW设置为非空字符串,则在dlopen()返回之前解析库中的所有未定义符号。如果无法完成,则返回错误。以下值中的零个或多个值也可以在标志中进行或运算: |
| RTLD_GLOBAL | 由该库定义的符号将可用于随后加载的库的符号解析。RTLD_LOCAL这是RTLD_GLOBAL的反过来,如果既没有指定标志,也是默认值。此库中定义的符号不可用于解决随后加载的库中的引用。 |
| RTLD_NODELETE | (自glibc 2.2以来)在dlclose()期间不要卸载该库。因此,如果库稍后用dlopen()重新加载,则库的静态变量不会重新初始化 。该标志在POSIX.1-2001中未被指定。 |
| RTLD_NOLOAD | (自glibc 2.2以来)不要加载库。这可以用来测试库是否已经驻留(dlopen()如果不是,则返回NULL,如果库驻留则返回库)。该标志也可用于提升已加载库的标志。 |
例如,以前加载RTLD_LOCAL的库 可以使用RTLD_NOLOAD |重新打开 RTLD_GLOBAL。该标志在POSIX.1-2001中未被指定。RTLD_DEEPBIND(自glibc 2.3.4开始)将该库中符号的查找范围放在全局范围之前。这意味着一个独立的库将使用它自己的符号而不是全局符号,这些符号包含在已经加载的库中。该标志在POSIX.1-2001中未被指定。一般来说,我们使用到的就是RTLD_LAZY 这个比较多一些。
3、接口使用示例3.1 SO示例部分
下面是So的头文件&源文件:
#ifndef __TEST_DEMO_H_ #define __TEST_DEMO_H_ #include#include using namespace std; class ShowWraper; class ShowWraper { public: std::string mTag = "[ShowWrapper]"; public: ShowWraper(); ~ShowWraper(); void DisplayByNum(int num); void Display(const char * data); }; extern "C" ShowWraper* createShowWraper(void); extern "C" void destroyShowWraper(); #endif
#include "test_demo.h"
static ShowWraper *g_ShowWraper = NULL;
ShowWraper::ShowWraper() {
std::cout << mTag << __FUNCTION__ << std::endl;
}
ShowWraper::~ShowWraper() {
std::cout << mTag << __FUNCTION__ << std::endl;
}
void ShowWraper::Display(const char * data ) {
std::cout << mTag << __FUNCTION__ << std::endl;
std::cout << mTag << data << std::endl;
}
void ShowWraper::DisplayByNum(int num) {
for ( int i = 0;i < num; i++) {
std::cout << mTag << __FUNCTION__ << std::endl;
}
}
ShowWraper* createShowWraper(void) {
std::cout << __FUNCTION__ << std::endl;
if (g_ShowWraper == NULL) {
g_ShowWraper = new ShowWraper();
}
return g_ShowWraper;
}
void destroyShowWraper(void) {
std::cout << __FUNCTION__ << std::endl;
if (!g_ShowWraper) {
delete g_ShowWraper;
g_ShowWraper = NULL;
}
}
之后是编译脚本和CMakeLists.txt:
//
cmake_minimum_required(VERSION 3.5)
project(test_demo)
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
add_library(test_demo SHARED test_demo.cpp test_demo.h)
//
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR linux)
set(CROSS_COMPILING_ROOT /usr/bin)
SET(CMAKE_C_COMPILER gcc-4.8)
SET(CMAKE_CXX_COMPILER g++-4.8)
if(NOT DEFINED CROSS_COMPILING_ROOT)
set(CROSS_COMPILING_ROOT $ENV{CROSS_COMPILING_ROOT})
endif()
if(NOT DEFINED CROSS_COMPILING_ROOT)
message(FATAL_ERROR "Missing CROSS_COMPILING_ROOT")
elseif(NOT IS_DIRECTORY ${CROSS_COMPILING_ROOT})
message(FATAL_ERROR "CROSS_COMPILING_ROOT is not directory: ${CROSS_COMPILING_ROOT}")
endif()
set(CMAKE_FIND_ROOT_PATH ${CROSS_COMPILING_ROOT})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY BOTH)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
UNSET(CMAKE_CXX_FLASS CACHE)
UNSET(CMAKE_C_FLASS CACHE)
set(SYSTEM_DETAILS linux)
mkdir -p output
cd output
cmake -DCMAKE_VERBOSE_MAKEFILE=ON
../
make
这时候就会编译出一个目标的So(libtest_demo.so),其头文件就是test_demo.h。
3.2 调用示例部分
3.1介绍了我们造的So,现在我们开始写调用So的逻辑代码:
#include#include "test_demo.h" #include #include using namespace std; //将调用逻辑封装成一个类,由来来统一操作So class testSoAdater{ public: std::string mTag = "[testSoAdater]" ; testSoAdater(); ~testSoAdater(); private: bool isInit = false; void* libHandle = NULL; ShowWraper* (*testcreateShowWraper)(void) {NULL}; void (*testdestroyShowWraper)(void) {NULL}; public: void setShowWraper(); void soDisplay(const char *data); void soDisplayByNum(int num); public: class ShowWraper *mShowWraper = NULL; }; testSoAdater::testSoAdater() { if (isInit) { std::cout << mTag << "so have load" << std::endl; return; } if (!libHandle) { //确定打开方式,调用dlopen打开so的句柄,之后拿到句柄做下一步操作 libHandle = dlopen("libtest_demo.so",RTLD_NOW); if (libHandle == NULL) { std::cout << mTag << "unable to dlopen libtest_demo" << std::endl; return; } } typedef ShowWraper* (*create)(void); //定义相同的接口,打开So并获取对象。 testcreateShowWraper = (create)dlsym(libHandle, "createShowWraper"); if (testcreateShowWraper == NULL) { std::cout << mTag << "unable to find createShowWraper" << std::endl << dlerror(); return; } typedef void (*destroy)(void); //定义相同的接口,打开So并拿到释放对象接口。 testdestroyShowWraper = (destroy)dlsym(libHandle, "destroyShowWraper"); if (testdestroyShowWraper == NULL) { std::cout << mTag << "unable to find destroyShowWraper" << std::endl; return; } isInit = true; } testSoAdater::~testSoAdater() { if (libHandle) { //关闭打开的SO。 dlclose(libHandle); libHandle = NULL; } } void testSoAdater::setShowWraper() { std::cout << __FUNCTION__ << "t" << mShowWraper << std::endl; mShowWraper = testcreateShowWraper(); std::cout << __FUNCTION__<< "t" << mShowWraper << std::endl; } //传Char数据,并打印 void testSoAdater::soDisplay(const char * data) { if (mShowWraper) { mShowWraper->Display(data); } } //传BOOL数据,并打印 void testSoAdater::soDisplayByNum(int num) { if (mShowWraper) { mShowWraper->DisplayByNum(num); } } int main() { class testSoAdater *mTest_Demo = new testSoAdater(); const char * data = "this is a demo so test!"; mTest_Demo->setShowWraper(); mTest_Demo->soDisplay(data); mTest_Demo->soDisplayByNum(10); return 0; } //输出结果 //setShowWraper 0 //createShowWraper //[ShowWrapper]ShowWraper //setShowWraper 0x62c0f0 //[ShowWrapper]Display //[ShowWrapper]this is a demo so test! //[ShowWrapper]DisplayByNum //[ShowWrapper]DisplayByNum //[ShowWrapper]DisplayByNum //[ShowWrapper]DisplayByNum //[ShowWrapper]DisplayByNum //[ShowWrapper]DisplayByNum //[ShowWrapper]DisplayByNum //[ShowWrapper]DisplayByNum //[ShowWrapper]DisplayByNum //[ShowWrapper]DisplayByNum
整理的调用过程还算是明晰,主要注意点就是打开和关闭SO的时候。至于其他的问题就是遇到了再分析解决嘛。当然了,这个Demo写的也不是很好,大家看了也多多包涵。剩下的就是编译测试用例的时候,需要连接头文件,不然找不到对应的数据结构定义。
4、总结总的来说,这种方式是通过代码来加载SO,具有灵活性。可以通过加载的版本来控制我们加载的So是不是目标SO,并且单独更新So和可执行程序时,二者不不受影响,非常的银杏。



