qt 动态加载类库

1.概要

1.1 记忆要点

1 关于代码

//动态加载类库
QLibrary library("libMyLib.dll");

// 定义函数指针类型
typedef int (*AddFunc)(int, int);
// 获取函数指针
AddFunc add = (AddFunc)library.resolve("add");

// 调用函数
int result = add(5, 10);

2 显示连接和隐式连接的差别

1.2 相关链接

总结:关于类库、针对c++-优快云博客

cmake的RKNN 工程分析和opencv相关等知识-优快云博客

opencv 简单实验-优快云博客

cmake概述,及其常用命令-优快云博客

 

2.实验

1.动态加载实验-使用QLibrary

1.代码

1.类库
1.工程结构

2.工程文件
cmake_minimum_required(VERSION 3.14)

project(MyLib 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)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core)

add_library(MyLib SHARED
  MyLib_global.h
  mylib.cpp
  mylib.h
)

target_link_libraries(MyLib PRIVATE Qt${QT_VERSION_MAJOR}::Core)

target_compile_definitions(MyLib PRIVATE MYLIB_LIBRARY)
3.文件
MyLib_global.h
#ifndef MYLIB_GLOBAL_H
#define MYLIB_GLOBAL_H

#include <QtCore/qglobal.h>

#if defined(MYLIB_LIBRARY)
#define MYLIB_EXPORT Q_DECL_EXPORT
#define MYLIBRARY_API __declspec(dllexport)
#else
#define MYLIB_EXPORT Q_DECL_IMPORT
#define MYLIBRARY_API __declspec(dllimport)
#endif 

#endif // MYLIB_GLOBAL_H
MyLib.h
#ifndef MYLIB_H
#define MYLIB_H

#include "MyLib_global.h"

class MYLIB_EXPORT MyLib
{
public:
    MyLib();
};

// 使用extern "C"来确保C++编译器不会对此函数进行名字修饰
extern "C" MYLIBRARY_API int add(int a, int b);

#endif // MYLIB_H
MyLib.app
#include "mylib.h"

MyLib::MyLib() {}

int add(int a, int b) {
    return a + b;
}
2.调用类库
1.工程结构

2.工程文件

要点,如果类库不在当前工程下,需要设置类库目录

cmake_minimum_required(VERSION 3.14)

project(MyLibC 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)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core)

add_executable(MyLibC
  main.cpp
)
target_link_libraries(MyLibC Qt${QT_VERSION_MAJOR}::Core)

include(GNUInstallDirs)
install(TARGETS MyLibC
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)
3.文件
#include <QCoreApplication>
#include <QLibrary>
#include <QDebug>

// 定义函数指针类型
typedef int (*AddFunc)(int, int);

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QLibrary library("libMyLib.dll");
    if (!library.load()) {
        qDebug() << "Cannot load library:" << library.errorString();
        return -1;
    }

    // 获取函数指针
    AddFunc add = (AddFunc)library.resolve("add");
    if (!add) {
        qDebug() << "Cannot find function 'add' in the library";
        return -1;
    }

    // 调用函数
    int result = add(5, 10);
    qDebug() << "The result is:" << result;

    // 卸载库
    library.unload();

    return a.exec();
}

2.运行结果

17:05:34: Debugging D:\projects\qts\MyLibC\build\Desktop_Qt_6_7_2_MinGW_64_bit-Debug\MyLibC.exe ...
The result is: 15

3.调查

1.qt 类库动态加载的方式有几种

在Qt中,类库(动态链接库,如DLL文件在Windows上,或.so文件在Linux/Unix上)的动态加载通常可以通过多种方式实现。以下是几种主要的动态加载方式:

1. 使用QLibrary

QLibrary类是Qt提供的一个用于加载动态库(DLL或.so文件)的类。通过调用QLibraryload()方法加载动态库,并使用resolve()方法获取动态库中导出的函数和符号。这种方式是Qt中加载动态库的一种标准且跨平台的方法。

示例代码:

#include <QLibrary>  
  
// 加载动态库  
QLibrary myLib("mylibrary.dll"); // 或在Linux上为 "libmylibrary.so"  
if (myLib.load()) {  
    // 动态库加载成功  
    // 获取导出函数  
    typedef int (*MyFunction)(int);  
    MyFunction myFunction = (MyFunction)myLib.resolve("myFunction");  
    if (myFunction) {  
        // 调用导出函数  
        int result = myFunction(42);  
    }  
    // 卸载动态库  
    myLib.unload();  
}

2. 使用QPluginLoader

如果你的动态库是使用Qt插件机制编写的,那么QPluginLoader类是一个更合适的选择。QPluginLoader专门用于加载Qt插件,它提供了比QLibrary更高级别的抽象,使得加载插件变得更加简单和直观。

示例代码:

#include <QPluginLoader>  
  
// 加载插件  
QPluginLoader pluginLoader("myplugin.dll"); // 或在Linux上为 "libmyplugin.so"  
QObject *plugin = pluginLoader.instance();  
if (plugin) {  
    // 插件加载成功  
    // 使用插件  
    // ...  
    // 卸载插件  
    pluginLoader.unload();  
}

3. 使用平台特定的API

Qt也支持使用平台特定的API来加载动态库,例如LoadLibrary(Windows)和dlopen(Unix/Linux)。这种方式允许你直接使用底层系统的API来加载动态库,但需要编写条件编译代码以支持不同平台。

示例代码(Windows和Linux):

#ifdef Q_OS_WIN  
#include <windows.h>  
  
// 加载动态库  
HMODULE myLib = LoadLibrary("mylibrary.dll");  
if (myLib) {  
    // 动态库加载成功  
    // 获取导出函数  
    typedef int (*MyFunction)(int);  
    MyFunction myFunction = (MyFunction)GetProcAddress(myLib, "myFunction");  
    if (myFunction) {  
        // 调用导出函数  
        int result = myFunction(42);  
    }  
    // 卸载动态库  
    FreeLibrary(myLib);  
}  
#endif  
  
#ifdef Q_OS_UNIX  
#include <dlfcn.h>  
  
// 加载动态库  
void *myLib = dlopen("libmylibrary.so", RTLD_LAZY);  
if (myLib) {  
    // 动态库加载成功  
    // 获取导出函数  
    typedef int (*MyFunction)(int);  
    MyFunction myFunction = (MyFunction)dlsym(myLib, "myFunction");  
    if (myFunction) {  
        // 调用导出函数  
        int result = myFunction(42);  
    }  
    // 卸载动态库  
    dlclose(myLib);  
}  
#endif

4. 读取配置文件动态加载库

在某些情况下,你可能希望从配置文件中读取动态库的路径和名称,然后根据这些信息进行动态加载。这可以通过结合使用QSettings类来读取配置文件和QLibrary类来加载库实现。

示例代码:

#include <QCoreApplication>  
#include <QSettings>  
#include <QLibrary>  
  
// 读取配置文件并动态加载库  
QSettings settings("config.ini", QSettings::IniFormat);  
QString libraryPath = settings.value("Library/Path").toString();  
QString libraryName = settings.value("Library/Name").toString();  
QLibrary library(libraryPath + "/" + libraryName);  
if (library.load()) {  
    // 动态库加载成功,后续处理...  
}

