Skip to content

动态链接库 ​

TIP

本文介绍动态链接库的概念、作用和使用方法。分别详细举例在QT、visual stdio、g++中导出/导入动态链接库的方法。

lib是什么 ​

你有一台台式机 有一天你发现配置不够了要换显卡 你得把电脑都拆了 拆下旧显卡 换上新显卡 再重新组装。

你有一个项目,里面有一个lib, 有一天出问题了 排查发现是这个lib里面的代码有问题, 你得换新的lib 然后重新编译整个项目 才能重新运行。

dll是什么 ​

你有一台台式机,它留了个外接显卡接口,你有一天想玩3A, 发现显卡带不动了,你关机,换个新的显卡,再开机,打开3A发现能玩了。

你有一个项目,它启动时读取目录下的某个dll,然后正常运行。有一天需要升级这个dll,把项目进程关了,换上新的dll,再重新打开项目运行即可。

测试代码 ​

cpp
#ifndef EXAMPLE_H
#define EXAMPLE_H

void myFunction();

#endif
cpp
#include "lib.h"
#include <iostream>

void myFunction() {
    std::cout << "Hello from myFunction!" << std::endl;
}
cpp
#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中的函数

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) ​

直接整体编译 ​

image-20240130092352504

没问题

lib ​

image-20240130092645917

生成得到静态库

image-20240130092927729

加入main文件

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

image-20240130093010334

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

image-20240130093052145

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

image-20240130093231182

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

dll ​

设置配置类型

image-20240130093708027

标识要导出的函数

image-20240131093022827

image-20240131093031677

image-20240131093105630

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

image-20240130094658386

QT ​

创建个新项目 添加dlla.h dlla.cpp文件

打开.pro文件

image-20240131104221979

image-20240131104208892

dlla.h

cpp
#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_H

dlla.cpp

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

image-20240131104321645

使用lib ​

use_dll_lib.pro

cmake
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 += target

main.cpp

c++
#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

c++
#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();
}

输出

image-20240131113313958

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

image-20240131094022427

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

image-20240131094051605

C++程序加载导出的dll ​

cpp
#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 写上这串代码

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文件的路径

image-20240201152030198

找到你QT安装目录下 msvc套件里的include 目的是让vs能找到.h文件

image-20240201152111953

接下来我们静态链接.lib文件 找到lib目录

image-20240201152152065

在该目录打开cmd

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

image-20240201152321297

image-20240201152347732

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

image-20240201152428794

生成成功

image-20240201152504595

运行 发现找不到dll文件

image-20240201152520157

原因是

image-20240201152540082

所以我们到msvc的bin目录下找到Qt5Core.dll文件 复制到你生成的可执行文件exe目录下

或者你也可以把这个bin目录配到环境变量里(更方便)

image-20240201152642794

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

image-20240201152934356

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

当前目录

image-20240201153011914

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

image-20240201153124554

运行结果

image-20240201153142165

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

学 习 记 录