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 相关链接
cmake的RKNN 工程分析和opencv相关等知识-优快云博客
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文件)的类。通过调用QLibrary的load()方法加载动态库,并使用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 标识,但这通常是一个好的做法。让我们分别讨论这两个部分:
extern "C":- 在C++中,为了确保能够与C语言代码进行互操作,或者确保函数名在编译时不会被C++编译器修改,通常会使用
extern "C"。 - 如果你知道你的库只会被C++代码使用,并且不需要与C语言代码互操作,那么理论上你可以省略
extern "C"。然而,这样做可能会限制你的库在未来的使用场景。
- 在C++中,为了确保能够与C语言代码进行互操作,或者确保函数名在编译时不会被C++编译器修改,通常会使用
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_METADATA和Q_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构建共享库:
- 在包含上述文件的目录中创建一个
build目录。 - 打开命令提示符或PowerShell,导航到
build目录。 - 运行
cmake ..来配置项目。 - 运行
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_EXPORT和Q_DECL_IMPORT(在较新的Qt版本中,这些宏已经被弃用,取而代之的是使用Q_PLUGIN_METADATA和其他机制来注册插件)。但是,如果你想要完全控制符号的导出和导入过程,或者你的项目有特殊的需求,那么手动定义MYLIBRARY_API是一个可行的选择。
7.cmake 设置库的路径
在CMake中设置库的路径可以通过多种方式实现,具体取决于你想要如何配置你的项目和构建环境。以下是一些常用的方法来设置库的路径:
-
使用
link_directories和target_link_libraries:# 指定库文件所在的目录 link_directories(/path/to/your/library/directory) # 链接库到目标 target_link_libraries(your_target_name your_library_name)link_directories告诉CMake在指定的目录中查找库文件,而target_link_libraries则用于将库链接到特定的目标(如可执行文件或共享库)。 -
使用
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尝试在指定的目录中查找库文件,并将找到的路径存储在变量中。然后,你可以使用这个变量来链接库。 -
使用
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_FOUND、YourLibraryName_LIBRARIES和YourLibraryName_INCLUDE_DIRS,你可以使用这些变量来配置你的项目。 -
设置
CMAKE_LIBRARY_PATH和CMAKE_INCLUDE_PATH:你也可以通过设置
CMAKE_LIBRARY_PATH和CMAKE_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_package、find_library或link_directories等机制。 -
使用
target_include_directories和target_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(如LoadLibrary和GetProcAddress)来加载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应用程序中使用LoadLibrary和GetProcAddress来动态加载它并调用add函数了。
请注意,如果你的Qt应用程序和DLL使用不同的编译器或编译器设置(例如,不同的字符集或运行时库),你可能会遇到一些兼容性问题。为了确保兼容性,请确保你的DLL和应用程序使用相同的编译器和编译器设置进行构建。
4.调查:关于隐私链接和显示连接
1.qt cmake windows c++动态库显示连接举例
在Windows上使用Qt和CMake创建一个C++动态库,并演示如何连接和使用该动态库,可以分为以下几个步骤:
- 创建动态库项目
- 编写库的代码
- 编写CMakeLists.txt文件来生成动态库
- 创建应用程序项目并使用动态库
- 编写和配置应用程序的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)
构建和运行
- 打开CMake GUI并选择
MyApp目录作为源目录。 - 创建一个新的构建目录,例如
MyApp/build。 - 点击 "Configure" 和 "Generate" 生成构建文件。
- 打开生成的
MyApp.sln文件,并使用Visual Studio构建项目。(或者你可以直接在命令行中使用cmake --build .) - 构建完成后,运行生成的可执行文件(通常位于
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代码相同) |
但请注意,若采用显式链接方式,且希望在运行时动态载入库(而非构建时链接),则需在程序中利用LoadLibrary和GetProcAddress来手动载入库和获取函数。不过,在使用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函数返回的句柄。这个句柄在后续的函数调用中(如dlsym和dlclose)被用作对加载的库的引用。 -
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!
注意事项
- 显式连接:在Windows上,通过
__declspec(dllexport)和__declspec(dllimport)(通过Qt的宏Q_DECL_EXPORT和Q_DECL_IMPORT包装)实现显式连接。 - 路径问题:确保
MyLibrary.dll在运行时可以被找到(例如,将其放在客户端应用程序的同一目录,或设置PATH环境变量)。 - CMake导出:为了更加健壮的构建系统,可以使用CMake的
install和export功能,使得库和头文件可以被下游项目轻松使用。
这个示例展示了如何在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的
install和export功能来导出目标,因为隐式连接不需要这些。但是,如果你打算在其他项目中重用你的库,那么使用这些功能可能会很有帮助。 - 确保
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) | 包含动态库头文件,链接时指定库 |
| 优点 | 灵活性高、错误处理能力强、支持插件机制 | 使用简单、依赖管理相对简单、性能开销可能较小 |
| 缺点 | 编程复杂度增加、性能开销可能较大、依赖管理复杂 | 灵活性较低、错误处理能力较弱、启动时间可能较长 |
这个表格总结了显式链接和隐式链接在定义、加载时机、灵活性、错误处理、编程复杂度、性能开销、依赖管理以及使用场景等方面的对比。希望这能帮助您更好地理解这两种链接方式的差异和特点。
9656

被折叠的 条评论
为什么被折叠?