综上所述,Qt中类库的动态加载方式主要有使用QLibrary类、使用QPluginLoader类、使用平台特定的API以及读取配置文件动态加载库等几种方法。具体选择哪种方法取决于你的需求和使用场景。

2.extern "C" MYLIBRARY_API 

在C++中,extern "C" 是一个指示符,用于告诉编译器要按照C语言的链接方式来处理指定的代码。这通常用于在C++代码中调用C语言编写的库或函数,或者为了确保C++代码中的某些函数可以被C语言代码调用。

MYLIBRARY_API 则可能是一个宏定义,用于控制库函数的导出或导入。这个宏的具体定义取决于你的开发环境和编译器。在Windows上,如果你正在使用DLL(动态链接库),MYLIBRARY_API 可能会被定义为 __declspec(dllexport) 或 __declspec(dllimport),取决于你是在创建库还是在使用库。在Linux或其他Unix-like系统上,这个宏可能是空的,因为Unix-like系统通常使用不同的机制来处理动态库。

一个典型的例子可能是这样的:

在你的库的头文件中,你可能会看到类似以下的代码:

#ifdef MYLIBRARY_EXPORTS  
#define MYLIBRARY_API __declspec(dllexport)  
#else  
#define MYLIBRARY_API __declspec(dllimport)  
#endif  
  
extern "C" MYLIBRARY_API int myFunction(int param);

在这个例子中:

  • MYLIBRARY_EXPORTS 是一个在创建库时定义的宏。当你编译库时,你应该定义这个宏(例如,在编译器的命令行参数中,或者在你的项目设置中)。
  • 如果没有定义 MYLIBRARY_EXPORTS,那么假设你正在编译一个使用这个库的应用程序,此时 MYLIBRARY_API 会被定义为 __declspec(dllimport),以便导入库中的函数。
  • extern "C" 确保函数名在编译时不会被C++编译器修改(例如,C++编译器通常会进行名字修饰,以支持函数重载)。

这样,当你在C++代码中使用这个头文件,并链接到相应的库时,你就可以调用 myFunction 函数,就像调用一个普通的C函数一样。同时,由于使用了 MYLIBRARY_API 宏,你还可以控制函数的导出和导入,这使得你的库可以更容易地被其他项目使用。

3.动态加载库使用的接口必须用extern "C" MYLIBRARY_API 标识吗 

动态加载库使用的接口并不必须用 extern "C" MYLIBRARY_API 标识,但这通常是一个好的做法。让我们分别讨论这两个部分:

  1. extern "C"
    • 在C++中,为了确保能够与C语言代码进行互操作,或者确保函数名在编译时不会被C++编译器修改,通常会使用 extern "C"
    • 如果你知道你的库只会被C++代码使用,并且不需要与C语言代码互操作,那么理论上你可以省略 extern "C"。然而,这样做可能会限制你的库在未来的使用场景。
  2. MYLIBRARY_API
    • MYLIBRARY_API 通常是一个宏,用于控制库函数的导出或导入。
    • 在Windows上,这通常与 __declspec(dllexport) 和 __declspec(dllimport) 相关。
    • 在其他平台上,这个宏可能是空的,或者可能定义为与平台相关的导出/导入指令。
    • 如果你不打算跨平台,并且只关心当前平台上的动态库加载,那么你可能不需要这个宏,或者可以使用平台特定的指令代替。

然而,将 extern "C" 和 MYLIBRARY_API 结合使用通常是一个好的实践,因为它:

  • 确保了C和C++之间的互操作性。
  • 提供了跨平台支持(如果你正确地定义了 MYLIBRARY_API)。
  • 使得你的库接口更加清晰和一致。

因此,即使你的库当前只用于C++,并且只在单一平台上运行,使用 extern "C" MYLIBRARY_API 也是一个明智的选择,因为它为你的库提供了更大的灵活性和未来的可扩展性。

总的来说,虽然动态加载库使用的接口不一定必须用 extern "C" MYLIBRARY_API 标识,但这样做通常是有益的,并且符合良好的编程实践。

4.动态加载共享库时,可以获取某个类下的函数指针吗?如果可以请举例 

是的,动态加载共享库时,你可以获取某个类下的函数指针。但是,由于C++的类成员函数通常具有一个隐含的this指针,直接通过函数名获取成员函数指针并不简单。通常,你需要获取的是静态成员函数或者通过某种方式获取类的实例,然后通过实例来调用成员函数。

然而,如果你有一个类的实例,并且知道成员函数的名称和签名,你可以使用QLibrary来解析该成员函数的地址,并通过某种方式(例如,使用函数指针或std::function)来调用它。但是,这种方法通常涉及到复杂的类型转换和安全性问题。

一个更常见和安全的做法是使用接口或抽象类,并通过工厂函数来创建对象的实例。下面是一个简化的例子,展示了如何在动态加载的共享库中定义一个接口,并在主程序中通过该接口调用成员函数。

共享库代码

IPlugin.h

#pragma once  
  
#include <QObject>  
#include <QString>  
  
class IPlugin : public QObject  
{  
    Q_OBJECT  
public:  
    virtual ~IPlugin() {}  
    virtual QString pluginName() const = 0;  
    virtual void doSomething() = 0;  
};  
  
#define IPlugin_iid "com.example.IPlugin"  
Q_DECLARE_INTERFACE(IPlugin, IPlugin_iid)

MyPlugin.h

#pragma once  
  
#include "IPlugin.h"  
  
class MyPlugin : public QObject, public IPlugin  
{  
    Q_OBJECT  
    Q_PLUGIN_METADATA(IID IPlugin_iid)  
    Q_INTERFACES(IPlugin)  
  
public:  
    QString pluginName() const override { return "MyPlugin"; }  
    void doSomething() override { /* 实现你的功能 */ }  
};

MyPlugin.cpp(实现文件,如果需要的话)

 
#include "MyPlugin.h"  
  
// 如果需要,可以在这里实现MyPlugin的成员函数

主程序代码

在主程序中,你可以使用QLibrary来加载共享库,并通过QPluginLoader来实例化插件对象。

main.cpp

#include <QCoreApplication>  
#include <QLibrary>  
#include <QPluginLoader>  
#include <QString>  
#include <QDebug>  
#include "IPlugin.h"  
  
int main(int argc, char *argv[])  
{  
    QCoreApplication a(argc, argv);  
  
    QLibrary library("MyPluginLibrary.dll"); // 假设你的共享库叫做MyPluginLibrary.dll  
    if (!library.load()) {  
        qDebug() << "Cannot load library:" << library.errorString();  
        return -1;  
    }  
  
    // 使用QPluginLoader来加载插件  
    QPluginLoader loader(library.fileName());  
    QObject *plugin = loader.instance();  
    if (!plugin) {  
        qDebug() << "Cannot create plugin instance:" << loader.errorString();  
        return -1;  
    }  
  
    // 检查插件是否实现了IPlugin接口  
    IPlugin *iPlugin = qobject_cast<IPlugin *>(plugin);  
    if (!iPlugin) {  
        qDebug() << "Plugin does not implement IPlugin interface";  
        return -1;  
    }  
  
    // 调用插件的函数  
    qDebug() << "Plugin name:" << iPlugin->pluginName();  
    iPlugin->doSomething();  
  
    // 卸载库(通常不需要,因为QPluginLoader会管理库的生命周期)  
    // library.unload(); // 如果你确实需要卸载库,可以在适当的时候调用它  
  
    return a.exec();  
}

