0

0

如何在C++中创建一个动态链接库_C++动态链接库(DLL)创建与使用

裘德小鎮的故事

裘德小鎮的故事

发布时间:2025-09-22 16:34:01

|

1053人浏览过

|

来源于php中文网

原创

创建C++动态链接库需使用__declspec(dllexport)标记导出函数,并通过头文件和宏定义区分导出与导入,编译生成.dll和.lib文件;使用时可通过隐式链接(自动加载)或显式链接(运行时动态加载)调用DLL功能,解决模块化、代码复用与内存效率问题。

如何在c++中创建一个动态链接库_c++动态链接库(dll)创建与使用

在C++中创建一个动态链接库(DLL)的核心在于,你需要明确地告诉编译器哪些函数或类是打算被外部程序调用的。这通常通过特定的关键字实现,例如在Windows平台上是

__declspec(dllexport)
。然后,将这些带有导出标记的代码编译成一个特殊的二进制文件(.dll),同时通常会生成一个对应的导入库(.lib),供其他程序在链接时使用。

解决方案

要创建一个C++动态链接库,我们通常会经历以下几个步骤。这不仅仅是技术操作,更是一种模块化思维的体现,把你的功能封装起来,让其他项目能够方便地复用。

首先,你需要定义你的DLL接口。这通常在一个头文件(例如

MyDLL.h
)中完成。在这个头文件中,你需要使用
__declspec(dllexport)
来标记那些你希望从DLL中导出的函数、类或变量。同时,为了让使用DLL的客户端程序能够正确导入这些符号,它们需要使用
__declspec(dllimport)
。为了避免在DLL内部编译时也使用
dllimport
,我们通常会定义一个宏来区分。

MyDLL.h
示例:

立即学习C++免费学习笔记(深入)”;

#ifdef MYDLL_EXPORTS
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif

// 确保C++编译器不会对这些函数进行名称修饰
// 这对于C语言或其他语言调用DLL函数尤其重要
extern "C" {
    MYDLL_API int Add(int a, int b);
    MYDLL_API void PrintMessage();
}

// 导出C++类,需要注意名称修饰问题
class MYDLL_API MyClass {
public:
    MyClass();
    void Greet();
    int Multiply(int a, int b);
};

接下来,在你的源文件(例如

MyDLL.cpp
)中实现这些函数和类。在编译这个源文件时,你需要定义
MYDLL_EXPORTS
这个宏,这样
MYDLL_API
就会被扩展为
__declspec(dllexport)

MyDLL.cpp
示例:

#define MYDLL_EXPORTS // 编译DLL时定义此宏

#include "MyDLL.h"
#include 

extern "C" {
    int Add(int a, int b) {
        return a + b;
    }

    void PrintMessage() {
        std::cout << "Hello from MyDLL!" << std::endl;
    }
}

MyClass::MyClass() {
    std::cout << "MyClass instance created in DLL." << std::endl;
}

void MyClass::Greet() {
    std::cout << "Greetings from MyClass in DLL!" << std::endl;
}

int MyClass::Multiply(int a, int b) {
    return a * b;
}

最后一步是编译。 如果你使用 MSVC (Visual Studio)

  1. 创建一个“动态链接库(DLL)”项目。
  2. MyDLL.h
    MyDLL.cpp
    添加到项目中。
  3. 编译项目,它会生成
    MyDLL.dll
    MyDLL.lib

如果你使用 MinGW/GCC: 你可以使用命令行编译。

g++ -c MyDLL.cpp -o MyDLL.o -D MYDLL_EXPORTS # 编译源文件
g++ -shared -o MyDLL.dll MyDLL.o -Wl,--out-implib,MyDLL.lib # 链接生成DLL和导入库

这里的

-D MYDLL_EXPORTS
就是定义了那个宏。
-shared
告诉GCC生成一个共享库(DLL)。
-Wl,--out-implib,MyDLL.lib
是链接器选项,用于生成导入库。

至此,你的DLL就创建完成了。接下来的挑战就是如何让其他应用程序正确地使用它。

为什么我们要用动态链接库?(以及它到底解决了什么痛点)

