在 QtCreator 中新建项目,选择 Library - C++库
共享库(动态链接库)
选用该类型,将生成动态链接库,linux
下为 *.so
,而在 Windows
下为 *.dll
。
静态链接库
选用该类型,将生成静态链接库,最终生成的库为 *.a
。
Qt Plugin
该类型与插件有关,这里暂且 PASS
。
ps:静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。编译之后程序文件大,但加载快,隔离性也好。动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。多个应用程序可以使用同一个动态库,启动多个应用程序的时候,只需要将动态库加载到内存一次即可 1。
新建项目,模版选择 Liabary
- C++库
,类型选择 共享库
。
Location
及 Kits
略,模块选择如下:
默认选中 QtCore
,若去除该模块,则 Qt
很多类型均无法使用,那只能使用 C/C++
的数据类型了。
若该库包含 GUI
,还需勾选 QtGui
。
创建完成后,得到以下文件:
这里有一个 testso_global.h
文件,存放的是一些宏定义
#include
#if defined(TESTSO_LIBRARY)
# define TESTSOSHARED_EXPORT Q_DECL_EXPORT
#else
# define TESTSOSHARED_EXPORT Q_DECL_IMPORT
#endif
若库中只构建了一个类,为了避免后面使用 so库
的时候麻烦,我们可以将上面该部分的宏定义及头文件增加到 testso.h
中,然后注释掉 #include "testso_global.h"
。
然后就可以把 testso_global.h
删除了。这样使用隐式链接的时候就只需要移植 testso.h
。
若库中存在多个类,还是留着比较方便。就是使用隐式链接的时候需要把 testso_global.h
捎上。
这里我在 testso
中添加了两个函数测试
testso.h
#ifndef TESTSO_H
#define TESTSO_H
#include "testso_global.h"
#include
#include
class TESTSOSHARED_EXPORT Testso
{
public:
Testso();
QString getName();
void testDebug();
};
#endif // TESTSO_H
testso.c
#include "testso.h"
Testso::Testso()
{
}
QString Testso::getName(){
QString re = "testso";
return re;
}
void Testso::testDebug(){
qDebug() << "Debug test success.";
}
只构建的话就会直接生成共享库,如果运行的话就是下面这个样子:
静态库的生成,操作上与动态库相同,不再赘述。
生成的文件如下:
可以发现相比动态库,少了一个 *_global.h
文件。
同样我们也给它增加一个用于测试的函数:
test_staticdll.h
#ifndef TEST_STATICDLL_H
#define TEST_STATICDLL_H
#include
class Test_staticdll
{
public:
Test_staticdll();
void test();
};
#endif // TEST_STATICDLL_H
test_staticdll.c
#include "test_staticdll.h"
Test_staticdll::Test_staticdll()
{
}
void Test_staticdll::test(){
qDebug() << "test Static dll is success.";
}
编译完成后,发现其生成了一个 *.a
文件
借助Qt工具,添加库
-> 外部库
-> 选择平台
-> 选择库文件
导入后在 *.pro
中会自动添加:
DISTFILES +=
unix:!macx: LIBS += -L$$PWD/../build-testso-Desktop_Qt_5_9_3_GCC_64bit-Debug/ -ltestso
INCLUDEPATH += $$PWD/../build-testso-Desktop_Qt_5_9_3_GCC_64bit-Debug
DEPENDPATH += $$PWD/../build-testso-Desktop_Qt_5_9_3_GCC_64bit-Debug
然后我们将 testso.h
复制到项目路径下。若 testso_global.h
有调用,则需将其一并复制。
使用demo
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
Testso *test = new Testso();
ui->label->setText(test->getName());
test->testDebug();
}
MainWindow::~MainWindow()
{
delete ui;
}
即不借助 Qt向导
,直接修改 *.pro
实现库的导入。
可以参照前面利用向导生成的 *.pro
补充知识:
-L
表示后面跟的是文件夹,工程会将这个路径加入库文件的搜索路径中-l
表示后面跟的是一个库文件的名字$$PWD
表示的是当前路径/..
表示返回上一路径即使用 Qlibrary
进行显式调用,该方法不需移植头文件,以下代码可供参考:
QLibrary mylibrary("/home/hsy/SW/Qt5.9.3/Project/build-testso-Desktop_Qt_5_9_3_GCC_64bit-Debug/testso");
if(!mylibrary.load()){
//加载so失败
qDebug() << "Load Testso.so is failed!";
qDebug() << mylibrary.errorString();
}
//声明函数指针
typedef QString (*Fun_getName)();
typedef void (*Fun_testDebug)();
//resolve得到库中函数地址
Fun_getName getName = (Fun_getName)mylibrary.resolve("_ZN6Testso7getNameEv");
Fun_testDebug testDebug = (Fun_testDebug)mylibrary.resolve("_ZN6Testso9testDebugEv");
if(nullptr == getName){
qDebug() << "Load fun() getName failed!";
}else{
ui->label->setText(getName());
}
if(nullptr == testDebug){
qDebug() << "Load fun() testDebug failed!";
}else{
testDebug();
}
//卸载库
mylibrary.unload();
常用的是以下方法:
通过构造函数传入文件名,这个文件名官方的建议是去除前缀及后缀的。例如我们在 Ubuntu
上生成的是 libtestso.so
。
我们传入 testso
即可,这个名字也被叫作 基名
,至于前缀和后缀 QLibrary
会根据系统给你尝试加上。所以使用基名的写法是 有利于跨平台移植
的。
若 未使用绝对路径,则 QLibrary
的规则是先尝试加上前缀后缀,再去搜索所有系统特定的库位置(Ubuntu下为/usr/lib)。
所以如果已将 so库
移至系统库中,可以这样写:
QLibrary mylibrary("testso");
当我们传入 绝对路径 时,QLibrary
会先去尝试加载该目录,如果找不到文件再根据不同的平台特定的文件前缀或后缀再次尝试。
此外,我们也可以在创建实例后使用 setFileName( )
显式的设置要加载的文件名 2。
在 Ubuntu
下操作我还遇到了一个坑,请看so文件
:
libtestso.so
是链接到 libtestso.so.1.0.0
的,若将 libtestso.so
复制到需要调用该库的项目中,会出现什么呢?
感情你以为人家是葫芦兄弟…
其实人家是影分身!
心情就和 Winodw
下拷贝了快捷方式,而且原文件还不移动了一样。
解决方法:
libtestso.so.1.0.0
,然后将文件名改为 libtestso.so
so文件
,然后拷贝该 so库
该函数用于 动态加载库文件,加载完成后,可通过 isLoaded( )
判断加载成功与否,当然通过 load( )
的返回值来判断也是可以的。
若加载失败,我们可以通过 errorString( )
获取错误详情。
加载后,库将保留在内存中,直到应用程序终止。我们可以尝试使用 unload( )
来卸载库,但是如果 QLibrary
的其他实例正在使用同一库,则调用将失败,并且仅当每个实例都调用了 unload( )
时才进行卸载 3。
QLibrary库
的典型用法是去解析一个库中的导出符号,并调用该符号表示的 C函数
。这被称为“显式链接” ,使用的就是 resolve( )
。
所以使用该函数必须将符号从库中导出为 C函数
,以便 resolve()
起作用。这意味如果使用 C ++编译器
编译该库,则必须使用 extern "C"
将该函数包装在一个块中 3。
那么如果 so库
中没有使用 extern "C"
呢,例如我…
我们知道 C++
之所以能实现 多态
,是因为其编译规则与 C
不同,C
编译生成的函数仅带 函数类型,例如 int_add
,而 C++
编译生成的函数还带上 参数类型,例如 int_add_int_int
。当然具体实现和编译器有关。
我的想法是先查看 so库
中的函数 4,Linux
下可使用的指令有:
个人比较喜欢 nm
,还可以使用 awk过滤
。
这里强烈建议在找不到函数的时候,使用该方法去查看 so库
中是否包含所需函数,以及其函数名是什么,毕竟这依赖于编译器,以防被坑。
Linux下动态库(.so)和静态库(.a) 的区别 ↩︎
使用QLibrary加载动态库 ↩︎
QLibrary Class ↩︎ ↩︎
Linux查看动态库.so导出函数列表 ↩︎