请注意,在这个例子中,MyPlugin类实现了IPlugin接口,并且使用了Qt的插件机制(Q_PLUGIN_METADATAQ_INTERFACES宏)。这样,主程序可以通过QPluginLoader来加载和实例化插件对象,并通过接口来调用插件的功能。

另外,请确保你的共享库和主程序使用相同的Qt版本和编译器构建,以避免兼容性问题。还需要确保共享库和主程序都能够找到彼此依赖的其他库(如果有的话)。

5.qt cmake windows下动态加载共享库的例子 

在Windows下,使用Qt和CMake来动态加载共享库(DLL)涉及几个步骤。首先,你需要创建一个共享库,然后使用CMake来构建它。接着,在你的Qt应用程序中,你可以使用Qt提供的QLibrary类来动态加载这个DLL。

下面是一个简单的例子,展示了如何创建共享库以及如何在Qt应用程序中动态加载它。

共享库代码

MyLibrary.h

#pragma once  
  
#ifdef MYLIBRARY_EXPORTS  
#define MYLIBRARY_API __declspec(dllexport)  
#else  
#define MYLIBRARY_API __declspec(dllimport)  
#endif  
  
extern "C" MYLIBRARY_API int add(int a, int b);

MyLibrary.cpp

#include "MyLibrary.h"  
  
int add(int a, int b) {  
    return a + b;  
}

CMakeLists.txt(用于构建共享库)

cmake_minimum_required(VERSION 3.5)  
  
project(MyLibrary)  
  
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)  # 导出所有符号  
  
add_library(MyLibrary SHARED  
    MyLibrary.h  
    MyLibrary.cpp  
)  
  
# 如果需要,可以设置目标编译选项或链接选项  
# target_compile_options(MyLibrary PRIVATE ...)  
# target_link_libraries(MyLibrary PRIVATE ...)

使用CMake构建共享库:

  1. 在包含上述文件的目录中创建一个build目录。
  2. 打开命令提示符或PowerShell,导航到build目录。
  3. 运行cmake ..来配置项目。
  4. 运行cmake --build .来构建共享库。

这将生成MyLibrary.dll和相关的导入库文件。

Qt 应用程序代码

在你的Qt应用程序中,你可以使用QLibrary来动态加载这个DLL。

main.cpp

#include <QCoreApplication>  
#include <QLibrary>  
#include <QDebug>  
  
// 定义函数指针类型  
typedef int (*AddFunc)(int, int);  
  
int main(int argc, char *argv[])  
{  
    QCoreApplication a(argc, argv);  
  
    QLibrary library("MyLibrary.dll");  
    if (!library.load()) {  
        qDebug() << "Cannot load library:" << library.errorString();  
        return -1;  
    }  
  
    // 获取函数指针  
    AddFunc add = (AddFunc)library.resolve("add");  
    if (!add) {  
        qDebug() << "Cannot find function 'add' in the library";  
        return -1;  
    }  
  
    // 调用函数  
    int result = add(5, 10);  
    qDebug() << "The result is:" << result;  
  
    // 卸载库  
    library.unload();  
  
    return a.exec();  
}

Qt 项目文件(.pro)

QT += core  
  
CONFIG += c++11 console  
CONFIG -= app_bundle  
  
TEMPLATE = app  
  
SOURCES += main.cpp  
  
# 如果DLL不在应用程序的同一目录下,你需要设置库的路径  
# LIBS += -L<path_to_dll> -lMyLibrary  
  
# 或者,你可以将DLL复制到应用程序的输出目录  
# 使用QMAKE_POST_LINK来复制DLL(需要在.pro文件中定义)  
#win32:CONFIG += dllcopy  
#QMAKE_POST_LINK += $$QMAKE_COPY $$PWD/path_to_dll/MyLibrary.dll $$DESTDIR

确保MyLibrary.dll位于你的Qt应用程序可以访问的目录中,或者在你的.pro文件中设置正确的库路径。然后,你可以像平常一样构建和运行你的Qt应用程序。在运行时,它将动态加载MyLibrary.dll并调用其中的add函数。

6.前面的例子(qt cmake windows下动态加载共享库的例子)中使用了“extern "C" MYLIBRARY_API”,请问MYLIBRARY_API 需要引入什么文件 

在前面的例子中,MYLIBRARY_API 是一个宏,它用于控制共享库中符号的导出或导入。这个宏通常不是由Qt或CMake直接提供的,而是由项目的构建系统定义的。因此,你不需要引入Qt或CMake的特定文件来获取MYLIBRARY_API宏的定义。

相反,你应该在项目的构建配置中(如CMakeLists.txt文件)定义MYLIBRARY_API宏。这通常是通过检查是否正在构建共享库来完成的,并相应地设置宏的值。例如,在CMake中,你可能会看到类似以下的代码:

# 检查是否定义了MYLIBRARY_EXPORTS,通常这是在项目内部设置的一个变量  
# 用于指示是否正在构建共享库  
if (DEFINED MYLIBRARY_EXPORTS)  
    add_definitions(-DMYLIBRARY_EXPORTS)  
endif()  
  
# ... 其他CMake配置 ...

然后,在你的C++源代码中,你应该这样使用MYLIBRARY_API宏:

#ifdef MYLIBRARY_EXPORTS  
#define MYLIBRARY_API __declspec(dllexport)  
#else  
#define MYLIBRARY_API __declspec(dllimport)  
#endif  
  
// 使用extern "C"来确保C++编译器不会对此函数进行名字修饰  
extern "C" MYLIBRARY_API void MyFunction();

在这个例子中,当MYLIBRARY_EXPORTS被定义时(通常是在构建共享库时),MYLIBRARY_API会被替换为__declspec(dllexport),从而导出MyFunction函数。当MYLIBRARY_EXPORTS没有被定义时(通常是在使用共享库的应用程序中),MYLIBRARY_API会被替换为__declspec(dllimport),从而导入MyFunction函数。

请注意,MYLIBRARY_EXPORTS通常是在构建共享库的项目设置中定义的,而不是在源代码中直接定义的。这是为了确保只有共享库的构建过程会导出符号,而使用共享库的应用程序则会导入这些符号。

如果你正在使用Qt和CMake,并且你的项目结构是标准的,那么你可能不需要手动定义MYLIBRARY_API。Qt和CMake提供了一些机制和宏来帮助处理共享库的导出和导入,比如Q_DECL_EXPORTQ_DECL_IMPORT(在较新的Qt版本中,这些宏已经被弃用,取而代之的是使用Q_PLUGIN_METADATA和其他机制来注册插件)。但是,如果你想要完全控制符号的导出和导入过程,或者你的项目有特殊的需求,那么手动定义MYLIBRARY_API是一个可行的选择。

