QT 动态链接库开发及动态调用

QT6.9
MinGW 13.1.1 64-bit
Qt Creator 17.0.0 (Community)

创建动态链接库项目BaseLibrary

BaseLibrary项目创建

打开Qt Creator新建项目,选择库,然后先择C++ Library:
image.png
名称输入BaseLibrary,目录设置为E:\Projects\Qt6.9\TestLibrary,这样的话项目的完整路径就是E:\Projects\Qt6.9\TestLibrary\BaseLibrary
image.png

选择CMake

image.png

修改一下文件名称

image.png

语言根据需求添加,然后是构建套件

image.png

然后完成项目创建

文件目录结构

E:\Projects\Qt6.9\TestLibrary\BaseLibrary
   ├─build
   │   └─Desktop_Qt_6_9_1_MinGW_64_bit-Debug
   ├─CMakeLists.txt
   ├─BaseLibrary_global.h
   ├─BaseLibrary.cpp
   └─BaseLibrary.h
shell

BaseLibrary代码编写

首先是BaseLibrary_global.h

/*
* @brief   :BaseLibrary_global.h
* @author  :Jiayi XU
* @date    :2025-07-11
*/

#ifndef BASELIBRARY_GLOBAL_H
#define BASELIBRARY_GLOBAL_H

#include <QtCore/qglobal.h>

#if defined(BASELIBRARY_LIBRARY)
#define BASELIBRARY_EXPORT Q_DECL_EXPORT
#else
#define BASELIBRARY_EXPORT Q_DECL_IMPORT
#endif

#endif // BASELIBRARY_GLOBAL_H
c++

其次是BaseLibrary.h

/*
* @brief   :BaseLibrary.h
* @author  :Jiayi XU
* @date    :2025-07-11
*/

#ifndef BASELIBRARY_H
#define BASELIBRARY_H

#include "BaseLibrary_global.h"
#include <QString>

class BASELIBRARY_EXPORT BaseLibrary
{
public:
    BaseLibrary();
    virtual QString className();
    virtual int operation(int a, int b);
    virtual void showMsg(QString msg);
};

extern "C" {
    BASELIBRARY_EXPORT BaseLibrary* getInstance();
    BASELIBRARY_EXPORT QString* welcome();
    BASELIBRARY_EXPORT int printCode();
}

#endif // BASELIBRARY_H
c++

然后是源文件:

/*
* @brief   :BaseLibrary.cpp
* @author  :Jiayi XU
* @date    :2025-07-11
*/

#include "BaseLibrary.h"
#include <QDebug>
#include <QMessageBox>

BaseLibrary::BaseLibrary() {}

QString BaseLibrary::className() {
    return "BaseLibrary";
}

int BaseLibrary::operation(int a, int b) {
    return a+b;
}

void BaseLibrary::showMsg(QString msg) {
    qDebug()<<msg;
    QMessageBox* msgBox = new QMessageBox;
    msgBox->setText(msg);
    msgBox->setWindowTitle("动态链接库-BaseLibrary");
    msgBox->show();
}

BASELIBRARY_EXPORT BaseLibrary* getInstance(){
    return new BaseLibrary;

}
BASELIBRARY_EXPORT QString* welcome(){
    return new QString("Hello World From BASELIBRARY!");
}

BASELIBRARY_EXPORT int printCode(){
    return 520;
}

c++

最后是CMake:

cmake_minimum_required(VERSION 3.16)

project(BaseLibrary LANGUAGES CXX)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Core)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Core)

add_library(BaseLibrary SHARED
  BaseLibrary_global.h
  BaseLibrary.cpp
  BaseLibrary.h
)

target_link_libraries(BaseLibrary PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Core)

target_compile_definitions(BaseLibrary PRIVATE BASELIBRARY_LIBRARY)

cmake

在这里我们有一个构造函数BaseLibrary,一个返回类名的函数className,一个可以用于操作两个输入的int变量的函数operation,以及一个用于显示信息的函数showMsg,这些函数的定义可以用来后续分辨在多个相同结构的类,实现我们Plugin的目的。

需要注意的是,这里面需要将要在导入后通过类对象来调用的函数声明为虚函数virtual,否则在后续导入时,如果类对象企图调用该函数,构建会报错:

