Qt下开发及调用带界面的DLL

0.背景

由于某项目需要,在Qt下开发及调用带界面的DLL。由于中间折腾时间较长才搞定,在这记录一下。
本帖子中所用Qt版本为QtCreator 4.10.2.基于Qt5.13.2(MSVC 2017,32位)

1. Qt DLL开发

1.1 工程建立

新建工程,选择:Library->C++ Library在工程细节中Qt module中选择 Widgets,如下图所示:

生成的项目中文件列表如下:

其中,在LaerRangerDLL_globall.h中,定义了宏
# define LASERRANGERDLL_EXPORT Q_DECL_EXPORT
查看Q_DECL_EXPORT的定义可以看出# define Q_DECL_EXPORT __declspec(dllexport)
该定义即为dll导出符号的宏定义。

1.2 添加窗体

新建窗体,选择Qt->Qt 设计师界面类,如下图所示:

选Main Window,并添加到之前的项目:

在窗体中加入一个Label,并修改显示字符串为:LaserRangerDLL
然后修改LaserRangerDLL的头文件和源文件:
头文件中做如下修改:

头文件中添加ui_mainwindow.hQWidget头文件
并将给LaserRangerDLL类添加基类:QMainWindow,并修改其构造函数原型。
添加私有成员:Ui::MainWindow ui;
修改源文件如下:

在构造函数里添加ui.setupUi(this);

1.3 生成DLL

在项目上点击右键:构建,生成DLL和Lib。
则在工程对应的Debug(或Release,和构建配置有关)文件夹里生成LaserRangerDLL.dll和LaserRangerDLL.lib文件。

2. 调用DLL

DLL调用分为两种:隐式调用和显式调用。
其中,隐式调用是在编译时包含.lib文件和.h头文件,这两个文件中包含了动态库中导出的接口信息。然后,在运行时调用dll中封装的二进制代码。
显式调用只有.dll,在运行时通过代码显式的加载dll文件,声明函数原型,并使用dll中的接口。

2.1 隐式调用

以第1建立的动态库项目LaserRangerDLL为例,建立LaserRangerCaller项目,来调用生成的DLL。

在项目文件夹下建立include文件夹,并将生成的LaserRangerDLL.liblaserrangerdll.hLaserRangerDLL_global.hui_mainwindow.h拷贝进include文件夹。
注意:ui_mainwindow.h需要将dll的项目编译后,在build文件夹中找到
如下图所示:

然后,在LaserRangerCaller工程中头文件中加入include文件夹

添加完之后如下图所示:

然后再在项目上右击,依次"添加库"->"外部库",库文件定位到LaserRangerDLL.lib,链接选动态。注意,在Windows选项中去掉为debug版本添加‘d’作为后缀的勾选(该选项默认为选中)。
如下图所示:

先编译一遍LaserRangerCaller工程。
并将生成的dll文件拷贝进该工程的build\debug文件夹中。
然后在main.cpp中加入头文件引用和对象调用。

然后运行laserRangerCaller,出现如下窗口:

证明隐式调用成功。

2.2 显式调用

显式调用共享库通过QLibrary类实现,QLibrary类可以实现动态库中的导出函数加载,相关成员函数如下:
load():用于手动载入DLL文件到内存里,一般无需手动调用此函数,在DLL里的函数第一次被使用时QLibrary会自动调用从函数

isLoaded():用于判断DLL是否已经被载入内存

unload():用于将DLL从内存中卸载

resolve():解析DLL文件中的函数
这些函数的详细解析可以看Qt的帮助文档。
下面通过例程,对第1部分生成的dll进行显式调用。
新建一个Qt Widget工程LaserRangerLoader
并自动生成一个MainWindow窗体,在窗体中加入一个按钮, 命名成pushButton_dll_loader

  • 加入接口函数
    注意要通过显式调用的话,只能调用导出函数,所以在laserrangerdll工程中加入导出函数:
    在头文件中加入:
extern "C" {
    LASERRANGERDLL_EXPORT LaserRangerDLL* getLaserRangerDLLObj();

    LASERRANGERDLL_EXPORT const char* disp();

    LASERRANGERDLL_EXPORT int addInt(int a,int b);
}

注意,由于C++具有多态特性,可能会给函数名在编译时添加其他后缀,所以在此用extern "C"关键字,保证导出符号与定义函数名一致。
然后在源文件里也加入对应实现代码:

LaserRangerDLL* getLaserRangerDLLObj()
{
    return new LaserRangerDLL();
}

const char* disp()
{
    return "This is a test function for LaserRangerDLL";
}

int addInt(int a,int b)
{
    return a+b;
}

然后编译dll工程
在工程目录下建立include文件夹,并将laserrangerdll.hLaserRangerDLL_global.hui_mainwindow.h拷贝进include文件夹。
编译一下该工程,将动态库LaserRangerDLL.dll放入编译后的build目录的debug目录下。

  • 显式调用
    pushButton_dll_loader的槽函数中加入dll的加载和调用。
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QLibrary"
#include "QDebug"
#include "iostream"
#include "QMessageBox"
#include "./include/laserrangerdll.h"

typedef LaserRangerDLL* (*getLaserRangerDLLObj_fcn)();
typedef const char* (disp_fcn)();
typedef int (intAdd_fcn)(int,int);

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

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


void MainWindow::on_pushButton_dll_loader_clicked()
{
    QLibrary* laser_ranger_dll_lib = new QLibrary("LaserRangerDLL.dll");
    if(laser_ranger_dll_lib->load())
    {
        getLaserRangerDLLObj_fcn get_obj_fcn = (getLaserRangerDLLObj_fcn)
                laser_ranger_dll_lib->resolve("getLaserRangerDLLObj");
        LaserRangerDLL* laser_ranger_dll = get_obj_fcn();
        laser_ranger_dll->show();
    }
}

然后运行,点击按钮,则出现dll中的窗体。

posted @ 2021-10-12 22:51  SpyCoder  阅读(3020)  评论(0编辑  收藏  举报