7.cmake 设置库的路径 

在CMake中设置库的路径可以通过多种方式实现,具体取决于你想要如何配置你的项目和构建环境。以下是一些常用的方法来设置库的路径:

  1. 使用link_directoriestarget_link_libraries:

    # 指定库文件所在的目录  
    link_directories(/path/to/your/library/directory)  
    
    # 链接库到目标  
    target_link_libraries(your_target_name your_library_name)

    link_directories告诉CMake在指定的目录中查找库文件,而target_link_libraries则用于将库链接到特定的目标(如可执行文件或共享库)。

  2. 使用find_library:

    # 查找库文件并设置变量  
    find_library(YOUR_LIB_VAR your_library_name /path/to/your/library/directory)  
    
    # 如果找到了库,则将其链接到目标  
    if(YOUR_LIB_VAR)  
        target_link_libraries(your_target_name ${YOUR_LIB_VAR})  
    else()  
        message(FATAL_ERROR "Library not found!")  
    endif()

    find_library尝试在指定的目录中查找库文件,并将找到的路径存储在变量中。然后,你可以使用这个变量来链接库。

  3. 使用find_package和配置文件:

    如果你正在使用一个已知的库(如Qt、Boost等),那么通常会有一个与之关联的find_package模块或配置文件。这个模块或文件会负责查找库并设置必要的变量。

    # 查找库包  
    find_package(YourLibraryName REQUIRED)  
    
    # 如果找到了包,则使用它提供的变量来链接库  
    if(YourLibraryName_FOUND)  
        target_link_libraries(your_target_name ${YourLibraryName_LIBRARIES})  
        include_directories(${YourLibraryName_INCLUDE_DIRS}) # 如果需要包含目录  
    else()  
        message(FATAL_ERROR "Library package not found!")  
    endif()

    请注意,YourLibraryName应该替换为你要查找的库的实际名称。find_package模块通常会设置一些变量,如YourLibraryName_FOUNDYourLibraryName_LIBRARIESYourLibraryName_INCLUDE_DIRS,你可以使用这些变量来配置你的项目。

  4. 设置CMAKE_LIBRARY_PATHCMAKE_INCLUDE_PATH:

    你也可以通过设置CMAKE_LIBRARY_PATHCMAKE_INCLUDE_PATH变量来告诉CMake在哪里查找库和头文件。这些变量可以在CMake命令行中设置,或者在CMakeLists.txt文件中通过set命令设置。

    # 在CMake命令行中设置  
    cmake -DCMAKE_LIBRARY_PATH=/path/to/your/library/directory -DCMAKE_INCLUDE_PATH=/path/to/your/include/directory ..  
    
    # 或在CMakeLists.txt中设置  
    set(CMAKE_LIBRARY_PATH ${CMAKE_LIBRARY_PATH} /path/to/your/library/directory)  
    set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH} /path/to/your/include/directory)

    然而,直接设置这些变量通常不是推荐的做法,因为它可能会导致构建环境的不可预测性。更好的方法是使用find_packagefind_librarylink_directories等机制。

  5. 使用target_include_directoriestarget_link_directories (推荐的方式,特别是对于现代CMake):

    这些命令允许你为特定的目标设置包含目录和库目录,而不是全局地设置它们。

    # 为目标设置包含目录  
    target_include_directories(your_target_name PRIVATE /path/to/your/include/directory)  
    
    # 为目标设置库目录(通常不需要,因为target_link_libraries可以直接处理库路径)  
    # target_link_directories(your_target_name PRIVATE /path/to/your/library/directory) # 这通常不是必需的  
    
    # 链接库到目标  
    target_link_libraries(your_target_name /path/to/your/library/directory/your_library.lib) # 或者使用库的名字,如果它在你的CMake环境中已知

    使用target_前缀的命令可以确保设置仅应用于指定的目标,这有助于保持构建环境的清晰和可维护性。

请根据你的具体需求和项目结构选择最合适的方法。通常,使用find_package

8.qt 举一个windows下动态加载共享库的例子

在Windows下,Qt应用程序可以使用平台特定的API来动态加载共享库(DLL)。尽管Qt本身提供了一套跨平台的库加载机制(例如,通过QPluginLoader类),但有时你可能需要直接使用Windows API(如LoadLibraryGetProcAddress)来加载DLL并获取函数指针。

下面是一个简单的例子,展示了如何在Windows下的Qt应用程序中动态加载一个共享库(DLL)并调用其中的一个函数。

首先,假设你有一个名为MyLibrary.dll的共享库,并且它包含一个名为add的函数,该函数接受两个整数并返回它们的和。你需要确保这个DLL文件位于你的应用程序可以访问的某个目录中。

然后,你可以编写以下Qt代码来动态加载这个DLL并调用add函数:

#include <QCoreApplication>  
#include <QDebug>  
#include <windows.h> // 包含Windows API头文件  
  
// 定义函数指针类型,用于存储从DLL中获取的函数地址  
typedef int (*AddFunc)(int, int);  
  
int main(int argc, char *argv[])  
{  
    QCoreApplication a(argc, argv);  
  
    // 加载DLL  
    HINSTANCE hLib = LoadLibrary(TEXT("./MyLibrary.dll"));  
    if (!hLib) {  
        qDebug() << "Could not load the DLL";  
        return -1;  
    }  
  
    // 获取函数地址  
    AddFunc add = (AddFunc)GetProcAddress(hLib, "add");  
    if (!add) {  
        qDebug() << "Could not locate the function";  
        FreeLibrary(hLib);  
        return -1;  
    }  
  
    // 调用函数  
    int result = add(5, 10);  
    qDebug() << "The result is:" << result;  
  
    // 释放DLL  
    FreeLibrary(hLib);  
  
    return a.exec();  
}

在这个例子中,LoadLibrary函数用于加载DLL,GetProcAddress函数用于获取DLL中函数的地址。一旦获得了函数地址,你就可以像调用普通函数一样调用它。最后,使用FreeLibrary函数来释放DLL。

请注意,TEXT宏用于将字符串转换为适合Windows API的字符编码(通常是Unicode)。如果你的DLL不是使用Unicode编建的,你可能需要相应地调整字符串编码。

此外,确保你的DLL中的函数是使用extern "C"声明的,以避免C++名称修饰(name mangling)导致的问题。例如,DLL中的add函数应该像这样声明:

extern "C" __declspec(dllexport) int add(int a, int b) {  
    return a + b;  
}

__declspec(dllexport)关键字用于指示该函数应该被导出为DLL的公共接口的一部分。

9.针对上面的调用,请提供一份类库的代码 

为了与上面的动态加载代码相配合,我们需要创建一个共享库(DLL),其中包含一个名为add的函数。以下是一个简单的C++代码示例,用于创建这样的DLL。

