在 C++ 中,动态库(Dynamic Link Library, DLL)允许模块化开发,运行时加载共享代码。以下是 动态库的导出与使用 的详细步骤:
1. 动态库(DLL)的导出
(1) 定义导出宏
在头文件中定义一个宏,用于控制 __declspec(dllexport)
(编译 DLL 时)和 __declspec(dllimport)
(使用 DLL 时):
cpp
// MyDLL.h
#ifdef MYDLL_EXPORTS
#define MYDLL_API __declspec(dllexport) // 编译 DLL 时导出
#else
#define MYDLL_API __declspec(dllimport) // 使用 DLL 时导入
#endif
(2) 导出函数/类
在 DLL 项目中,使用 MYDLL_API
标记要导出的函数或类:
cpp
// MyDLL.h
MYDLL_API int Add(int a, int b); // 导出函数
class MYDLL_API MyClass { // 导出类
public:
MyClass();
int Multiply(int a, int b);
};
(3) 实现 DLL 函数
cpp
// MyDLL.cpp
#include "MyDLL.h"
int Add(int a, int b) {
return a + b;
}
MyClass::MyClass() {}
int MyClass::Multiply(int a, int b) {
return a * b;
}
(4) 编译生成 DLL
-
Visual Studio:
- 右键项目 -> 属性 -> 配置类型 设为 "动态库(.dll)"。
- 编译后生成
MyDLL.dll
和MyDLL.lib
(导入库)。
-
CMake:
cmakeadd_library(MyDLL SHARED MyDLL.cpp MyDLL.h)
2. 使用动态库
(1) 包含头文件
cpp
#include "MyDLL.h" // 确保头文件路径正确
(2) 链接导入库(.lib)
-
方法 1:项目属性配置(推荐)
- 右键项目 -> 属性 -> 链接器 -> 输入 -> 附加依赖项 ,添加
MyDLL.lib
。 - 链接器 -> 常规 -> 附加库目录 ,添加
.lib
所在路径。
- 右键项目 -> 属性 -> 链接器 -> 输入 -> 附加依赖项 ,添加
-
方法 2:
#pragma comment
(仅 MSVC)cpp#pragma comment(lib, "MyDLL.lib")
(3) 运行时加载 DLL
确保 MyDLL.dll
位于:
- 可执行文件(
.exe
)所在目录,或 - 系统
PATH
环境变量包含的路径。
3. 动态加载(运行时显式加载)
如果不想静态链接 .lib
,可以使用 LoadLibrary
+ GetProcAddress
动态加载:
cpp
#include <windows.h>
typedef int (*AddFunc)(int, int); // 定义函数指针类型
int main() {
HMODULE hDll = LoadLibrary(L"MyDLL.dll");
if (!hDll) {
std::cerr << "Failed to load DLL!" << std::endl;
return 1;
}
// 获取函数指针
AddFunc add = (AddFunc)GetProcAddress(hDll, "Add");
if (!add) {
std::cerr << "Failed to find function!" << std::endl;
FreeLibrary(hDll);
return 1;
}
int result = add(3, 4); // 调用 DLL 函数
std::cout << "Result: " << result << std::endl;
FreeLibrary(hDll); // 释放 DLL
return 0;
}
4. 常见问题
(1) 无法解析的外部符号(LNK2019)
- 原因 :未正确链接
.lib
或__declspec(dllimport)
未正确应用。 - 解决 :
- 检查
MYDLL_API
是否正确定义。 - 确保
.lib
路径正确。
- 检查
(2) 找不到 DLL(运行时错误)
- 原因 :
DLL
不在PATH
或.exe
目录。 - 解决 :
- 将
DLL
复制到.exe
所在目录。 - 或修改
PATH
环境变量。
- 将
(3) 名称修饰(Name Mangling)问题
- C++ 函数名会被编译器修饰 (如
?Add@@YAHHH@Z
),导致GetProcAddress
失败。 - 解决 :
-
使用
extern "C"
避免修饰:cppextern "C" MYDLL_API int Add(int a, int b);
-
或使用
.def
文件显式导出名称。
-
5. 跨平台兼容(Linux/macOS)
在 Linux/macOS 上,动态库是 .so
(Shared Object)或 .dylib
,使用 __attribute__((visibility("default")))
导出:
cpp
// MyDLL.h
#ifdef __GNUC__
#define MYDLL_API __attribute__((visibility("default")))
#else
#define MYDLL_API __declspec(dllexport)
#endif
编译:
bash
g++ -shared -fPIC -o libMyDLL.so MyDLL.cpp
使用:
cpp
#include <dlfcn.h>
void* handle = dlopen("libMyDLL.so", RTLD_LAZY);
auto add = (AddFunc)dlsym(handle, "Add");
总结
步骤 | Windows (DLL) | Linux/macOS (SO) |
---|---|---|
导出宏 | __declspec(dllexport) |
__attribute__((visibility("default"))) |
导入宏 | __declspec(dllimport) |
(通常不需要) |
编译 | cl /LD MyDLL.cpp |
g++ -shared -fPIC -o libMyDLL.so MyDLL.cpp |
加载方式 | LoadLibrary + GetProcAddress |
dlopen + dlsym |
正确导出和使用动态库可以大幅提高代码的模块化和复用性。