c/c++开发时直接调用dll动态库中的函数,无需使用头文件编译

一、开发环境

我写dome用的是Qt5.14.2

27a69b53624741e58fb88ad314bdc7a5.png

二、创建dll

1、搭建生成dll的工程

        使用Qt Creator新建一个C++动态库工程

73cd6efd59774d17ad787d70aabae380.png

 f7099259af5a47328eeea4d0f1867f32.png

 78f864d30d744920a21cfba9add72420.png

 407f560658ef4840803961f448db5dce.png

设置类名和所需的Qt库,然后直接下一步

d01756b6645c46a5a51a90a7f2bf1d21.png

 点击完成,完成工程的创建

c577ef67c8cc4712ba4940a0f72c4677.png

 生成的项目里有以下文件

965da0019703415b8fb96770850565e1.png

 需要其他的库可以直接在pro文件中进行配置

f9977f5328c942758f4d5d45b1b6d2ee.png

2、在项目文件中添加代码,实现一个可对外继承QWidget的类

.h文件,ReadDllTest 类继承自QWidget,可以使用Qt的信号与槽函数等等机制。

#include <QWidget>
#include <QString>
class __declspec(dllexport) ReadDllTest : public QWidget
{
public:
    ReadDllTest(QWidget *parent = nullptr);
    QString getObjStr();
};

.cpp文件,

ReadDllTest::ReadDllTest(QWidget *parent)
    :QWidget(parent)
{
    // 设置初始位置和大小 
    resize(100, 100);
    move(500, 200);
    // 随意打印一个字符串
    qDebug() << getObjStr();
}
// 非QWidget的标准函数 用于测试
QString ReadDllTest::getObjStr()
{
    return QString("这里是对象中的str");
}

3、使用 __declspec(dllexport) 关键字来修饰dll对外的接口,用来获取dll中的对象指针或其他资源。

// 在调用此接口时返回一个基类是QWidget的对象的指针
__declspec(dllexport) QWidget* __stdcall getObj();

函数实现

// 保存对象指针的全局变量
ReadDllTest *obj = nullptr;

// 判断全局变量是否为空 返回基类为QWidget的对象的指针
QWidget *getObj()
{
    if(!obj){
        obj = new ReadDllTest();
    }
    return obj;
}

4、点击构建后生成dll

6838faacee974ac7aaef76c8ac6fb041.png966afd1eb24747d782f89fb8edd55697.png

三、新建一个测试的工程用于加载dll

 1、随便绘制一个窗口,给按钮创建槽函数

fb8b939fddae4907ad20ab971d697287.png

 88e812c607f4474e8e673d3a1997363b.png

 2、使用windows接口加载dll库

引入接口头文件

#include <windows.h>

使用QFileDialog的接口选择dll文件,获取它的文件路径

QString filepsth = QFileDialog::getOpenFileName(this, "选择dll", "./", "dll(*.dll);");

 定义一个用于

将文件路径字符串转换为接口使用的类型

// 定义一个数组,长度根据文件路径自行判断,要够长
wchar_t filepathwc[256];

// 一定要对数组进行初始化,不然后面的步骤会出错
memset(filepathwc, 0, 128 * sizeof(wchar_t));

qDebug() << "file = " << filepsth;

// 使用QString的接口将文件名写入数组中
qDebug() << "to wc = " << filepsth.toWCharArray(filepathwc);

加载动态库

// 传入文件路径 加载dll
HMODULE module = LoadLibrary(filepathwc);
    
// 判断是否加载成功
if (module == NULL){
    qDebug() << "加载动态库失败" << GetLastError();         
}else{
    qDebug() << "加载成功";
}

 定义一个接收函数指针的函数指针类型,接口和dll的接口一致

typedef QWidget*(*GetObj)();

从dll库中获取函数地址

// 获取dll中的函数的地址
GetObj add = NULL;
// 传入动态库句柄 函数在dll中的函数名
add = (GetObj)GetProcAddress(module, "_Z6getObjv");
qDebug() << add;

获取函数地址成功后,调用函数,获取dll库中的对象,并将其显示

    // 判断函数地址是否获取成功
    if(add){
        qDebug() << "fun get success!";

        // 使用QWidget指针接收函数传来的对象
        QWidget *wids = add();

        if(wids){

            // 显示dll库中实现的窗口
            wids->show();
        }
    }else{
        qDebug() << "error = " << GetLastError();
    }

运行效果

b63993e3715f4b3a94bec4708eba0c24.png

74eca153d58446db8163eaddecc10848.png dll中的窗口显示出来了

772585a3c8794fc38b8baca2cf26cdc7.png

四、 如何查看dll中的函数名

1、打开vs的窗口应用

b1c25b972bd146ed82bfac84781da2b8.png

 cd到dll生成的位置

670fd2e84d0641d8aa59236a73d047b6.png

 使用命令

dumpbin /exports readDllTest.dll > readDllTest.txt

e8aaf21779624093b3ceaba91e684d52.png

 然后打开生成的txt文档,可以看到一个和我们定义的函数很像的字符串,主要是首位多了些字符

37ab6330e77d46a8a2c59cc3f6734b7a.png

 3a67f25d936046fb8fc09e3091ce1b50.png

 这个就是我们之前定义函数,使用txt文档中的这一串字符来获取函数名,直接使用函数名可能会获取不到

4b6c40abd4ce4f9daca819312827a903.png

 五、总结

        这种方法可以用于开发可扩展的应用程序,在程序主体已经发布的情况下,可以通过继承统一的基类来实现exe直接加载dll,无需二次编译,之后可以直接发布dll作为插件,然后在exe中编写加载管理插件模块来实现应用程序平台的动态扩展。