首先,我们需要创建一个头文件来声明将要导出的函数。这个头文件将用于DLL的实现以及任何想要使用该DLL的客户端代码。

MyLibrary.h

#pragma once  
  
#ifdef MYLIBRARY_EXPORTS  
#define MYLIBRARY_API __declspec(dllexport)  
#else  
#define MYLIBRARY_API __declspec(dllimport)  
#endif  
  
extern "C" MYLIBRARY_API int add(int a, int b);

在上面的头文件中,MYLIBRARY_EXPORTS是一个宏,它在创建DLL时应该被定义。这样,__declspec(dllexport)就会被用于修饰add函数,指示它应该被导出为DLL的一部分。当客户端代码包含这个头文件时,MYLIBRARY_EXPORTS通常不会被定义,因此__declspec(dllimport)会被用于修饰add函数,指示它应该从DLL中导入。

接下来,我们创建DLL的实现文件。

MyLibrary.cpp

#include "MyLibrary.h"  
  
int add(int a, int b) {  
    return a + b;  
}

在这个实现文件中,我们简单地实现了add函数,它接受两个整数参数并返回它们的和。

为了构建这个DLL,你需要使用像Visual Studio这样的IDE,或者你可以使用命令行工具(如cl.exe)来编译和链接代码。在Visual Studio中,你可以创建一个新的“动态链接库(DLL)”项目,将上面的头文件和实现文件添加到项目中,并设置适当的项目属性来生成DLL。

确保在构建DLL时定义了MYLIBRARY_EXPORTS宏。这通常通过在项目的预处理器定义设置中添加MYLIBRARY_EXPORTS来完成。

一旦DLL构建完成,你就可以像之前展示的那样,在Qt应用程序中使用LoadLibraryGetProcAddress来动态加载它并调用add函数了。

请注意,如果你的Qt应用程序和DLL使用不同的编译器或编译器设置(例如,不同的字符集或运行时库),你可能会遇到一些兼容性问题。为了确保兼容性,请确保你的DLL和应用程序使用相同的编译器和编译器设置进行构建。

4.调查:关于隐私链接和显示连接

1.qt cmake windows c++动态库显示连接举例 

在Windows上使用Qt和CMake创建一个C++动态库,并演示如何连接和使用该动态库,可以分为以下几个步骤:

  1. 创建动态库项目
  2. 编写库的代码
  3. 编写CMakeLists.txt文件来生成动态库
  4. 创建应用程序项目并使用动态库
  5. 编写和配置应用程序的CMakeLists.txt

以下是详细的步骤和示例代码:

1. 创建动态库项目

创建一个新的目录用于动态库项目,例如 MyLibrary

2. 编写库的代码

在 MyLibrary 目录中创建以下文件:

mylib.h

#ifndef MYLIB_H  
#define MYLIB_H  
  
#ifdef MYLIBRARY_LIBRARY  
#  define MYLIBRARY_EXPORT Q_DECL_EXPORT  
#else  
#  define MYLIBRARY_EXPORT Q_DECL_IMPORT  
#endif  
  
extern "C" MYLIBRARY_EXPORT int add(int a, int b);  
  
#endif // MYLIB_H

mylib.cpp

#include "mylib.h"  
  
int add(int a, int b) {  
    return a + b;  
}

3. 编写CMakeLists.txt文件来生成动态库

在 MyLibrary 目录中创建 CMakeLists.txt 文件:

CMakeLists.txt

cmake_minimum_required(VERSION 3.14)  
  
project(MyLibrary)  
  
set(CMAKE_CXX_STANDARD 17)  
set(CMAKE_CXX_STANDARD_REQUIRED ON)  
  
find_package(Qt5 REQUIRED COMPONENTS Core)  
  
add_library(MyLibrary SHARED  
    mylib.h  
    mylib.cpp  
)  
  
target_link_libraries(MyLibrary Qt5::Core)  
  
# Define MYLIBRARY_LIBRARY so that mylib.h knows we are building the library  
target_compile_definitions(MyLibrary PRIVATE MYLIBRARY_LIBRARY)

4. 创建应用程序项目并使用动态库

在 MyLibrary 目录之外创建一个新的目录用于应用程序项目,例如 MyApp

在 MyApp 目录中创建以下文件:

main.cpp

#include <QCoreApplication>  
#include <QDebug>  
  
// You need to provide the correct path to your MyLibrary headers  
#include "../MyLibrary/mylib.h"  
  
int main(int argc, char *argv[])  
{  
    QCoreApplication a(argc, argv);  
  
    int result = add(3, 4);  
    qDebug() << "The result of add(3, 4) is:" << result;  
  
    return a.exec();  
}

5. 编写和配置应用程序的CMakeLists.txt

在 MyApp 目录中创建 CMakeLists.txt 文件:

CMakeLists.txt

cmake_minimum_required(VERSION 3.14)  
  
project(MyApp)  
  
set(CMAKE_CXX_STANDARD 17)  
set(CMAKE_CXX_STANDARD_REQUIRED ON)  
  
find_package(Qt5 REQUIRED COMPONENTS Core)  
  
# Add the directory containing MyLibrary's CMakeLists.txt  
add_subdirectory(../MyLibrary MyLibrary)  
  
add_executable(MyApp  
    main.cpp  
)  
  
# Link MyApp with MyLibrary  
target_link_libraries(MyApp MyLibrary Qt5::Core)  
  
# Add the MyLibrary include directory  
target_include_directories(MyApp PRIVATE ../MyLibrary)

