目录

[toc]

简介

介绍 QT6,DLL 动态链接库的开发,开发示例

详细

DLL 入口函数

DllMain 是每个 dll 的入口函数,可以写,也可以不写,不影响其他库函数运行。如果使用此函数需要包含头文件#include <objbase.h>

BOOL WINAPI DllMain(
    HANDLE hModule, // dll 实例句柄, 也可以记作 HINSTANCE
    DWORD dwReason, // dll 当前所处状态
    LPVOID ljReserved // 保留参数,和 dll 的状态相关,使用较少
);

入口函数定义示例:

BOOL WINAPI DllMain(HANDLE hModule, DWORD dwReason, LPVOID ljReserved)
{
    switch (dwReason) {
    case DLL_PROCESS_ATTACH: // 进程加载 dll 时触发
        qDebug() << "DLL_PROCESS_ATTACH trick...";
        break;
    case DLL_PROCESS_DETACH: // 进程卸载 dll 时触发
        qDebug() << "DLL_PROCESS_DETACH trick...";
        break;
    case DLL_THREAD_ATTACH: // 线程加载 dll 时触发
        qDebug() << "DLL_THREAD_ATTACH trick...";
        break;
    case DLL_THREAD_DETACH: // 线程卸载 dll 时触发
        qDebug() << "DLL_THREAD_DETACH trick...";
        break;
    }
    return true;
}

DLL 自定义库函数

导出函数

导出函数,即为使用 dll 库时候,可以从外部调用到的函数。不声明为导出函数,只能在库内部调动,外部无法调用到未进行导出函数声明的函数。其他关键字的说明如下:

extern "[C/C++]" __declspec(dllexport) void [CALLBACK] test(); // 简单库函数声明导出函数示例
  • extern "C" 表示在用 C++ 代码调用 dll 库时候,需要声明。是由于 C 和 C++ 编译器对同一函数编译后,生成的符号表有差异,导致调用时候可能有异常,加上此声明,表示按照C语言的方式进行函数调用。如果只在 C 语言中进行使用,那么可以不写此关键字。

  • __declspec(dllexport) 声明函数为导出函数。

  • CALLBACK 如果自定义函数是 windows 窗口函数,还需要在函数声明时候使用 (表示可以被操作系统进行调用)。

导出类

几乎所有的Windows平台的C++编译器都支持从DLL中导出C++类,导出C++类与导出C函数非常相似。如果需要导出整个类,就是在类名前面使用说明符。

#define XYZAPI __declspec(dllexport)
// 导出整个 CXyz 类
class XYZAPI CXyz
{
public:
    int Foo(int n);
}

// 仅导出 Foo 方法
class CXyz
{
public:
    XYZAPI int Foo(int n);
}

默认情况下,C++编译器使用 __thiscall 的调用约定,由于不同的编译器使用的名字修饰约定不同,因此导出的 C++ 类只能被相同的编译器和相同版本的编译器使用。

导出类使用示例:

#include "XyzLibrary.h"
CXyz xyz;
xyz.Foo(21);

但是在以下情况下,编译器会警告你未导出基类和数据成员,如果要成功导出C++类,开发人员必须导出所有相关的基类和所有用于定义数据成员的类,

class Base {};
class Data {};
// 警告:未导出基类
class __declspec(dllexport) Child : Base
{
    ...
private:
    Data m_data; // 警告:未导出成员
}
优缺点

优点:该导出方式导出的类与 C++ 其他类的使用几乎相同。

缺点:

  1. 后期维护比较麻烦,导出的类可能产生非常大的依赖,导致代码耦合度高
  2. 必须保证使用同一种编译器,导出类的本质是导出类中的函数,因为语法上直接导出了类,没有对函数的调用方式,重命名进行设置,导致产生的 dll 没有通用性
  3. DLL HELL 问题

DLL Hell 是指当多个应用程序试图共享一个公用组件(如某个动态连接库(DLL)或某个组件对象模型(COM)类)时所引发的一系列问题。最典型的情况是,某个应用程序将要安装一个新版本的共享组件,而该组件与机器上的现有版本不向后兼容。虽然刚安装的应用程序运行正常,但原来依赖前一版本共享组件的应用程序也许已无法再工作。

“Side by side” 是在同一台机器上同时运行不同版本的相同组件的能力。使用支持并列的组件,编程人员不必努力维护严格的向后兼容,因为不同的应用程序自由使用某个共享组件的不同版本。

导出变量

通过 __declspec 导出声明
// 导出/导入变量声明
DLL_SAMPLE_API extern int DLLData;
用模块定义文件(.def)进行导出声明
LIBRARY DLLSample
DESCRIPTION "my simple DLL"
EXPORTS
    DLLData DATA  ;DATA表示这是数据(变量)
导出变量使用

导出变量的隐式使用示例:

#include <stdio.h>
#include "DLLSample.h"
#pragma comment(lib,"DLLSample.lib")

int main(int argc, char *argv[])
{
    printf("%d ", DLLData);
    return 0;
}

导出变量的显示使用示例:

#include <iostream>
#include <windows.h>

int main()
{
        int my_int;
        HINSTANCE hInstLibrary = LoadLibrary("DLLSample.dll");

        if (hInstLibrary == NULL)
        {
            FreeLibrary(hInstLibrary);
        }
        // 获取变量值
        my_int = *(int*)GetProcAddress(hInstLibrary, "DLLData");
        if (dllFunc == NULL)
        {
            FreeLibrary(hInstLibrary);
        }
        std::cout<<my_int;
        std::cin.get();
        FreeLibrary(hInstLibrary);
        return(1);
}
最后修改日期: 24 2 月, 2025

作者

留言

撰写回覆或留言