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.h
和QWidget
頭文件
並將給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.lib
、laserrangerdll.h
、LaserRangerDLL_global.h
,ui_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.h
、LaserRangerDLL_global.h
,ui_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中的窗體。