C / C++ 调用 DLL 的几种常见方法(详解 + 示例)
在 Windows 平台开发中,DLL(Dynamic Link Library,动态链接库) 是非常核心的一种代码复用方式。
无论是:
调用第三方 SDK
拆分大型工程
插件化设计
COM / OPC / 工控 / 驱动相关开发
都会不可避免地用到 C / C++ 调用 DLL。
本文将从 零基础视角 出发,系统讲清楚:
DLL 是什么
C/C++ 调用 DLL 的 3 种主流方式
每种方式的使用场景
常见坑点与避坑建议
一、DLL 是什么?(一句话理解)
DLL 是一个“在运行时被加载的函数集合”
和静态库(.lib)的核心区别:
项目
DLL
静态库
链接时间
运行时
编译期
文件
.dll + .lib
.lib
更新
可单独替换
需重新编译
内存
多进程共享
各自一份
二、最基础示例:先做一个 DLL
1、 编写 DLL 源码
// mydll.h
#pragma once
#ifdef MYDLL_EXPORTS
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif
extern "C" MYDLL_API int Add(int a, int b);
// mydll.cpp
#define MYDLL_EXPORTS
#include "mydll.h"
int Add(int a, int b)
{
return a + b;
}
关键点解释:
__declspec(dllexport):导出函数
__declspec(dllimport):导入函数
extern "C":防止 C++ 名字修饰(非常重要)
2、 编译生成
打开 VS 开发者命令行,在开始菜单中找到并打开:
x64 Native Tools Command Prompt for VS 2022
注意位数:
x86 DLL → 用 x86 Native Tools
x64 DLL → 用 x64 Native Tools
使用 cl.exe 生成 Debug 版 DLL(带 PDB)
最常用 Debug 编译命令(推荐)
cl /LD /Zi /Od /MDd /EHsc mydll.cpp
各参数含义,编译选项参数说明
参数
含义
/LD
生成 DLL
/Zi
生成调试信息(PDB)
/Od
关闭优化(便于调试)
/MDd
使用 Debug CRT(msvcrtd.dll)
/EHsc
标准 C++ 异常处理(推荐)
D:\test\dll>cl /LD /Zi /Od /MDd /EHsc mydll.cpp
用于 x86 的 Microsoft (R) C/C++ 优化编译器 19.50.35720 版
版权所有(C) Microsoft Corporation。保留所有权利。
mydll.cpp
Microsoft (R) Incremental Linker Version 14.50.35720.0
Copyright (C) Microsoft Corporation. All rights reserved.
/out:mydll.dll
/dll
/implib:mydll.lib
/debug
mydll.obj
D:\test\dll>
编译结果
mydll.dll ← Debug DLL
mydll.lib ← 导入库
mydll.pdb ← 调试符号文件(关键)
指定 Debug 输出目录
cl /LD /Zi /Od /MDd mydll.cpp ^
/Fe:Debug\mydll.dll ^
/Fd:Debug\mydll.pdb
生成结果:
Debug\
├── mydll.dll
├── mydll.lib
└── mydll.pdb
Release 版对照
cl /LD /O2 /MD mydll.cpp
Debug
Release
/Zi /Od /MDd
/O2 /MD
有 pdb
可选 pdb
可单步
优化后难调试
编译选项参数说明
最终你会得到:
mydll.dll
mydll.lib
三、方式一:隐式链接(最推荐,最常用)
适用场景
DLL 在开发时就确定
SDK / 内部模块
不需要动态卸载
1、调用方代码
#include
#include "mydll.h"
int main()
{
int r = Add(2, 3);
std::cout << "result = " << r << std::endl;
return 0;
}
2、工程设置(Visual Studio)
添加 lib:
项目属性
链接器 → 输入 → 附加依赖项
添加:mydll.lib
放置 dll:
mydll.dll 放在:
exe 同目录
或 PATH 目录
优点
语法最简单
编译期检查函数签名
性能最好
缺点
DLL 缺失 → 程序启动直接失败
四、方式二:显式加载(LoadLibrary / GetProcAddress)
适用场景
插件系统
可选功能
DLL 可能不存在
逆向 / 热更新 / 工控系统常用
示例代码
#include
#include
typedef int (*AddFunc)(int, int);
int main()
{
HMODULE hDll = LoadLibraryA("mydll.dll");
if (!hDll)
{
std::cout << "LoadLibrary failed" << std::endl;
return -1;
}
AddFunc Add = (AddFunc)GetProcAddress(hDll, "Add");
if (!Add)
{
std::cout << "GetProcAddress failed" << std::endl;
FreeLibrary(hDll);
return -1;
}
int r = Add(3, 4);
std::cout << "result = " << r << std::endl;
FreeLibrary(hDll);
return 0;
}
关键注意点
函数名必须是导出名
C++ 函数如果没有 extern "C",名字会被修饰
函数指针签名必须完全一致,可以使用下面的命令查看函数指针签名
dumpbin /exports mydll.dll
调用约定要一致(__cdecl / __stdcall)
优点
程序可在 DLL 缺失时继续运行
灵活性极高
缺点
写法复杂
容易崩溃(函数签名不匹配)
五、方式三:DLL + .lib,但运行时可选(进阶)
这是 方式一和方式二的混合体:
编译期链接 .lib
运行时通过 LoadLibrary 判断是否可用
适合大型工程 / 插件化 SDK。
六、C++ 类如何导出 / 调用?
1、DLL 导出类(示例)
class __declspec(dllexport) MyClass
{
public:
int Add(int a, int b);
};
不推荐直接跨 DLL 导出 C++ 类,原因:
ABI 不稳定
编译器 / STL 版本耦合
内存管理容易炸
工程实践建议:
DLL 对外只暴露 C 接口,内部再用 C++
七、常见坑点总结(新手必看)
1、忘记 extern "C"
extern "C" __declspec(dllexport) int Func();
否则 GetProcAddress 很容易失败。
2、32 位 / 64 位不匹配
32 位 exe ❌ 加载 64 位 dll
会直接 LoadLibrary 失败
用 dumpbin /headers xxx.dll 查看位数
3、 调用约定不一致
__stdcall vs __cdecl
不一致 → 栈损坏 → 程序随机崩溃
4、 DLL 放置路径错误
Windows 查找 DLL 顺序:
exe 同目录
系统目录
PATH 环境变量
八、如何查看 DLL 导出函数?(实用技巧)
dumpbin /exports mydll.dll
可以看到:
函数名
序号
是否被修饰
这是调试 GetProcAddress 的神器。
九、实际工程建议(经验总结)
新项目:优先 extern "C" + 隐式链接
插件系统:显式加载
不要跨 DLL 传 STL 容器
不要在不同 CRT 中 free 内存
十、总结一句话
DLL 是 Windows 下模块化的核心能力,
会用 DLL,才算真正进入 C/C++ 工程开发。