除了静态库,Qt 还可以创建共享库,也就是 Windows 平台上的动态链接库
。动态链接库项目编译后生成 DLL 文件,DLL 文件在 windows 平台上应用广泛。DLL 文件是在应用程序运行时加载的,不像静态库那样在编译期间就连编到应用程序里。若更新了 DLL 文件版本,只要接口未变,应用程序依然可以调用。
创建共享库项目,单击Qt Creator 的“File”->“New File or Project”菜单项,在 New File orProject 对话框中选择 Projects 组里的 Library,在右侧的具体类别中再选择 C++ Library,单击“Choose*·.”按钮后出现如下图 所示的向导对话框。
在此对话框的 Type 下拉列表框里选择 Shared Library,并给项目命名,例如 mySharedLib,再选择项目保存目录。单击“Next”按钮后选择编译器,下一步选择需要包含的 Qt 模块,再下一步是类定义页面,在其中输入类的名称,这里仍然输入类名称为QWDialogPen,再下一步结束即可。
由向导生成的 mySharedLib项目包含文件 mySharedLib.pro、qwdialogpen.h 和qwdialogpen.cpp。此外还有一个特殊的头文件 mysharedlib global.h。结构如下图所示:
#ifndef MYSHAREDLIB_GLOBAL_H
#define MYSHAREDLIB_GLOBAL_H
#include
#if defined(MYSHAREDLIB_LIBRARY)
# define MYSHAREDLIBSHARED_EXPORT Q_DECL_EXPORT
#else
# define MYSHAREDLIBSHARED_EXPORT Q_DECL_IMPORT
#endif
#endif // MYSHAREDLIB_GLOBAL_H
这里定义了符号MYSHAREDLIBSHARED_EXPORT用于替代Qt的宏Q_DECL_EXPORT或Q_DECL_IMPORT。
一个共享库导出给用户使用的类、符号、函数等都需要用宏Q_DECL_EXPORT来定义导出,一个使用共享库的应用程序需要通过Q_DECL_IMPORT导入共享库里的可用对象。
在mySharedLib.pro文件中增加了符号MYSHAREDLIB_LIBRARY的定义,下面是mySharedLib.pro文件的主要内容:
QT += widgets
TARGET = mySharedLib
TEMPLATE = lib
DEFINES += MYSHAREDLIB_LIBRARY
DEFINES += QT_DEPRECATED_WARNINGS
自动生成的 qwdialogpen.h 文件里的内容是对 QWDialogPen 类的定义,在类名称前使用了宏MYSHAREDLIBSHARED_EXPORT,定义QWDialogPen 为一个导出的类。
#ifndef QWDIALOGPEN_H
#define QWDIALOGPEN_H
#include
#include
#include "mysharedlib_global.h"
namespace Ui {
class QWDialogPen;
}
class MYSHAREDLIBSHARED_EXPORT QWDialogPen : public QDialog
{ //QPen属性设置对话框
Q_OBJECT
private:
QPen m_pen; //成员变量
public:
explicit QWDialogPen(QWidget *parent = 0);
~QWDialogPen();
void setPen(QPen pen); //设置QPen,用于对话框的界面显示
QPen getPen(); //获取对话框设置的QPen的属性
static QPen getPen(QPen iniPen, bool &ok); //静态函数
private slots:
void on_btnColor_clicked();
private:
Ui::QWDialogPen *ui;
};
#endif // QWDIALOGPEN_H
将 12.3 节静态库项目里的文件 qwdialogpen.h、qwdialogpen.cpp 和 qwdialogpen.ui 复制到本项目目录下,覆盖自动生成的初始文件,但是修改文件 qwdialogpen.h 里的类的定义,在类名称前增加MYSHAREDLIBSHARED_EXPORT 宏,并加入mysharedlib global.h 的包含语句。
项目的文件准备好之后就可以编译生成 DLL 文件,根据使用的编译器不同,生成的文件有些区别。
mySharedLib.dll 在运行应用程序时调用,mySharedLib.lib 在应用程序隐式调用动态链接库时使用
。采用 debug 和release 不同模式生成的文件只能当应用程序在 debug 或release 模式下编译或调用。
由于动态库的代码和上篇静态库的基本一样,只是多了mysharedlib global.h文件,这里就不再赘述了。
调用动态链接库有两种形式,隐式链接 (implicit linking)调用和显式链接 (explicit linking)调用。
隐式链接调用是在编译应用程序时,有动态库的 lib 文件(或a 文件)和 h 头文件,知道 DLL中有哪些接口类和函数,编译时就隐式地生成必要的链接信息,使用 DLL 中的类或函数时根据h头文件中的定义使用即可。应用程序运行时将自动加载 DLL 文件。隐式链接调用主要用于同一种编程软件(如 Qt)生成的代码的共享。
显式链接调用是只有 DLL 文件,知道 DLL 里的函数原型,使用 QLibrary 类对象在应用程序里动态加载 DLL 文件,声明函数原型,并使用 DLL 里的函数。这种方式需要在应用程序里声明函数原型,并解析 DLL 里的函数。
创建一个基于QMainWindow 的应用程序 shareLibUser,程序功能与 12.3 节的 LibUser 项目一样,将LibUser 项目的 mainwindow 相关3 个文件mainwindow.h、mainwindow.cpp 和mainwindow.ui复制到 shareLibUser 项目下,替换自动生成的文件。
在 shareLibUser 项目文件目录下新建一个 include 目录,将 mySharedLib 项目的两个头文件qwdialogpen.h 和 mysharedlib_global.h 复制到此目录下。若使用 MSVC 编译器,则将 release 版本的mySharedLib.lib 复制到此目录下,debug 版本的 mySharedLib.lib 更名为 mySharedLibd.lib 复制到此目录下;若使用 MinGW 编译器,则复制 release 版本的 libmySharedLib.a,debug 版本的libmySharedLib.a 更名为 libmySharedLibd.a 复制到此目录下。
为应用程序增加动态链接库,右键单击 shareLibUser 项目节点,在快捷菜单里单击“Add Library···”菜单项,在出现的向导对话框里首先选择添加的库类型为“Extermal Library”,在向导第二步设置导入的动态库文件(见下图)。
在上图中,选择项目 include 目录下的 mySharedLib.lib 文件或libmySharedLib.a 作为库文其他设置如上图所示。
完成后在shareLibUser.pro 文件中自动增加项目设置的语句如下:(目的也是为了下面的程序,也可以不通过UI操作,自己写相应的代码)
win32:CONFIG(release, debug|release): LIBS += -L$$PWD/include/ -lmySharedLib
else:win32:CONFIG(debug, debug|release): LIBS += -L$$PWD/include/ -lmySharedLibd
INCLUDEPATH += $$PWD/include
DEPENDPATH += $$PWD/include
项目编译时,会根据当前是 release 还是 debug 模式,自动添加相应的库文件。这里添加库文件只是使用了动态库的导出定义,而不是将库的实现代码连接到应用程序的可执行文件里。主窗体类 MainWindow 的功能与上篇静态库 的程序完全一致,调用共享库里的类OWDialogPen也无需特别说明,只需包含头文件 qwdialogpen.h 即可。
注意:必须将动态链接库文件 mySharedLib.dll复制到可执行文件的目录下,程序才可以正常运行mySharedLib.dll 的 debug 和 release 版本必须分别用于应用程序的 debug 和 relcase 版本,否则运行时出错。
使用动态链接库可以很方便地扩展应用程序的功能,但是 DLL 文件需要随应用程序一起发布,并且编译 DLL和应用程序的 Qt 版本最好保持一致,否则需要考虑二进制兼容问题。关于二进制兼容可以参考:Qt源代码中二进制兼容及d、q指针的理解
显式链接调用共享库是在应用程序运行时才加载共享库文件,并调用库里的函数的。应用程序编译时无需共享库的任何文件,只需知道函数名和函数的原型即可。所以,这种方式可以调用其他语言编写的 DLL 文件
,例如用 Delphi 生成的一个DLL 文件。
显式链接调用共享库是通过 QLibrary 类实现的。QLibrary 是与平台无关的,用于在运行时载入共享库,一个 QLibrary 对象只对一个共享库进行操作。
一般在 QLibrary 的构造函数中传递一个文件名,可以是带路径的绝对文件名,也可以是不带后缀的单独文件名。QLibrary 会根据运行的平台自动查找不同后缀的共享库文件,例如 Unix 上是.so”,Mac上是“.dylib”,Windows 上是“.dll”。
作为示例,用 Delphi编写一个 DLL 项目,生成一个 DelphiDLL.dll文件,这个文件里只有一个函数,函数的原型为:
function triple(N;integer):integer;
它会计算传递参数N的3倍值并返回。
在Qt Creator 里创建一个基于 QMainWindow 的应用程序DelphiDLLUser,设计一个简单的界面,运行时下图所示。单击按钮时将根据输入,调用动态链接库 DelphiDLL.d11里的triple()函数,计算结果并显示在输出编辑框里。
按钮的槽函数代码如下:
void MainWindow::on_pushButton_clicked()
{
QLibrary myLib("DelphiDLL");
if (myLib.isLoaded())
QMessageBox::information(this,"信息","DelphiDLL.DLL已经被载入,第1处");
typedef int (*FunDef)(int); //函数原定定义
FunDef myTriple = (FunDef) myLib.resolve("triple"); //解析DLL中的函数
int V=myTriple(ui->spinInput->value()); //调用函数
ui->spinOutput->setValue(V);
if (myLib.isLoaded())
QMessageBox::information(this,"信息","DelphiDLL.DLL已经被载入,第2处");
}
在定义QLibrary对象实例 myLib 时传递了共享库文件名“DelphiDLL”,这里不需要给出后缀名。DelphiDLL.dll 文件必须在应用程序同一目录、系统目录或可搜索目录下。
QLibrary 有几个函数用于 DLL文件的载入与卸载:
load()用于手动载入 DLL 文件到内存里,一般无需手工调用此函数,在DLL里的函数第一次被使用时 QLibrary 会自动调用此函数;
isLoaded()用于判断 DLL是否已经被载入内存;
unload()用于将DLL从内存中卸载。
一个动态链接库在内存里只能有一个实例,也就是即使有多处调用了这个动态链接库里的函数,它也只会被载入一次,如果不是所有的实例都使用 unload()卸载它,那么它会在应用程序退出时才卸载。
在槽函数on_pushButton_clicked()的代码里,有两处QMessageBox 显示信息。在运行应用程序,第一次单击按钮时,只有第 2 处信息框显示,说明声明了 QLibrary 对象后,动态链接库没有立即被载入内存;第二次单击按钮时,两处信息框会先后显示,说明动态链接库上次载入内存后还在内存里。
显式调用动态链接库里的函数,需要声明函数原型的类型,即:
typedef int (*FunDef)(int); //函数原定定义
然后使用QLibrary的resolve()函数解析需要调用的函数。
FunDef myTriple = (FunDef) myLib.resolve("triple"); //解析DLL中的函数
这样就定义了一个函数 myTriple,用于实现 DLL文件里的函数"triple"的功能,当然重新声明的函数名称可以和 DLL 里的函数名称完全相同。
如果 DelphiDLL.dll 文件没有复制到应用程序目录下,则编译和启动应用程序都不会出错,只有单击按钮调用 DLL 里的函数时才会出错。所以,要使应用程序正常运行,需要将 DelphiDLL.dIl文件复制到应用程序目录下。(实际操作时发现即使复制进去,点击后会显示“程序异常结束”,后期实践时再测试)