说实话,我最初接触DLL的时候,觉得它把事情搞得有点复杂,多了一个文件,多了一些编译步骤。但随着项目规模的增长,我才真正体会到它的价值。最核心的,DLL解决了软件开发的“模块化”和“资源共享”两大痛点。

想象一下,你有一个庞大的应用程序,里面有几十个甚至上百个模块。如果所有代码都编译成一个巨大的可执行文件,每次修改哪怕一个很小的功能,你都得重新编译整个程序,这效率简直是灾难。DLL就像是把这些模块拆分成独立的“积木”,每个积木可以独立开发、独立编译、独立更新。我个人觉得最舒服的,就是那种项目变得可插拔的感觉。比如,我可以为我的应用程序开发一个插件系统,每个插件就是一个DLL,用户可以根据需要加载或卸载,而不需要我重新发布整个应用程序。这极大地提升了软件的灵活性和可维护性。

再者,资源共享也是一个大头。多个应用程序如果都使用了同一套底层功能(比如一个图形渲染库或者一个数据库访问模块),如果这些功能都静态链接到每个应用程序里,那么每个应用程序运行时都会在内存中加载一份相同的代码副本。而使用DLL,这份代码只需要在内存中加载一次,所有使用它的程序都可以共享。这在系统资源有限的情况下,能显著减少内存占用,提升系统整体效率。虽然现代计算机内存普遍较大,但这依然是一个优雅的解决方案,尤其是在部署和分发时,可以减少应用程序的体积。

创建DLL时,有哪些“坑”是新手常踩的?

创建DLL这事儿,看起来直截了当,但实际操作起来,总有些小细节能把人搞得头大。我刚开始的时候,就没少在这些“坑”里打滚。

最大的一个问题,我觉得就是导出符号问题。你明明写了函数,也编译了DLL,结果调用方就是“找不到符号”。这多半是忘记了

__declspec(dllexport)
,或者在头文件里没有正确区分
dllexport
dllimport
。编译器并不会自动导出所有函数,你得明确告诉它哪些是“公共接口”。还有就是C++的名称修饰(Name Mangling),这是个隐形杀手。C++为了支持函数重载和命名空间,编译器会把函数名和参数类型编码成一个内部名称。比如
int Add(int a, int b)
可能被修饰成
?Add@@YAHHH@Z
(在MSVC上)。如果你不使用
extern "C"
,外部程序,特别是那些非C++编写的程序,就很难找到正确的函数入口点。所以,对于打算被C语言或其他语言调用的函数,务必加上
extern "C"
,强制编译器使用C语言的链接约定,避免名称修饰。

MVM mall 网上购物系统
MVM mall 网上购物系统

采用 php+mysql 数据库方式运行的强大网上商店系统,执行效率高速度快,支持多语言,模板和代码分离,轻松创建属于自己的个性化用户界面 v3.5更新: 1).进一步静态化了活动商品. 2).提供了一些重要UFT-8转换文件 3).修复了除了网银在线支付其它支付显示错误的问题. 4).修改了LOGO广告管理,增加LOGO链接后主页LOGO路径错误的问题 5).修改了公告无法发布的问题,可能是打压

下载

另一个常见的陷阱是调用约定不匹配。在Windows上,常见的有

__cdecl
__stdcall
__cdecl
是C/C++的默认调用约定,调用者负责清理
__stdcall
则由被调用者清理栈,常用于Windows API。如果DLL导出的函数是
__stdcall
,而你的调用代码期望的是
__cdecl
,那么栈就会出现问题,轻则程序崩溃,重则出现难以追踪的内存错误。

再来就是运行时库不一致。如果你用Visual Studio开发,Debug版本和Release版本,或者使用MT(静态链接运行时库)和MD(动态链接运行时库)的DLL和调用程序,它们的运行时库版本必须一致。否则,当你尝试在DLL和主程序之间传递STL对象(如

std::string
std::vector
)时,就可能出现内存分配和释放不匹配的问题,导致程序崩溃或数据损坏。这不是编译器错误,而是运行时环境的冲突,排查起来非常麻烦。

最后,别忘了DLL Hell。虽然现在Windows系统在这方面做得更好了,但不同版本的DLL文件可能会相互覆盖,导致某些程序崩溃。这提醒我们,部署DLL时要特别小心,尽量使用独立的应用程序目录,或者确保版本兼容性。

