C++动态库和静态库的生成和使用
DLL的生成
头文件
#ifndef TEST_H
#define TEST_H#include<iostream>
#include<stdexcept>
using namespace std;#ifndef TESTAPI
#define TESTAPI __declspec(dllexport)
#else
#define TESTAPI __declspec(dllimport)
#endif extern "C" {TESTAPI int add(int a, int b);TESTAPI int sub(int a, int b);TESTAPI int mul(int a, int b);TESTAPI int div_(int a, int b);
}#endif // ! TEST_H
为什么用TESTAPI宏呢,便于后续使用接口
可以看到此时dllimport是灰色的,导出接口dllexport生效
源文件
#include"test.h"int add(int a, int b) {return a + b;
}int sub(int a, int b) {return a - b;
}int mul(int a, int b) {return a * b;
}int div_(int a, int b) {try {if (b == 0) {throw std::runtime_error("Division by zero error!"); // 抛出异常}return a / b;}catch (const std::runtime_error& e) {cout << "Error: " << e.what() << endl; // 捕获并处理异常return 0; // 或根据需求返回其他值}
}
配置类型DLL
生成DLL
‘
DLL的调用法一
把生成的test1.dll放在测试工程test_useDll的目录下
调用DLL中的接口
#include <iostream>
#include <windows.h>
using namespace std;// 声明函数指针
typedef int(*AddFunc)(int, int);
typedef int(*SubFunc)(int, int);
typedef int(*MulFunc)(int, int);
typedef int(*DivFunc)(int, int);int main() {// 加载DLLHINSTANCE hDLL = LoadLibraryA("test1.dll");if (!hDLL) {cout << "Failed to load DLL." << endl;return 1;}// 获取函数地址AddFunc add = (AddFunc)GetProcAddress(hDLL, "add");SubFunc sub = (SubFunc)GetProcAddress(hDLL, "sub");MulFunc mul = (MulFunc)GetProcAddress(hDLL, "mul");DivFunc div_ = (DivFunc)GetProcAddress(hDLL, "div_");if (!add || !sub || !mul || !div_) {cout << "Failed to get function address." << endl;FreeLibrary(hDLL);return 1;}// 调用DLL中的函数int a = 10, b = 5;cout << "add(a, b) = " << add(a, b) << endl;cout << "sub(a, b) = " << sub(a, b) << endl;cout << "mul(a, b) = " << mul(a, b) << endl;cout << "div_(a, b) = " << div_(a, b) << endl;// 释放DLLFreeLibrary(hDLL);return 0;
}
输出结果
DLL的调用法二
- 新建文件夹:E:\Desktop\test_DLL
- 在test_DLL下面新建include文件夹
- 在test_DLL下面新建lib文件夹
生成的test1.lib放到lib文件夹
test.h放到include文件夹
测试工程配置
配置宏
可以看到此时dllexport是灰色的,dllimport生效
包含目录配置
库目录配置
链接器配置
将DLL与exe放置在同一目录
实际项目中所生成的DLL如果依赖了别的DLL,需要把依赖的DLL也放在同一目录下。
正常输出
静态库lib的生成
头文件改动
- 头文件用条件编译,源文件不动
- 在生成 DLL 时定义
BUILD_DLL
,在使用 DLL 时定义USE_DLL
,生成静态库时都不定义。
#ifndef TEST_H
#define TEST_H#include<iostream>
#include<stdexcept>
using namespace std;#ifdef BUILD_DLL
#define TESTAPI __declspec(dllexport)
#elif defined(USE_DLL)
#define TESTAPI __declspec(dllimport)
#else
#define TESTAPI // 静态库时为空
#endifextern "C" {TESTAPI int add(int a, int b);TESTAPI int sub(int a, int b);TESTAPI int mul(int a, int b);TESTAPI int div_(int a, int b);
}#endif // ! TEST_H
工程导出配置
生成静态库lib
静态库lib的调用
- 新建文件夹:E:\Desktop\test_lib
- 在test_lib下面新建include文件夹,放test.h
- 在test_lib下面新建lib文件夹,可以看到这个lib和之前生成DLL时的lib大小不一样,其实DLL生成时一起生成的lib是动态库的导入库,不是真正的静态库。
包含目录配置
库目录配置
链接器配置
正常输出
补充知识
VS生成动态库的时候会生成一个lib,这不是静态库,和单独生成的静态库lib大小差很多 。
区别说明:
类型 | 后缀 | 作用 | 说明 |
---|---|---|---|
动态库 | .dll | 包含实际的可执行代码 | 在运行时被加载使用 |
导入库 | .lib (与DLL配套) | 用于链接时解析符号 | 编译器链接用,运行时还需要 .dll |
静态库 | .lib (独立生成) | 包含实际实现代码 | 链接时被合并到可执行文件中,运行时无需 .dll |
为什么大小差很多?
导入库 .lib
只是一个轻量级的“符号索引文件”,里面只包含函数/类等符号的信息,不包含实际实现代码,因此比静态库小得多。而静态库 .lib
是将编译后的对象代码封装在里面,通常会大很多。
动态库的动态加载和静态加载
上面的DLL的调用法二就是静态加载,法一就是动态加载
特性 | 静态加载(使用导入库) | 动态加载(使用 LoadLibrary) |
---|---|---|
是否需要 .lib | 是(导入库) | 否 |
是否需要 .dll | 是 | 是 |
加载时机 | 程序启动时加载 DLL | 程序运行时手动加载 DLL |
函数使用方式 | 像普通函数直接用 | 必须用 GetProcAddress 获取函数指针 |
错误检查 | 编译/链接时能检查符号是否存在 | 运行时出错才知道 |
是否灵活 | 不灵活(固定依赖) | 很灵活(可以按需加载) |
动态库和静态库的区别
项目 | 静态库(Static Library) | 动态库(Dynamic Library) |
---|
文件扩展名 | .lib (Windows) .a (Linux) | .dll (Windows) .so (Linux) |
---|
链接方式 | 编译时合并到 EXE 或 DLL 中 | 程序运行时加载 |
---|
是否独立 | 可独立运行,无需库文件 | 必须依赖 DLL 文件存在 |
---|
占用体积 | 程序体积较大(复制代码) | 程序体积较小(共享代码) |
---|
更新方式 | 更新需要重新编译主程序 | 可单独更新 DLL |
---|
调用方式 | 直接调用函数 | 通过导入库或动态加载调用 |
---|
多程序共享 | 不行(每个程序各自有一份) | 可以多个程序共享一个 DLL |
---|
运行效率 | 启动快,运行略快(无函数指针开销) | 启动略慢,运行略慢(调用时需跳转) |
---|
动态库和静态库的使用场景
静态库适用场景(Static Library)
🛠 1. 简单项目 / 内部工具
- 不想处理运行时依赖(如 .dll 丢失)
- 工具或命令行程序,小巧独立
📦 2. 单文件部署要求
- 例如嵌入式开发、单机部署,不能有外部依赖
🔐 3. 源码闭合封装
- 不想暴露库的实现(不易逆向)
- 所有代码编译进可执行文件
⚡ 4. 性能优先
- 启动更快、调用无跳转(微小优化)
- 无运行时装载开销
✅ 动态库适用场景(Dynamic Library)
🧩 1. 插件/模块系统
- 比如浏览器插件、游戏引擎模块
- 支持运行时加载和替换模块
🔁 2. 多程序共享库
- 一个 DLL 被多个程序调用,节省内存和磁盘空间
- 例如操作系统 API、数据库驱动等
🚀 3. 支持热更新
- 只更换 DLL 就能修复 bug 或添加功能,无需重新编译主程序
🧪 4. 版本兼容 / 动态扩展
- 支持不同 DLL 版本(如数据库接口、第三方 SDK)
- 加载成功与否可动态判断,适合做可选功能
💻 5. 第三方库分发
- 如 OpenCV、SQLite、Qt 提供 DLL 供调用
- 避免源代码或静态库暴露
✅ 实战建议
项目类型 | 建议使用 |
---|---|
命令行工具、小工具 | 静态库,部署简单 |
插件架构(如浏览器、IDE) | 动态库,可按需加载 |
跨多个产品共享模块 | 动态库,节省资源 |
面向外部发布的 C++ SDK | 两者都提供(常见做法) |
嵌入式系统 | 静态库,避免外部依赖 |