error: undefined reference to `__imp__ZN11BaseLibrary9classNameEv'
shell

完成类的结构定义后,注意到后面还有extern "C"的声明内容,表示该内容用于导出,可供其他模块使用,例如,这里声明的几个函数getInstance,welcome以及printCode在打包好动态链接库后,调用该动态链接库的位置可以通过使用QLibrary对象的resolve函数来获取函数指针,例如:

typedef BaseLibrary* (*Instance)();
QLibrary * baseLibrary = new QLibrary("../lib/libBaseLibrary.dll");
Instance baseLibrary = (Instance) baseLibrary->resolve("getInstance");
BaseLibrary* baseInstance = base();
c++

这里就是通过QLibrary对象的resolve函数来获取函数指针,然后调用函数创建的对象。

然后点击构建,完成之后,我们找到刚才项目目录E:\Projects\Qt6.9\TestLibrary\BaseLibrary,会发现下面有一个build文件夹,进去之后会有个Desktop_Qt_6_9_1_MinGW_64_bit-Debug文件夹,然后进去之后就可以看到有一个libBaseLibrary.dll文件,这就是我们打包好的动态链接库,在别的项目中就可以使用了。

我们记住它的位置,待会需要进行拷贝工作。

主项目Main中动态调用

Main项目创建

打开Qt Creator新建项目,选择Application (Qt),然后先择Qt Widgets Application

image.png

名称输入Main,目录设置为E:\Projects\Qt6.9\TestLibrary,这样的话项目的完整路径就是E:\Projects\Qt6.9\TestLibrary\Main

image.png

选择CMake

image.png

创建主窗口文件,修改一下文件名称,根据需求选择是否创建Form (UI)文件

image.png

语言根据需求添加,然后是构建套件

image.png

然后完成项目创建,等创建好之后,将项目构建一下,完成之后会在E:\Projects\Qt6.9\TestLibrary\Main目录下有一个build文件夹

这个文件夹内有Desktop_Qt_6_9_1_MinGW_64_bit-Debug文件夹,记住这个文件夹。

关键文件拷贝

为了后续工作的进行,需要先将关键的文件进行拷贝操作。

主要涉及的文件是前面提到的BaseLibrary.hBaseLibrary_global.h以及生成的动态链接库文件libBaseLibrary.dll,它们的完整路径为:

E:\Projects\Qt6.9\TestLibrary\BaseLibrary\BaseLibrary.h
E:\Projects\Qt6.9\TestLibrary\BaseLibrary\BaseLibrary_global.h
E:\Projects\Qt6.9\TestLibrary\BaseLibrary\build\Desktop_Qt_6_9_1_MinGW_64_bit-Debug\libBaseLibrary.dll
shell

首先,我们在主项目目录E:\Projects\Qt6.9\TestLibrary\Main下创建文件夹include,然后将BaseLibrary.hBaseLibrary_global.h拷贝进去,那么拷贝后的完整文件路径为:

E:\Projects\Qt6.9\TestLibrary\Main\include\BaseLibrary.h
E:\Projects\Qt6.9\TestLibrary\Main\include\BaseLibrary_global.h
shell

然后我们在主项目的构建目录E:\Projects\Qt6.9\TestLibrary\Main\build\Desktop_Qt_6_9_1_MinGW_64_bit-Debug下创建文件夹lib,然后将libBaseLibrary.dll拷贝进去,那么拷贝后的完整文件路径为:

E:\Projects\Qt6.9\TestLibrary\Main\build\Desktop_Qt_6_9_1_MinGW_64_bit-Debug\lib\libBaseLibrary.dll
shell

完成以上操作之后,就可以开始Main代码编写了

文件目录结构

E:\Projects\Qt6.9\TestLibrary\Main
   ├─build
   │   └─Desktop_Qt_6_9_1_MinGW_64_bit-Debug
   │      └─lib
   │         └─libBaseLibrary.dll
   ├─include
   │   ├─BaseLibrary.h
   │   └─BaseLibrary_global.h
   ├─CMakeLists.txt
   ├─main.cpp
   ├─MainWindow.cpp
   └─MainWindow.h
shell

Main代码编写

因为主要是实现调用动态链接库的功能,因此我们不修改UI的文件,只需要在main函数里面使用一下即可,其他位置原理一致。因此案例里我们只需要修改两个文件即可。

首先是CMakeLists.txt,我们需要配置刚才在Main目录下面创建的include目录,通过include_directories实现,完整的代码如下:

cmake_minimum_required(VERSION 3.16)

project(Main VERSION 0.1 LANGUAGES CXX)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)


# 全局默认设定:可执行文件和 DLL 到 bin/, 静态库和导入库(.dll.a)到 lib/
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)


find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)

set(PROJECT_SOURCES
        main.cpp
        MainWindow.cpp
        MainWindow.h
)

include_directories(${PWD}/include)

if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
    qt_add_executable(Main
        MANUAL_FINALIZATION
        ${PROJECT_SOURCES}
    )
# Define target properties for Android with Qt 6 as:
#    set_property(TARGET Main APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
#                 ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
    if(ANDROID)
        add_library(Main SHARED
            ${PROJECT_SOURCES}
        )
# Define properties for Android with Qt 5 after find_package() calls as:
#    set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
    else()
        add_executable(Main
            ${PROJECT_SOURCES}
        )
    endif()
endif()

target_link_libraries(Main PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)

# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1.
# If you are developing for iOS or macOS you should consider setting an
# explicit, fixed bundle identifier manually though.
if(${QT_VERSION} VERSION_LESS 6.1.0)
  set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.Main)
endif()
set_target_properties(Main PROPERTIES
    ${BUNDLE_ID_OPTION}
    MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
    MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
    MACOSX_BUNDLE TRUE
    WIN32_EXECUTABLE TRUE
)

include(GNUInstallDirs)
install(TARGETS Main
    BUNDLE DESTINATION .
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

if(QT_VERSION_MAJOR EQUAL 6)
    qt_finalize_executable(Main)
endif()
cmake

这里面主要修改了两处,第一处为修改输出的文件目录:

# 全局默认设定:可执行文件和 DLL 到 bin/, 静态库和导入库(.dll.a)到 lib/
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
cmake

第二处为添加子目录:

include_directories(${PWD}/include)
cmake

${PWD}代表CMakeLists.txt所在的目录。

然后是main.cpp

/*
* @brief   :main.cpp
* @author  :Jiayi XU
* @date    :2025-07-11
*/

#include "MainWindow.h"

#include <QApplication>
#include <QLibrary>
#include <QMessageBox>
#include <QDebug>
#include "include/BaseLibrary.h"

typedef BaseLibrary* (*Instance)();
typedef QString* (*Welcome)();
typedef int (*PrintCode)();

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    QLibrary * baseLibraryDll = new QLibrary("../lib/libBaseLibrary.dll");
    if (baseLibraryDll->load()) {
        Instance getInstance = (Instance)baseLibraryDll->resolve("getInstance");
        BaseLibrary* baseLibrary = getInstance();
        qDebug()<<baseLibrary->className();
        baseLibrary->showMsg("JIAYI XU 调用的 BaseLibrary.dll");
        qDebug()<<baseLibrary->operation(101, 57);

        Welcome welcome = (Welcome)baseLibraryDll->resolve("welcome");
        qDebug() << welcome();

        PrintCode printCode = (PrintCode)baseLibraryDll->resolve("printCode");
        qDebug() << printCode();

    } else {
        QMessageBox* msgBox = new QMessageBox;
        msgBox->setText(QStringLiteral("动态库加载失败!"));
        msgBox->setWindowTitle(QStringLiteral("信息框"));
        msgBox->show();
    }

    return a.exec();
}
c++

然后构建,运行:

image.png

调用成功。

插件化

使用同样的方法,我们可以再将第一步创建动态链接库的方法重复一遍,并且保持类的结构完全一致,那么我们就可以实现类似于插件的功能:我们只需要按照同样的方法再主项目的构建文件夹下我们自己创建的lib文件夹下面把dll文件拷入,然后主程序扫描lib文件夹下的dll文件批量执行代码。

例如,我们按照创建BaseLibrary的方法,重新创建一个SubLibrary

为了使其和BaseLibrary具有相同的结构,我们可以直接将BaseLibrary下面的主程序代码拷贝过来简单修改:
SubLibrary_global.h

/*
* @brief   :SubLibrary_global.h
* @author  :Jiayi XU
* @date    :2025-07-11
*/

#ifndef SUBLIBRARY_GLOBAL_H
#define SUBLIBRARY_GLOBAL_H

#include <QtCore/qglobal.h>

#if defined(SUBLIBRARY_LIBRARY)
#define SUBLIBRARY_EXPORT Q_DECL_EXPORT
#else
#define SUBLIBRARY_EXPORT Q_DECL_IMPORT
#endif

#endif // SUBLIBRARY_GLOBAL_H

c++

SubLibrary.h

/*
* @brief   :SubLibrary.h
* @author  :Jiayi XU
* @date    :2025-07-11
*/

#ifndef SUBLIBRARY_H
#define SUBLIBRARY_H

#include "SubLibrary_global.h"

class SUBLIBRARY_EXPORT SubLibrary
{
public:
    SubLibrary();
    virtual QString className();
    virtual int operation(int a, int b);
    virtual void showMsg(QString msg);
};

extern "C" {
    SUBLIBRARY_EXPORT SubLibrary* getInstance();
    SUBLIBRARY_EXPORT QString* welcome();
    SUBLIBRARY_EXPORT int printCode();
};

#endif // SUBLIBRARY_H
c++

SubLibrary.cpp

/*
* @brief   :SubLibrary.cpp
* @author  :Jiayi XU
* @date    :2025-07-11
*/

#include "SubLibrary.h"


#include <QDebug>
#include <QMessageBox>

SubLibrary::SubLibrary() {}

QString SubLibrary::className() {
    return "SubLibrary";
}

int SubLibrary::operation(int a, int b) {
    return a*b;
}

void SubLibrary::showMsg(QString msg) {
    qDebug()<<msg;
    QMessageBox* msgBox = new QMessageBox;
    msgBox->setText(msg);
    msgBox->setWindowTitle("动态链接库-SubLibrary");
    msgBox->show();
}

SUBLIBRARY_EXPORT SubLibrary* getInstance(){
    return new SubLibrary;

}
SUBLIBRARY_EXPORT QString* welcome(){
    return new QString("Hello World From SUBLIBRARY!");
}

SUBLIBRARY_EXPORT int printCode(){
    return 520;
}
c++

CMakeLists.txt

cmake_minimum_required(VERSION 3.16)

project(SubLibrary LANGUAGES CXX)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Widgets)

add_library(SubLibrary SHARED
  SubLibrary_global.h
  SubLibrary.cpp
  SubLibrary.h
)

target_link_libraries(SubLibrary PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Widgets)

target_compile_definitions(SubLibrary PRIVATE SUBLIBRARY_LIBRARY)
cmake

然后进行构建即可

构建完成之后只需要拷贝dll到主项目即可,头文件不需要了,因为我们的SubLibrary和BaseLibrary的结构完全一致。
然后修改一下主项目的main函数尝试运行:只需要修改文件路径即可。

/*
* @brief   :main.cpp
* @author  :Jiayi XU
* @date    :2025-07-11
*/

#include "MainWindow.h"

#include <QApplication>
#include <QLibrary>
#include <QMessageBox>
#include <QDebug>
#include "include/BaseLibrary.h"

typedef BaseLibrary* (*Instance)();
typedef QString* (*Welcome)();
typedef int (*PrintCode)();

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    // QLibrary * baseLibraryDll = new QLibrary("../lib/libBaseLibrary.dll");
    QLibrary * baseLibraryDll = new QLibrary("../lib/libSubLibrary.dll");
    if (baseLibraryDll->load()) {
        Instance getInstance = (Instance)baseLibraryDll->resolve("getInstance");
        BaseLibrary* baseLibrary = getInstance();
        qDebug()<<baseLibrary->className();
        // baseLibrary->showMsg("JIAYI XU 调用的 BaseLibrary.dll");
        baseLibrary->showMsg("JIAYI XU 调用的 SubLibrary.dll");
        qDebug()<<baseLibrary->operation(101, 57);

        Welcome welcome = (Welcome)baseLibraryDll->resolve("welcome");
        qDebug() << welcome();

        PrintCode printCode = (PrintCode)baseLibraryDll->resolve("printCode");
        qDebug() << printCode();

    } else {
        QMessageBox* msgBox = new QMessageBox;
        msgBox->setText(QStringLiteral("动态库加载失败!"));
        msgBox->setWindowTitle(QStringLiteral("信息框"));
        msgBox->show();
    }

    return a.exec();
}
c++

运行结果:

image.png

那么这就实现了类似于插件的功能!

后续想要扩展,主程序不需要修改,只需要按照模板写插件即可。

打赏
  • 微信
  • 支付宝
评论
来发评论吧~
···

歌手: