动态链接
动态库的使用有两种方式,一种是显示链接(Explicit Linking),一种是隐示链接(Implicit Linking)。
隐式链接
隐式链接会在链接生成可执行程序时就确立依赖关系,在该程序启动时,操作系统自动会检查它依赖的动态库,并一一加载到该程序的内存空间,程序员就不需要操心什么时候加载动态库了。比如 VC 编译环境,链接时使用动态库对应的 .lib 文件(包含动态库的导出函数声明,但没有实际功能代码),在 .exe 程序运行前系统会检查依赖的 .dll。隐式链接是最为常见的,所有的编译环境默认都是采用隐式链接的方式使用动态库。
显式链接
动态链接库通常都有其导出函数列表, 告知其他可执行程序可以使用它的哪些函数。可执行程序使用这些导出函数有两种方式:一是在运行时使用主动加载动态库的函数,Linux 里比如用 dlopen 函数打开并加载动态库,Windows 里一般用 LoadLibrary 打开并加载动态库,只有当程序代码执行到这些函数时,其参数里的动态库才会被加载,这就是显式链接。显式链接方式是在运行时加载动态库,其程序启动时并不检查这些动态库是否存在。隐式链接是最为常见的,所有的编译环境默认都是采用隐式链接的方式使用动态库。
一种最直观的表现是,隐示链接的程序在程序启动是缺少动态库无法正常启动,隐示链接在程序启动时不依赖动态库
QLibrary
Qt提供QLibrary动态加载库,使用QLibrary可以在程序运行时加载动态链接库。一个QLibrary的实例作用于一个单一的共享库上。QLibrary提供了一种平台无关的方式访问库中的函数,当然内部是封装了上面所说的平台相关的LoadLibrary 、dlopen 等。QLibrary库的典型用法是去解析一个库中的导出符号,并调用该符号表示的C函数。
动态加载C函数
这个在Qt文档里就有例子,这里简单列举一下。
创建了动态库的工程
导出一个c函数,如
extern "C" {
CPPLIB_EXPORT int MyAdd(int a, int b);
}
cpp实现
int MyAdd(int a, int b)
{
return a+b;
}
需要注意的是使用C++编译器编译的C库需要extern "C",当然C++使用C库也要使用extern "C"
在调用工程中使用QLibrary动态加载
把动态库放到程序生成目录下,QLibrary会自动查找。如果传入的文件名具有绝对路径,那么会首先尝试加载该目录。如果该文件找不到,QLibrary会使用不同的平台特定的文件前缀或后缀再次尝试,比如Unix和Mac平台的"lib"前缀,Unix平台的".so"后缀,Mac平台的".dylib",Windows平台的".dll"。如果文件名不是绝对路径,QLibrary会修改搜索顺序,首先尝试系统特定的前缀和后缀,紧接着是指定的文件路径。
声明函数指针
using addFunc = int (*)(int, int);
调用
QLibrary lib("CppLib");
if(!lib.load()) {
qDebug() << __FUNCTION__ << "song" << "load err";
return;
}
auto func = (addFunc)lib.resolve("MyAdd");
auto val = func(1, 2);
qDebug() << __FUNCTION__ << "song" << val;
动态加载类
动态加载类也是通过导出C函数,不过该函数返回的是类的指针对象,通过指针对象使用类的接口
class CppLib
{
public:
virtual ~CppLib();
virtual int add(int a, int b) = 0;
};
class CPPLIB_EXPORT TestLib : public CppLib
{
public:
virtual ~TestLib();
int add(int a, int b) override;
};
extern "C" {
CPPLIB_EXPORT void *createCppLib();
}
cpp实现:
CppLib::~CppLib()
{
}
TestLib::~TestLib()
{
}
int TestLib::add(int a, int b)
{
return a+b;
}
void *createCppLib()
{
return new TestLib;
}
需要注意的是类必须使用继承的形式,函数导出的指针对象void *,基类为抽象类,接口为纯虚函数,继承自基类实现接口。使用方式大体相同,只不过拿到void *后需强转会相应的类对象
定义函数指针
using cppFunc = void *(*)();
QLibrary lib("CppLib");
if(!lib.load()) {
qDebug() << __FUNCTION__ << "song" << "load err";
return;
}
auto func = (cppFunc)lib.resolve("createCppLib");
TestLib *cppLibObj = static_cast<TestLib *>(func());
if(cppLibObj) {
qDebug() << __FUNCTION__ << "song" << cppLibObj->add(2,3);
}
这里需要包含头文件才能识别到TestLib类,这样就能使用TestLib的接口了,上面调用C函数不需要头文件也是能正常调用的
结语
本篇文章主要介绍QLibrary加载动态库的基本用法,其中加载C++类的用法Qt文档是没有介绍的,这里介绍其基本的用法,其中接口设计得比较简陋,接口可以使用工厂的方式根据不同的类型返回不同的指针对象;返回指针还可以使用智能指针,这样就能够不用管理指针的释放,实际项目中可根据情况进行改进。
demo地址:QtDemo/QLibraryTest at master · a137748099/QtDemo · GitHub