如何在C++应用程序中正确加载和使用DLL?

创建好DLL之后,下一步就是如何在你的C++应用程序中“消费”它。这主要有两种方式:隐式链接和显式链接。我通常会根据项目需求来决定用哪种方式,各有优劣。

1. 隐式链接(Implicit Linking)

这是最简单、最常用的方式,感觉就像在用一个普通的静态库一样。在编译你的应用程序时,你需要告诉链接器去链接DLL对应的导入库(

.lib
文件)。当应用程序启动时,操作系统会自动加载DLL文件到内存中。

步骤:

  1. 包含头文件: 在你的应用程序代码中包含DLL的头文件(
    MyDLL.h
    )。确保头文件中的
    MYDLL_API
    宏被正确地扩展为
    __declspec(dllimport)
    (即不要定义
    MYDLL_EXPORTS
    )。
  2. 链接导入库: 在你的项目设置中,将DLL生成的
    .lib
    文件添加到链接器的输入中。
    • MSVC: 在项目属性 -> 链接器 -> 输入 -> 附加依赖项中添加
      MyDLL.lib
    • GCC/MinGW: 在编译命令中加入
      -L -lMyDLL
    • 或者,在代码中使用
      #pragma comment(lib, "MyDLL.lib")
      (仅限MSVC)。
  3. 部署DLL: 确保
    MyDLL.dll
    文件在应用程序的可执行文件同目录下,或者在系统的PATH环境变量指定的路径中,以便操作系统能够找到并加载它。

示例代码:

// 应用程序代码 (MyApp.cpp)
#include "MyDLL.h" // 此时MYDLL_API 会被定义为 __declspec(dllimport)
#include 

#pragma comment(lib, "MyDLL.lib") // 告诉MSVC链接MyDLL.lib

int main() {
    // 调用DLL导出的C函数
    int result = Add(5, 3);
    std::cout << "Add(5, 3) = " << result << std::endl;
    PrintMessage();

    // 使用DLL导出的C++类
    MyClass myObj;
    myObj.Greet();
    int product = myObj.Multiply(4, 2);
    std::cout << "Multiply(4, 2) = " << product << std::endl;

    return 0;
}

优点: 使用起来非常方便,就像调用本地函数一样自然。 缺点: 应用程序在启动时就强依赖于DLL。如果DLL文件不存在或版本不匹配,应用程序将无法启动。

2. 显式链接(Explicit Linking)

这种方式更为灵活,你可以在运行时动态地加载DLL,并在不需要时卸载它。这对于实现插件系统或者处理可选功能模块非常有用。

步骤:

  1. 加载DLL: 使用
    LoadLibrary
    (Windows) 或
    dlopen
    (Linux/macOS) 函数加载DLL。
  2. 获取函数地址: 使用
    GetProcAddress
    (Windows) 或
    dlsym
    (Linux/macOS) 函数获取你想要调用的函数或类的入口点。这需要你将函数指针声明为正确的类型。
  3. 调用函数: 通过获取到的函数指针进行调用。
  4. 卸载DLL: 当不再需要DLL时,使用
    FreeLibrary
    (Windows) 或
    dlclose
    (Linux/macOS) 卸载它。

示例代码 (Windows):

// 应用程序代码 (MyApp_Explicit.cpp)
#include  // For LoadLibrary, GetProcAddress, FreeLibrary
#include 

// 定义函数指针类型,匹配DLL中导出的函数签名
typedef int (*AddFunc)(int, int);
typedef void (*PrintMessageFunc)();
// 对于C++类,显式链接会更复杂,通常会导出工厂函数来创建对象
// 比如:typedef MyClass* (*CreateMyClassFunc)();
//       typedef void (*DestroyMyClassFunc)(MyClass*);


