目录
一、C++动态库的生成
1.1 动态库源码
1.1.1 声明文件
// g++ myso.cpp -fPIC -shared -o myso.so
#include <cstdint>
#include <iostream>
typedef uint8_t uint8;
typedef uint16_t uint16;
typedef uint32_t uint32;
typedef int8_t int8;
typedef int16_t int16;
typedef int32_t int32;
extern "C" uint8 GetData(uint8* data, uint16 len);
extern "C" void RecvDataCallback(uint8* data, uint16 len);
1.1.2 实现文件
#include "myso.h"
uint8 GetData(uint8* data, uint16 len){
std::cout<<"I step into GetData()."<<std::endl;
return 1;
}
void RecvDataCallback(uint8* data, uint16 len){
std::cout<<"I step into RecvDataCallback()."<<std::endl;
}
1.2 编译命令
g++ -shared -fPIC myso.cpp -o libmyso.so
- g++:编译器的名称,它是GCC编译器的C++语言版本。
- -shared:这个选项告诉编译器生成一个动态链接库,而不是生成可执行文件。动态链接库是一种可以在运行时加载并链接的库,它可以被多个程序共享。
- -fPIC:这个选项告诉编译器生成位置无关代码(Position Independent Code, PIC),以便动态链接器可以将库加载到任何内存位置上。这对于动态链接库来说是必须的,因为它们可以在不同的程序和不同的地址空间中使用。
- myso.cpp:要编译的源文件的名称。
- -o libmyso.so:指定输出文件的名称,这里是libmyso.so。这个名称是动态链接库的约定命名方式,其中lib是前缀,.so是文件扩展名,表示它是一个共享对象(shared object)。
1.3 extern "C"作用
上述源程序中需重点注意myso.h
使用了extern "C"
修饰函数名称。
在每个C++程序(或库、目标文件)中,所有非静态(non-static)函数在二进制文件中都是以“符号(symbol)”形式出现的。这些符号都是唯一的字符串,从而把各个函数在程序、库、目标文件中区分开来。
在C中,符号名正是函数名:strcpy
函数的符号名就是“strcpy”
。这可能是因为两个非静态函数的名字一定各不相同的缘故。
而C++允许重载(不同的函数有相同的名字但不同的参数),并且有很多C所没有的特性──比如类、成员函数、异常说明──几乎不可能直接用函数名作符号名。为了解决这个问题,C++采用了所谓的name mangling。C++把函数名和一些信息(如参数数量和大小)杂糅在一起,改造成奇形怪状,只有编译器才懂的符号名。例如,被mangle后的foo可能看起来像foo@4%6^,或者,符号名里头甚至不包括“foo”。
C++标准(目前是[ISO14882])并没有定义名字必须如何被mangle,所以每个编译器都按自己的方式来进行name mangling。有些编译器甚至在不同版本间更换mangling算法(尤其是g++ 2.x和3.x)。即使搞清楚了编译器到底怎么进行mangling的,从而可以用dlsym
调用函数了,但可能仅仅限于手头的这个编译器而已,而无法在下一版编译器下工作。
用 extern "C"
声明的函数将使用函数名作符号名,该函数就可以像C函数一样被dlopen
动态加载。
二、动态库的调用
动态库的调用可分为两种方式:编译链接与动态加载。
2.1 编译链接
1.在自己的程序中包含动态库的头文件。
- 编译自己程序时链接到动态库
g++ main.cpp -o main -L. -lshared_memory
其中-l
表示编译时链接的动态库名称为shared_memory
,中间没有空格。
3.运行时添加动态库路径
export LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH:
其中$PWD
表示把当前路径添加到程序运行时寻找动态库的路径。
优点:可以在main中直接使用动态库的函数。
缺点:要求动态库提供方必须提供头文件且必须重新编译main程序。
2.2 动态加载
动态加载方式不需要重新编译main程序,也不需要在main中添加动态库的头文件。
Linux中通过添加头文件#include <dlfcn.h>
提供动态加载动态库的方法。
dlfcn
库提供dlopen
、dlsym
、dlerror
、dlclose
四个函数完成动态加载功能。
2.2.1 dlopen()函数
函数原型:void *dlopen(const char *libname,int flag);
功能描述:dlopen
必须在dlerror
,dlsym
和dlclose
之前调用,表示要将库装载到内存,准备使用。如果要装载的库依赖于其它库,必须首先装载依赖库。如果dlopen
操作失败,返回NULL值;如果库已经被装载过,则dlopen
会返回同样的句柄。
参数中的libname
一般是库的全路径,这样dlopen
会直接装载该文件;如果只是指定了库名称,在dlopen
会按照下面的机制去搜寻:
- 根据环境变量
LD_LIBRARY_PATH
查找 - 根据
/etc/ld.so.cache
查找 - 查找依次在
/lib
和/usr/lib
目录查找。
flag
参数表示处理未定义函数的方式,可以使用RTLD_LAZY
或RTLD_NOW
。
-
RTLD_LAZY
表示暂时不去处理未定义函数,先把库装载到内存,等用到没定义的函数再说; -
RTLD_NOW
表示马上检查是否存在未定义的函数,若存在,则dlopen
以失败告终。
2.2.2 dlsym()函数
函数原型:void *dlsym(void *handle,const char *symbol);
功能描述:在dlopen
之后,库被装载到内存。dlsym
可以获得指定函数(symbol)
在内存中的位置(指针)。如果找不到指定函数,则dlsym
会返回NULL
值。但判断函数是否存在最好的方法是使用dlerror
函数。
2.2.3 dlerror()函数
函数原型:char *dlerror(void);
功能描述:dlerror
可以获得最近一次dlopen
,dlsym
或dlclose
操作的错误信息,返回NULL
表示无错误。dlerror
在返回错误信息的同时,也会清除错误信息。
2.2.4 dlclose()函数
函数原型:int dlclose(void *);
功能描述:将已经装载的库句柄减一,如果句柄减至零,则该库会被卸载。如果存在析构函数,则在dlclose
之后,析构函数会被调用。
2.3 动态加载测试
将第一节中的动态库作为动态加载模块进行测试。
2.3.1 main.cpp
// g++ main.cpp -ldl -o main
#include <dlfcn.h>
#include <iostream>
typedef uint8_t uint8;
typedef uint16_t uint16;
typedef uint32_t uint32;
typedef int8_t int8;
typedef int16_t int16;
typedef int32_t int32;
using namespace std;
int main(void)
{
int get_flag = -1;
uint16 get_len =2;
uint8_t get_data[get_len] = {1, 2};
const char *libname = "./myso.so";
char *err = nullptr;
void *handle = dlopen(libname, RTLD_NOW);
if(!handle){
std::cout << "Load" << libname << "failed" << dlerror() << std::endl;
exit(1);
}
// 清除错误信息
dlerror();
// 调用mysolib动态库的函数
// 获取GetData函数指针
typedef uint8 (*pf_GetData) (uint8*, uint16);
pf_GetData GetData = (pf_GetData)dlsym(handle, "GetData");
// 获取RecvDataCallback函数指针
typedef void (*pf_RecvDataCallback) (uint8*, uint16);
pf_RecvDataCallback RecvDataCallback = (pf_RecvDataCallback)dlsym(handle, "RecvDataCallback");
//判断是否成功载入函数
err = dlerror();
if(err){
std::cout << "Can't find symbol function" << err << std::endl;
exit(1);
}
GetData(get_data, get_len);
RecvDataCallback(get_data, get_len);
//关闭库
dlclose(handle);
if(dlerror()){
std::cout << "Close" << libname << "failed" << dlerror() << std::endl;
exit(1);
}
}
2.3.2 编译与运行
编译:
g++ main.cpp -ldl -o main
其中-ldl
表示编译时链接到动态库dl
,即#include <dlfcn.h>
对应的库函数!
运行:
./main
输出:
I step into GetData().
I step into RecvDataCallback().
2.3.3 代码解释
main.cpp
通过typedef
定义了两个函数指针类型pf_GetData
和pf_RecvDataCallback
,然后通过这两个函数指针定义了两个函数指针类型的变量GetData
与RecvDataCallback
,然后通过dlsym()
函数从动态库中进行加载函数符号。
// 获取GetData函数指针
typedef uint8 (*pf_GetData) (uint8*, uint16);
pf_GetData GetData = (pf_GetData)dlsym(handle, "GetData");
// 获取RecvDataCallback函数指针
typedef void (*pf_RecvDataCallback) (uint8*, uint16);
pf_RecvDataCallback RecvDataCallback = (pf_RecvDataCallback)dlsym(handle, "RecvDataCallback");
三、动态库中的类如何处理
3.1 含类的动态库
3.1.1 声明文件
// base.h
#ifndef BASE_H
#define BASE_H
class BaseClass {
public:
virtual void foo() = 0;
};
#endif // BASE_H
3.1.2 实现文件
// base.cpp
#include <iostream>
#include "base.h"
using namespace std;
class DerivedClass : public BaseClass {
public:
void foo() final {
cout << "Derived class!" << endl;
}
};
extern "C" BaseClass* create_object() {
return new DerivedClass;
}
extern "C" void destroy_object(BaseClass* object) {
delete object;
}
3.1.3 编译
g++ base.cpp -fPIC -shared -o base.so
3.2 主程序
3.2.1 源码
// basemain.cpp
#include <dlfcn.h>
#include <iostream>
#include "base.h"
using namespace std;
#define CHECK() \
{ \
const char* dlsym_error = dlerror(); \
if (dlsym_error) { \
cerr << "Cannot load libray or symbol: " \
<< dlsym_error << '\n'; \
exit(1); \
} \
} \
int main() {
cout << "Start program..." << endl;
void* handle = dlopen("derived.so", RTLD_LAZY);
CHECK();
// "create" is a function pointer
// argv: ()
// return: BaseClass*
BaseClass* (*create)();
// "destroy" is a function pointer
// argv: BaseClass*
// return: void
void (*destroy)(BaseClass*);
// explicitly change void pointer to function pointer
create = (BaseClass* (*)())dlsym(handle, "create_object");
CHECK();
destroy = (void (*)(BaseClass*))dlsym(handle, "destroy_object");
CHECK();
BaseClass* derived_ptr = (BaseClass*)create();
derived_ptr->foo();
destroy(derived_ptr);
}
3.2.2 编译与运行
编译:
g++ basemain.cpp -ldl -o basemain
运行:
./basemain
3.3 输出
Start program...
Derived class!