动态链接库
TIP
本文介绍动态链接库的概念、作用和使用方法。分别详细举例在QT、visual stdio、g++中导出/导入动态链接库的方法。
lib是什么
你有一台台式机 有一天你发现配置不够了要换显卡 你得把电脑都拆了 拆下旧显卡 换上新显卡 再重新组装。
你有一个项目,里面有一个lib, 有一天出问题了 排查发现是这个lib里面的代码有问题, 你得换新的lib 然后重新编译整个项目 才能重新运行。
dll是什么
你有一台台式机,它留了个外接显卡接口,你有一天想玩3A, 发现显卡带不动了,你关机,换个新的显卡,再开机,打开3A发现能玩了。
你有一个项目,它启动时读取目录下的某个dll,然后正常运行。有一天需要升级这个dll,把项目进程关了,换上新的dll,再重新打开项目运行即可。
测试代码
#ifndef EXAMPLE_H
#define EXAMPLE_H
void myFunction();
#endif#include "lib.h"
#include <iostream>
void myFunction() {
std::cout << "Hello from myFunction!" << std::endl;
}#include<iostream>
#include "lib.h"
using namespace std;
int main(){
myFunction();
cout<<"\n";
return 0;
}g++
直接整体编译
.cpp文件在一块编译
生成可执行文件
编译 :g++ -o m main.cpp lib.cpp
运行 :m
dll
编译成dll : g++ -shared lib.cpp -o lib.dll
链接生成可执行文件: g++ -L. -llib main.cpp -o m
运行 m.exe
修改lib.cpp中的函数
void myFunction() {
std::cout << "修改过后 " << std::endl;
}重新编译dll : g++ -shared lib.cpp -o lib.dll
运行m.exe 输出 "修改过后 "
lib
编译成lib: g++ -c lib.cpp -o lib.o //-c表示不生成可执行文件 ar rcs liblib.lib lib.o
此时目录下生成了一个liblib.lib文件
编译链接静态库 生成可执行文件: g++ -o m main.cpp -L. -lliblib
-L后面的.表示当前目录
-l 后面跟具体的库名 去掉后缀.lib
运行 m.exe
如果你没有显式地使用这些导出关键字,仍然可能能够创建工作的动态链接库。这是因为编译器和链接器有一些默认规则。
在C++中,导出关键字通常是指在Windows平台上使用的 __declspec(dllexport) 和 __declspec(dllimport)。
visual stdio(msvc)
直接整体编译

没问题
lib

生成得到静态库

加入main文件
#pragma comment导入静态库 也可以在属性里配置

把我们编译出来的静态库移到main.cpp目录下

删掉项目中的lib.cpp文件 可以运行

总结 使用静态库lib 静态链接生成exe文件 我们只需要给使用者提供.h和.lib文件 不需要提供.cpp文件 这样隐藏了代码的实现细节 但是相对于dll 链接出来的exe文件很大 并且如果某一.cpp改代码 需要重新编译该x.cpp文件变成x.lib 然后替换项目中旧的x.lib 重新整体编译项目 生成可执行文件 所以我们下面换成dll 这样方便
dll
设置配置类型

标识要导出的函数



生成项目 在Debug目录里生成dll文件

QT
创建个新项目 添加dlla.h dlla.cpp文件
打开.pro文件


dlla.h
#ifndef DLLA_H
#define DLLA_H
#define EXPROT
#ifdef EXPROT
#define DLL_EXPROT __declspec(dllexport)
#else
#define DLL_EXPROT __declspec(dllimport)
#endif
//注意 如果使用Q_DECL_EXPORT宏 需要include <QObject>
//#ifdef EXPROT
// #define DLL_EXPROT Q_DECL_EXPORT
//#else
// #define DLL_EXPROT Q_DECL_EXPORT
//#endif
//导出函数
void DLL_EXPROT test01();
void DLL_EXPROT test02();
void DLL_EXPROT test03();
//想要直接从dll里获取类信息 进而构造一个实例 很困难
class DLL_EXPROT DllA //如果使用静态库 一定要导出这个类 不然就“无法解析外部符号”
{
public:
DllA();
DllA(int i);
};
// 我们用一个工厂函数 来读取dll构造类实例 不同构造函数例子
extern "C" DLL_EXPROT DllA* Instance();
extern "C" DLL_EXPROT DllA* Instance2(int i);
#endif // DLLA_Hdlla.cpp
#include "dlla.h"
DllA::DllA()
{
}
DllA::DllA(int i)
{
}
void test01()
{
}
void test02()
{
}
void test03()
{
}
DllA *Instance()
{
return new DllA();
}
DllA *Instance2(int i)
{
return new DllA(i);
}构建以后 可以在导出目录看到被导出的.lib和.dll