int main() {
    HMODULE hDLL = LoadLibrary(TEXT("MyDLL.dll")); // 加载DLL

    if (hDLL != NULL) {
        // 获取Add函数的地址
        AddFunc pAdd = (AddFunc)GetProcAddress(hDLL, "Add"); // 注意:这里是原始的函数名,因为我们使用了extern "C"

        // 获取PrintMessage函数的地址
        PrintMessageFunc pPrintMessage = (PrintMessageFunc)GetProcAddress(hDLL, "PrintMessage");

        if (pAdd != NULL && pPrintMessage != NULL) {
            int result = pAdd(10, 20);
            std::cout << "Explicit Link: Add(10, 20) = " << result << std::endl;
            pPrintMessage();
        } else {
            std::cerr << "Error: Could not find function in DLL." << std::endl;
        }

        FreeLibrary(hDLL); // 卸载DLL
    } else {
        std::cerr << "Error: Could not load MyDLL.dll. GetLastError(): " << GetLastError() << std::endl;
    }

    return 0;
}

优点: 极高的灵活性,可以动态加载/卸载DLL,实现插件、热更新等功能。应用程序启动时不会强依赖DLL,即使DLL不存在也能正常启动(只是相关功能不可用)。 缺点: 代码相对复杂,需要手动管理DLL的生命周期,并且对于C++类和其成员函数的导出和调用,处理起来会更加繁琐(通常需要导出工厂函数和销毁函数)。

选择哪种方式,真的得看你的具体场景。如果是一个核心功能,DLL是应用程序不可或缺的一部分,隐式链接会更方便。如果是一个可插拔的模块,或者你需要在运行时决定是否加载某个功能,那么显式链接就是你的不二之选。

相关专题

更多
C语言变量命名
C语言变量命名

c语言变量名规则是:1、变量名以英文字母开头;2、变量名中的字母是区分大小写的;3、变量名不能是关键字;4、变量名中不能包含空格、标点符号和类型说明符。php中文网还提供c语言变量的相关下载、相关课程等内容,供大家免费下载使用。

397

2023.06.20

c语言入门自学零基础
c语言入门自学零基础

C语言是当代人学习及生活中的必备基础知识,应用十分广泛,本专题为大家c语言入门自学零基础的相关文章,以及相关课程,感兴趣的朋友千万不要错过了。

618

2023.07.25

c语言运算符的优先级顺序
c语言运算符的优先级顺序

c语言运算符的优先级顺序是括号运算符 > 一元运算符 > 算术运算符 > 移位运算符 > 关系运算符 > 位运算符 > 逻辑运算符 > 赋值运算符 > 逗号运算符。本专题为大家提供c语言运算符相关的各种文章、以及下载和课程。

354

2023.08.02

c语言数据结构
c语言数据结构

数据结构是指将数据按照一定的方式组织和存储的方法。它是计算机科学中的重要概念,用来描述和解决实际问题中的数据组织和处理问题。数据结构可以分为线性结构和非线性结构。线性结构包括数组、链表、堆栈和队列等,而非线性结构包括树和图等。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

258

2023.08.09

c语言random函数用法
c语言random函数用法

c语言random函数用法:1、random.random,随机生成(0,1)之间的浮点数;2、random.randint,随机生成在范围之内的整数,两个参数分别表示上限和下限;3、random.randrange,在指定范围内,按指定基数递增的集合中获得一个随机数;4、random.choice,从序列中随机抽选一个数;5、random.shuffle,随机排序。

600

2023.09.05

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

525

2023.09.20

c语言get函数的用法
c语言get函数的用法

get函数是一个用于从输入流中获取字符的函数。可以从键盘、文件或其他输入设备中读取字符,并将其存储在指定的变量中。本文介绍了get函数的用法以及一些相关的注意事项。希望这篇文章能够帮助你更好地理解和使用get函数 。

641

2023.09.20

c数组初始化的方法
c数组初始化的方法

c语言数组初始化的方法有直接赋值法、不完全初始化法、省略数组长度法和二维数组初始化法。详细介绍:1、直接赋值法,这种方法可以直接将数组的值进行初始化;2、不完全初始化法,。这种方法可以在一定程度上节省内存空间;3、省略数组长度法,这种方法可以让编译器自动计算数组的长度;4、二维数组初始化法等等。

601

2023.09.22

Java编译相关教程合集
Java编译相关教程合集

本专题整合了Java编译相关教程,阅读专题下面的文章了解更多详细内容。

9

2026.01.21

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
C# 教程
C# 教程

共94课时 | 7.2万人学习

C 教程
C 教程

共75课时 | 4.1万人学习

C++教程
C++教程

共115课时 | 13.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号