12-4_Qt 5.9 C++开发指南_创建和使用共享库

1. 创建共享库

除了静态库,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。结构如下图所示:

在这里插入图片描述
mysharedlib_global.h文件的内容如下:

#ifndef MYSHAREDLIB_GLOBAL_H
#define MYSHAREDLIB_GLOBAL_H

#include <QtCore/qglobal.h>

#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    <QDialog>
#include    <QPen>
#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 文件,根据使用的编译器不同,生成的文件有些区别。

  • 若使用 MSVC 编译,编译后会生成 mySharedLib.dllmySharedLib.lib 两个文件,mySharedLib.dll 在运行应用程序时调用,mySharedLib.lib 在应用程序隐式调用动态链接库时使用
  • 若使用 MinGW 编译,编译后会生成 mySharedLib.dll 和 libmySharedLib.a 两个文件,mySharedLib.dll 在运行应用程序时调用,libmySharedLib.a 在应用程序隐式调用动态链接库时使用。

采用 debug 和release 不同模式生成的文件只能当应用程序在 debug 或release 模式下编译或调用。

由于动态库的代码和上篇静态库的基本一样,只是多了mysharedlib global.h文件,这里就不再赘述了。

2. 使用共享库

2.1 共享库的调用方式

调用动态链接库有两种形式,隐式链接 (implicit linking)调用和显式链接 (explicit linking)调用。

  • 隐式链接调用是在编译应用程序时,有动态库的 lib 文件(或a 文件)和 h 头文件,知道 DLL中有哪些接口类和函数,编译时就隐式地生成必要的链接信息,使用 DLL 中的类或函数时根据h头文件中的定义使用即可。应用程序运行时将自动加载 DLL 文件。隐式链接调用主要用于同一种编程软件(如 Qt)生成的代码的共享。—— 使用 lib 文件+DLL 文件

  • 显式链接调用是只有 DLL 文件,知道 DLL 里的函数原型,使用 QLibrary 类对象在应用程序里动态加载 DLL 文件,声明函数原型,并使用 DLL 里的函数。这种方式需要在应用程序里声明函数原型,并解析 DLL 里的函数。

2.2 隐式链接调用共享库:使用 .hlib 文件+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 的功能与上篇静态库 的程序完全一致,调用共享库里的类QWDialogPen也无需特别说明,只需包含头文件 qwdialogpen.h 即可。

注意:必须将动态链接库文件 mySharedLib.dll复制到可执行文件的目录下,程序才可以正常运行mySharedLib.dll 的 debug 和 release 版本必须分别用于应用程序的 debug 和 relcase 版本,否则运行时出错。

使用动态链接库可以很方便地扩展应用程序的功能,但是 DLL 文件需要随应用程序一起发布,并且编译 DLL和应用程序的 Qt 版本最好保持一致,否则需要考虑二进制兼容问题。关于二进制兼容可以参考:Qt源代码中二进制兼容及d、q指针的理解

2.2.1 shareLibUser.pro

#-------------------------------------------------
#
# Project created by QtCreator 2017-04-05T16:30:52
#
#-------------------------------------------------

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = shareLibUser
TEMPLATE = app

# The following define makes your compiler emit warnings if you use
# any feature of Qt which as been marked as deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if you use deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0


SOURCES += main.cpp\
        mainwindow.cpp

HEADERS  += mainwindow.h

FORMS    += mainwindow.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

2.2.2 mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#include    <QPen>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

private:
    QPen    mPen;

protected:
    void    paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();


private slots:
//    void on_pushButton_clicked();

    void on_action_Pen_triggered();

private:
    Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H

2.2.3 mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include    "qwdialogpen.h"
#include    <QPainter>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::paintEvent(QPaintEvent *event)
{//绘图
    Q_UNUSED(event);

    QPainter    painter(this);
    QRect rect(0,0,width(),height()); //viewport矩形区
    painter.setViewport(rect);//设置Viewport
    painter.setWindow(0,0,100,50); // 设置窗口大小,逻辑坐标
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setRenderHint(QPainter::TextAntialiasing);

    painter.setPen(mPen);
    painter.drawRect(10,10,80,30);
}

void MainWindow::on_action_Pen_triggered()
{//设置Pen
    bool    ok=false;
    QPen    pen;
    pen=QWDialogPen::getPen(mPen,ok);
    if (ok)
    {
        mPen=pen;
        this->repaint();
    }
}

2.3 显式链接调用共享库:使用 .hDLL 文件

显式链接调用共享库是在应用程序运行时才加载共享库文件,并调用库里的函数的。应用程序编译时无需共享库的任何文件,只需知道函数名和函数的原型即可。所以,这种方式可以调用其他语言编写的 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文件复制到应用程序目录下。(实际操作时发现即使复制进去,点击后会显示“程序异常结束”,后期实践时再测试)
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十月旧城

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值