构建和运行

  1. 打开CMake GUI并选择 MyApp 目录作为源目录。
  2. 创建一个新的构建目录,例如 MyApp/build
  3. 点击 "Configure" 和 "Generate" 生成构建文件。
  4. 打开生成的 MyApp.sln 文件,并使用Visual Studio构建项目。(或者你可以直接在命令行中使用 cmake --build .
  5. 构建完成后,运行生成的可执行文件(通常位于 MyApp/build/Debug 或 MyApp/build/Release 目录下)。

这样,你就创建了一个Qt和CMake的C++动态库,并在一个应用程序中连接和使用了该动态库。

2.上面,对显式链接和隐式链接分别举例 

在Windows操作系统上,利用Qt和CMake构建C++项目时,我们通常会遇到显式链接(explicit linking)和隐式链接(implicit linking)这两种库连接方式。接下来,我将通过具体的例子来阐述这两种链接方式。

显式链接

显式链接,顾名思义,就是在程序中明确指出需要链接的库。在Windows环境下,这通常涉及到在运行时利用LoadLibrary(或LoadLibraryEx)函数来载入动态链接库(DLL),然后借助GetProcAddress函数来获取所需函数的地址。但在使用CMake进行构建时,显式链接更多是指在构建过程中明确指定要链接的库目标。

CMakeLists.txt配置(MyApp目录下,采用显式链接):

# ...(省略了部分CMake配置)  
  
# 显式地指定MyLibrary的目标路径(假设已构建并位于指定目录)  
set(MYLIBRARY_PATH "../MyLibrary/build/Debug") # 或Release,视构建配置而定  
set(MYLIBRARY_NAME "MyLibrary")  
  
# 将MyLibrary的动态库文件路径添加到运行时库搜索路径中  
link_directories(${MYLIBRARY_PATH})  
  
# 构建可执行文件MyApp,并显式链接MyLibrary  
add_executable(MyApp main.cpp)  
target_link_libraries(MyApp ${MYLIBRARY_NAME})  
  
# 若MyLibrary的头文件不在标准路径中,则需添加包含目录  
target_include_directories(MyApp PRIVATE "../MyLibrary")

在此配置中,link_directories指明了动态库的搜索路径,而target_link_libraries则负责将MyLibrary与MyApp进行链接。需要注意的是,若MyLibrary为Qt项目且采用了Qt的命名约定(如libMyLibrary.dll),在target_link_libraries中指定库名时,可省略前缀lib和后缀.dll

main.cpp代码样例(显式链接时通常无需特别修改):

cpp复制代码

// ...(与隐式链接时的main.cpp代码相同)

但请注意,若采用显式链接方式,且希望在运行时动态载入库(而非构建时链接),则需在程序中利用LoadLibraryGetProcAddress来手动载入库和获取函数。不过,在使用CMake进行构建时,我们通常推荐在构建阶段就完成库的链接,以便简化运行时操作。

隐式链接

隐式链接是指程序在运行时自动载入并链接所需的动态库。在Windows上,这要求动态库的名称和导入库(.lib文件)必须遵循特定的命名规则,并确保它们位于系统的库搜索路径中,或已明确指定其路径。

CMakeLists.txt配置(MyApp目录下,采用隐式链接,假设MyLibrary已正确导出导入库):

# ...(省略了部分CMake配置)  
  
# 假设MyLibrary构建后生成了导入库MyLibrary.lib,并位于指定目录  
set(MYLIBRARY_IMPORT_LIB_PATH "../MyLibrary/build/Debug") # 或Release,视构建配置而定  
set(MYLIBRARY_IMPORT_LIB "MyLibrary.lib")  
  
# 将MyLibrary的导入库文件路径添加到链接器搜索路径中  
link_directories(${MYLIBRARY_IMPORT_LIB_PATH})  
  
# 构建可执行文件MyApp,并隐式链接MyLibrary(通过导入库)  
add_executable(MyApp main.cpp)  
target_link_libraries(MyApp ${MYLIBRARY_IMPORT_LIB})  
  
# 若MyLibrary的头文件不在标准路径中,则需添加包含目录(与显式链接相同)  
target_include_directories(MyApp PRIVATE "../MyLibrary")

在此配置中,我们利用link_directories指明了导入库的搜索路径,并通过target_link_libraries将MyLibrary的导入库与MyApp进行链接。由于采用的是隐式链接方式,因此无需在运行时手动载入库和获取函数。

main.cpp代码样例(隐式链接时无需特别修改,与显式链接相同):

// ...(包含Qt头文件、使用Qt功能及MyLibrary函数的代码)

需要强调的是,无论是显式链接还是隐式链接,在构建MyApp时,CMake都会根据target_link_libraries的指示来解析并链接所需的库。区别主要在于链接的时机和方式:显式链接允许在运行时动态选择并载入库,而隐式链接则在程序启动时自动完成库的载入和链接。在实际应用中,隐式链接更为常见和便捷,因为它简化了程序的运行流程。

显示连接和隐式连接唯一的差别就是显示设置库的名称,隐式设置的是文件路径

3.void* handle = dlopen("./libmath.so", RTLD_LAZY); 

在Linux和其他类Unix操作系统中,dlopen函数用于在运行时动态地加载共享库(例如.so文件)。在你的代码片段中,dlopen被用来尝试加载名为libmath.so的共享库,并且使用了RTLD_LAZY标志。

下面是对你代码片段的详细解释:

void* handle = dlopen("./libmath.so", RTLD_LAZY);
  • void* handle: 声明了一个void指针类型的变量handle,它将用于存储dlopen函数返回的句柄。这个句柄在后续的函数调用中(如dlsymdlclose)被用作对加载的库的引用。

  • dlopen("./libmath.so", RTLD_LAZY): 调用dlopen函数来加载共享库。

    • "./libmath.so": 这是要加载的共享库的路径。在这个例子中,它指定了当前目录下的libmath.so文件。

    • RTLD_LAZY: 这是一个标志,告诉dlopen在加载库时采用延迟解析(lazy resolution)的方式。这意味着,只有在实际调用库中的函数时,这些函数才会被解析和链接。如果某个函数从未被调用,那么它可能永远不会被加载到内存中。这种方式可以减少程序的启动时间和内存使用。

如果dlopen成功加载了库,它会返回一个有效的句柄,该句柄可以用于后续的dlsym调用来获取库中的函数指针。如果加载失败,dlopen会返回NULL,并且可以通过dlerror函数来获取错误信息。

为了完整性和健壮性,你的代码应该检查dlopen的返回值,并处理可能的错误情况。例如:

#include <dlfcn.h>  
#include <stdio.h>  
  
int main() {  
    void* handle = dlopen("./libmath.so", RTLD_LAZY);  
    if (!handle) {  
        fprintf(stderr, "Error loading library: %s\n", dlerror());  
        return 1;  
    }  
  
    // 在这里使用dlsym来获取函数指针,并使用这些函数...  
  
    // 完成后,关闭库  
    dlclose(handle);  
    return 0;  
}

在这个示例中,如果dlopen失败,程序会打印出错误信息并返回1。如果成功,它会继续执行(在这里你可以添加使用dlsym来获取函数指针并调用这些函数的代码),然后在结束时调用dlclose来关闭库。

4.qt cmake windows c++动态库采用显示连接举例

在Windows平台上使用CMake构建Qt C++项目,并创建一个动态库(DLL),然后显式地连接到该动态库,涉及多个步骤。下面是一个详细的示例,包括创建动态库和使用该库的客户端应用程序。

1. 创建动态库

目录结构
MyLibrary/  
├── CMakeLists.txt  
├── include/  
│   └── MyLibrary/  
│       └── MyLibrary.h  
└── src/  
    ├── MyLibrary.cpp  
    └── MyLibrary_global.h
CMakeLists.txt
cmake_minimum_required(VERSION 3.14)  
project(MyLibrary VERSION 1.0 LANGUAGES CXX)  
  
set(CMAKE_CXX_STANDARD 17)  
set(CMAKE_CXX_STANDARD_REQUIRED ON)  
  
# Set the include directory  
include_directories(${CMAKE_SOURCE_DIR}/include)  
  
# Add the library  
add_library(MyLibrary SHARED  
    src/MyLibrary.cpp  
)  
  
# Set the version of the library  
set_target_properties(MyLibrary PROPERTIES  
    VERSION ${PROJECT_VERSION}  
    SOVERSION 1  
)  
  
# Export the library for downstream projects  
export(TARGETS MyLibrary FILE MyLibraryTargets.cmake)  
  
# Install the library and headers  
install(TARGETS MyLibrary  
    LIBRARY DESTINATION lib  
)  
install(FILES include/MyLibrary/MyLibrary.h DESTINATION include/MyLibrary)  
install(EXPORT MyLibraryTargets FILE MyLibraryTargets.cmake DESTINATION lib/cmake/MyLibrary)
MyLibrary_global.h
#pragma once  
  
#if defined(MYLIBRARY_LIBRARY)  
#  define MYLIBRARY_EXPORT Q_DECL_EXPORT  
#else  
#  define MYLIBRARY_EXPORT Q_DECL_IMPORT  
#endif
MyLibrary.h
#pragma once  
  
#include <MyLibrary_global.h>  
  
class MYLIBRARY_EXPORT MyLibrary  
{  
public:  
    MyLibrary();  
    void hello();  
};
MyLibrary.cpp
#include "MyLibrary.h"  
#include <QDebug>  
  
MyLibrary::MyLibrary()  
{  
}  
  
void MyLibrary::hello()  
{  
    qDebug() << "Hello from MyLibrary!";  
}

2. 构建动态库

MyLibrary目录下运行以下命令:

mkdir build  
cd build  
cmake ..  
cmake --build . --config Release

这将生成MyLibrary.dll和相关文件。

3. 创建客户端应用程序

目录结构
ClientApp/  
├── CMakeLists.txt  
└── main.cpp
CMakeLists.txt
cmake_minimum_required(VERSION 3.14)  
project(ClientApp VERSION 1.0 LANGUAGES CXX)  
  
set(CMAKE_CXX_STANDARD 17)  
set(CMAKE_CXX_STANDARD_REQUIRED ON)  
  
# Find Qt  
find_package(Qt5 REQUIRED COMPONENTS Core)  
  
# Add the include directories for MyLibrary  
include_directories(${CMAKE_SOURCE_DIR}/../MyLibrary/include)  
  
# Link to the MyLibrary  
link_directories(${CMAKE_SOURCE_DIR}/../MyLibrary/build/Release)  
  
add_executable(ClientApp  
    main.cpp  
)  
  
# Link Qt and MyLibrary  
target_link_libraries(ClientApp  
    Qt5::Core  
    MyLibrary  
)
main.cpp
#include <QCoreApplication>  
#include <MyLibrary.h>  
  
int main(int argc, char *argv[])  
{  
    QCoreApplication a(argc, argv);  
  
    MyLibrary lib;  
    lib.hello();  
  
    return a.exec();  
}

4. 构建客户端应用程序

ClientApp目录下运行以下命令:

mkdir build  
cd build  
cmake ..  
cmake --build . --config Release

这将生成ClientApp.exe,并将动态库MyLibrary.dll复制到可执行文件所在目录(如果需要,可以手动复制或设置环境变量)。

5. 运行客户端应用程序

ClientApp/build/Release目录下运行ClientApp.exe,你应该会看到以下输出:

Hello from MyLibrary!

注意事项

  1. 显式连接:在Windows上,通过__declspec(dllexport)__declspec(dllimport)(通过Qt的宏Q_DECL_EXPORTQ_DECL_IMPORT包装)实现显式连接。
  2. 路径问题:确保MyLibrary.dll在运行时可以被找到(例如,将其放在客户端应用程序的同一目录,或设置PATH环境变量)。
  3. CMake导出:为了更加健壮的构建系统,可以使用CMake的installexport功能,使得库和头文件可以被下游项目轻松使用。

这个示例展示了如何在Windows平台上使用CMake和Qt创建一个C++动态库,并显式地连接到该动态库。如果有更复杂的需求,可以根据实际情况进行调整。

5.qt cmake windows c++动态库采用隐式连接举例 

在Windows平台上,使用CMake构建Qt C++项目,并创建一个动态库(DLL),然后采用隐式连接(Implicit Linking)的方式使用该库,涉及几个关键步骤。下面是一个详细的示例。

1. 创建动态库

目录结构
MyLibrary/  
├── CMakeLists.txt  
├── include/  
│   └── MyLibrary/  
│       └── MyLibrary.h  
└── src/  
    ├── MyLibrary.cpp  
    └── MyLibrary_global.h
CMakeLists.txt
cmake_minimum_required(VERSION 3.14)  
project(MyLibrary VERSION 1.0 LANGUAGES CXX)  
  
set(CMAKE_CXX_STANDARD 17)  
set(CMAKE_CXX_STANDARD_REQUIRED ON)  
  
# Set the include directory  
include_directories(${CMAKE_SOURCE_DIR}/include)  
  
# Add the library  
add_library(MyLibrary SHARED  
    src/MyLibrary.cpp  
)  
  
# Set the version of the library  
set_target_properties(MyLibrary PROPERTIES  
    VERSION ${PROJECT_VERSION}  
    SOVERSION 1  
)  
  
# Export the library for downstream projects (not strictly necessary for implicit linking)  
export(TARGETS MyLibrary FILE MyLibraryTargets.cmake)  
  
# Install the library and headers  
install(TARGETS MyLibrary  
    LIBRARY DESTINATION lib  
)  
install(FILES include/MyLibrary/MyLibrary.h DESTINATION include/MyLibrary)  
# The following line is optional and typically used for config-module exports  
# install(EXPORT MyLibraryTargets FILE MyLibraryTargets.cmake DESTINATION lib/cmake/MyLibrary)
MyLibrary_global.h
#pragma once  
  
#if defined(MYLIBRARY_LIBRARY)  
#  define MYLIBRARY_EXPORT Q_DECL_EXPORT  
#else  
#  define MYLIBRARY_EXPORT Q_DECL_IMPORT  
#endif

注意:在构建动态库时,MYLIBRARY_LIBRARY应该被定义,以便MyLibrary_global.h中的宏能够正确地导出符号。这通常通过在CMakeLists.txt中添加编译定义来实现,但在这个例子中,我们假设在构建库时会自动定义它(例如,通过IDE设置或命令行参数)。然而,为了更明确,你也可以在CMakeLists.txt中添加如下行:

target_compile_definitions(MyLibrary PRIVATE MYLIBRARY_LIBRARY)
MyLibrary.h
#pragma once  
  
#include <MyLibrary_global.h>  
  
class MYLIBRARY_EXPORT MyLibrary  
{  
public:  
    MyLibrary();  
    void hello();  
};
MyLibrary.cpp
#include "MyLibrary.h"  
#include <QDebug>  
  
MyLibrary::MyLibrary()  
{  
}  
  
void MyLibrary::hello()  
{  
    qDebug() << "Hello from MyLibrary!";  
}

2. 构建动态库

MyLibrary目录下运行以下命令:

mkdir build  
cd build  
cmake ..  
cmake --build . --config Release

这将生成MyLibrary.dll和相关的.lib文件。

3. 创建客户端应用程序

目录结构
ClientApp/  
├── CMakeLists.txt  
└── main.cpp
CMakeLists.txt
cmake_minimum_required(VERSION 3.14)  
project(ClientApp VERSION 1.0 LANGUAGES CXX)  
  
set(CMAKE_CXX_STANDARD 17)  
set(CMAKE_CXX_STANDARD_REQUIRED ON)  
  
# Find Qt  
find_package(Qt5 REQUIRED COMPONENTS Core)  
  
# Add the include directories for MyLibrary  
include_directories(${CMAKE_SOURCE_DIR}/../MyLibrary/include)  
  
# Link to the MyLibrary (implicitly)  
# Note: No need to specify the .lib extension, CMake handles this automatically  
link_directories(${CMAKE_SOURCE_DIR}/../MyLibrary/build/Release)  
add_executable(ClientApp  
    main.cpp  
)  
  
# Link Qt and MyLibrary  
target_link_libraries(ClientApp  
    Qt5::Core  
    MyLibrary  # This will link to MyLibrary.lib, which will implicitly load MyLibrary.dll at runtime  
)
main.cpp
#include <QCoreApplication>  
#include <MyLibrary.h>  
  
int main(int argc, char *argv[])  
{  
    QCoreApplication a(argc, argv);  
  
    MyLibrary lib;  
    lib.hello();  
  
    return a.exec();  
}

4. 构建客户端应用程序

ClientApp目录下运行以下命令:

mkdir build  
cd build  
cmake ..  
cmake --build . --config Release

这将生成ClientApp.exe。由于我们采用了隐式连接,因此在运行时,ClientApp.exe将自动加载MyLibrary.dll(假设它位于可执行文件的同一目录或系统的PATH中)。

5. 运行客户端应用程序

ClientApp/build/Release目录下运行ClientApp.exe,你应该会看到以下输出:

Hello from MyLibrary!

注意事项

  • 隐式连接要求.lib文件在链接时可用,并且.dll文件在运行时可用。
  • 在这个例子中,我们没有使用CMake的installexport功能来导出目标,因为隐式连接不需要这些。但是,如果你打算在其他项目中重用你的库,那么使用这些功能可能会很有帮助。
  • 确保MyLibrary.dll在运行时可以被找到(例如,将其放在客户端应用程序的同一目录,或设置PATH环境变量)。
  • 如果你的库和客户端应用程序都使用Qt,并且你希望避免在客户端应用程序中重复包含Qt的头文件和库,你可以在客户端应用程序的CMakeLists.txt中使用target_link_libraries来链接到Qt的组件,就像上面所做的那样。

 6.显式链接和隐式链接的差异和特点

显式链接(Explicit Linking)和隐式链接(Implicit Linking)是两种在程序与动态库(如DLL或so文件)之间交互的方式,它们在多个方面存在显著的差异和特点。

显式链接

定义与特点

  • 显式链接是指在程序运行时,由程序代码主动加载动态链接库到进程的地址空间中。这种方式允许程序在需要时才加载特定的库,从而增加了程序的灵活性和可控性。
  • 显式链接通常通过调用系统提供的API函数(如Windows下的LoadLibrary或Linux下的dlopen)来实现。这些函数会加载指定的dll/so文件,并返回一个句柄或指针,用于后续的函数调用。

优点

  • 灵活性高:允许程序在运行时根据需要动态地加载和卸载动态库,这提供了高度的灵活性。
  • 错误处理能力强:在显式链接中,如果dll/so文件不存在、损坏或版本不兼容,程序可以捕获这些错误并作出相应的处理。
  • 支持插件机制:是实现插件机制的基础,程序可以在不修改自身代码的情况下扩展新功能。

缺点

  • 编程复杂度增加:需要程序员在代码中显式地处理dll/so的加载、函数地址的获取和错误处理等问题。
  • 性能开销:每次加载和卸载dll/so时,操作系统都需要执行一系列操作来管理内存和文件资源,可能会引入一定的性能开销。
  • 依赖管理复杂:显式链接的程序需要显式地指定dll/so的路径和名称,增加了依赖管理的复杂性。

隐式链接

定义与特点

  • 隐式链接是最为常见的动态库使用方式,所有的编译环境默认都是采用隐式链接的方式。隐式链接会在链接生成可执行程序时就确立依赖关系,在该程序启动时,操作系统自动会检查它依赖的动态库,并一一加载到该程序的内存空间。
  • 隐式链接的实现通常涉及到包含动态库的头文件(.h)和链接动态库。在编译时,编译器会使用这些文件来解析对动态库中函数的引用,并在链接阶段将相关信息嵌入到可执行文件中。

优点

  • 使用简单:隐式链接通过链接器将dll/so的导入信息嵌入到可执行文件中,并在程序启动时由操作系统自动加载所需的dll/so,无需在代码中显式处理。
  • 依赖管理相对简单:由于dll/so的导入信息被嵌入到可执行文件中,因此操作系统可以根据这些信息自动查找和加载dll/so。
  • 性能开销较小:与显式链接相比,隐式链接在加载dll/so时的性能开销可能较小。

缺点

  • 灵活性较低:隐式链接的程序在启动时会自动加载所有依赖的dll/so,这限制了程序的灵活性。如果某个dll/so不是必需的,但在程序启动时被加载了,就会浪费内存和其他系统资源。
  • 错误处理能力较弱:在隐式链接中,如果dll/so文件不存在、损坏或版本不兼容,程序可能会立即崩溃或显示错误消息,而无法像显式链接那样进行错误处理。
  • 启动时间可能较长:如果程序依赖多个dll/so,并且这些dll/so在程序启动时都被加载了,那么程序的启动时间可能会相对较长。

总结

显式链接和隐式链接各有其适用场景和优缺点。显式链接适用于需要高度灵活性和可控性的场景,如插件系统或可选功能。而隐式链接则因其简单性和易用性,成为大多数桌面应用程序和系统级或应用级框架的默认选择。在实际开发中,应根据具体需求选择合适的链接方式。

7.能以表格的方式做一个对比说明吗 

当然可以,以下是一个以表格形式对比显式链接和隐式链接的说明:

特性显式链接隐式链接
定义在程序运行时主动加载动态库在程序编译时确立依赖,运行时自动加载
加载时机运行时,按需加载程序启动时自动加载所有依赖
灵活性高,允许动态加载和卸载低,依赖在编译时确定
错误处理强,可以捕获加载错误并处理弱,错误可能导致程序崩溃
编程复杂度较高,需要处理加载、函数地址获取等较低,链接器自动处理依赖
性能开销可能较大,每次加载和卸载都有开销可能较小,启动时一次性加载
依赖管理复杂,需要显式指定库路径和名称相对简单,依赖信息嵌入可执行文件
使用场景插件系统、可选功能、需要高度灵活性的场景大多数桌面应用、系统级或应用级框架
实现方式调用系统API(如LoadLibrary、dlopen)包含动态库头文件,链接时指定库
优点灵活性高、错误处理能力强、支持插件机制使用简单、依赖管理相对简单、性能开销可能较小
缺点编程复杂度增加、性能开销可能较大、依赖管理复杂灵活性较低、错误处理能力较弱、启动时间可能较长

这个表格总结了显式链接和隐式链接在定义、加载时机、灵活性、错误处理、编程复杂度、性能开销、依赖管理以及使用场景等方面的对比。希望这能帮助您更好地理解这两种链接方式的差异和特点。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值