使用lib
use_dll_lib.pro
QT -= gui
CONFIG += c++11 console
CONFIG -= app_bundle
INCLUDEPATH += E:/Learning/QT/code/dll_lib
#LIBS += -lE:/Learning/QT/code/use_dll_lib/dll_lib
DEFINES += QT_DEPRECATED_WARNINGS
SOURCES += \
main.cpp
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += targetmain.cpp
#include <QCoreApplication>
#include <dlla.h>
#pragma comment(lib,"E:/Learning/QT/code/use_dll_lib/dll_lib.lib")
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
DllA dlla;
return a.exec();
}使用dll
main.cpp
#include <QCoreApplication>
#include <dlla.h>
#include <QLibrary>
#include <iostream>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
//加载dll
QLibrary* dll = new QLibrary("E:/Learning/QT/code/use_dll_lib/dll_lib.dll");
DllA* dlla;
//dll存在 并且加载成功
if(dll && dll->load()){
//定义函数指针
typedef DllA* (*MyPointer)(int i);
MyPointer instance2 = (MyPointer)dll->resolve("Instance2");
if(instance2){
dlla = instance2(2);
}
}
delete dll;
return a.exec();
}输出

dumpbin工具的使用
如果查看a.dll库中包含哪些函数,可以使用:dumpbin /exports a.dll >1.txt
如果查看b.exe中加载了哪些动态库,可以使用:dumpbin /imports b.exe >2.txt
如果查看c.lib中包含哪些函数,可以使用:dumpbin /all /rawdata:none c.lib >3.txt
如果查看d.obj中包含哪些函数,可以使用:dumpbin /all /rawdata:none d.obj >4.txt
用dumpbin查看dll文件
新建一个项目, 新建一个 main.cpp文件 导入相关.h文件
打开dumpbin查看被MSVC修饰过的函数名
dumpbin可以在visual stdio目录下找到
例如我的C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.29.30133\bin\HostX64\x64
打开cmd
dumpbin.exe /exports 你的dll路径.dll

回车 可以看到刚刚导出后 被msvc修饰的函数名 和类名

C++程序加载导出的dll
#include <iostream>
#include <Windows.h> //使用函数和某些特殊变量
#include "lib2.h" // 包含你的DLL中类的头文件
#include <functional>
using namespace std;
int main()
{
const char* dllname = "C:/Users/Administrator/Desktop/dll/ConsoleApplication1/x64/Debug/ddq.dll"; // 加载.dll
HMODULE hDLL = LoadLibrary(dllname);
if (hDLL != NULL)
{
typedef void (*FUN)(); //定义一个函数指针,确定调用函数的形参
FUN fp = FUN(GetProcAddress(hDLL, "?myFunction@@YAXXZ")); //填入被修饰过的函数名
if (fp != NULL)
{
fp();
}
typedef lib2 *(*Class)();
Class cla = Class(GetProcAddress(hDLL, "Instance")); //继续做,不要问
if (cla)
{
lib2* plugin = cla();//相当于Instance()
plugin->Func();
}
else {
std::cout << "asd\n";
}
FreeLibrary(hDLL);
}
else
cout << "Can not find2: " << dllname;
return 0;
}补充说明 extern "C" 可以让被导出的函数 函数名不被编译器修饰
动态库实战
visual stdio 与 QT
不安装vs的qt插件,使用visual stdio 调用QT的API来写QT程序
创建一个vs项目 创一个main.cpp 写上这串代码
#include <iostream>
#include "QtCore/qdebug.h"
#include "QtWidgets/qapplication.h"
#include "QtWidgets/qwidget.h"
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
QWidget aa;
aa.show();
qDebug() << "adsa";
std::cout << "Hello World!\n";
a.exec();
}右键熟悉 去c++中的附加包含目录里配置.h文件的路径

找到你QT安装目录下 msvc套件里的include 目的是让vs能找到.h文件
接下来我们静态链接.lib文件 找到lib目录

在该目录打开cmd
输入dir /s /b *.lib 找到该目录下所有的.lib文件


把这串lib的路径 配置到附加依赖项

生成成功

运行 发现找不到dll文件

原因是

所以我们到msvc的bin目录下找到Qt5Core.dll文件 复制到你生成的可执行文件exe目录下
或者你也可以把这个bin目录配到环境变量里(更方便)

还有一步 就是把这个文件夹 也拷贝到你的exe所在目录下

里边也是dll 或者你也可以配环境变量 随你
当前目录

反正缺什么你就去qt安装目录里拷什么 或者配环境变量 就这么简单

运行结果

通过这篇文章 你应该对dll lib 和如何导入第三方库比较熟悉了