Windows平台用vistual studio 2017打包制作C++动态库
1. 创建库项目
- 打开 Visual Studio 2017,选择 文件 → 新建 → 项目。
- 选择 Visual C++ → Windows 桌面 → 动态链接库 (DLL) 或 静态库 (LIB)。
- 动态库 (DLL):生成
.dll
和.lib
(导出符号表)。 - 静态库 (LIB):生成
.lib
(直接包含代码)。
- 动态库 (DLL):生成
- 输入项目名称(如
MyLibrary
),点击 确定。
2. 编写类代码
2.1示例:动态库 (DLL)
-
添加头文件(
MyClass.h
):#pragma once// 导出宏定义 #ifdef MYLIBRARY_EXPORTS #define MYLIBRARY_API __declspec(dllexport) #else #define MYLIBRARY_API __declspec(dllimport) #endifclass MYLIBRARY_API MyClass { public:MyClass();int Add(int a, int b); };
-
添加源文件(
MyClass.cpp
):#include "MyClass.h"MyClass::MyClass() {}int MyClass::Add(int a, int b) {return a + b; }
2.2示例:静态库 (LIB)
-
静态库不需要导出宏(直接包含完整代码):
// MyClass.h #pragma onceclass MyClass { public:MyClass();int Add(int a, int b); };
3. 配置项目属性
- 设置导出符号(仅动态库需要):
- 右键项目 → 属性 → C/C++ → 预处理器 → 预处理器定义。
- 添加
MYLIBRARY_EXPORTS
(确保动态库项目导出符号)。
- 调整输出目录(可选):
- 右键项目 → 属性 → 常规 → 输出目录,设置为
$(SolutionDir)bin\$(Platform)\$(Configuration)\
。
- 右键项目 → 属性 → 常规 → 输出目录,设置为
- 确保生成目标类型:
- 检查 配置类型(属性 → 常规 → 配置类型)应为
动态库 (.dll)
或静态库 (.lib)
。
- 检查 配置类型(属性 → 常规 → 配置类型)应为
4. 编译生成库
- 选择 生成 → 生成解决方案(或按
F7
)。 - 生成的库文件会在输出目录中:
- 动态库:
MyLibrary.dll
和MyLibrary.lib
。 - 静态库:
MyLibrary.lib
。
- 动态库:
5. 使用库
5.1在另一个项目中调用动态库
-
包含头文件:
#include "MyClass.h"
-
链接库文件:
- 右键项目 → 属性 → 链接器 → 输入 → 附加依赖项,添加
MyLibrary.lib
。 - 在 链接器 → 常规 → 附加库目录 中指定
.lib
文件路径。
- 右键项目 → 属性 → 链接器 → 输入 → 附加依赖项,添加
-
将
.dll
文件放在可执行文件目录:- 将
MyLibrary.dll
复制到可执行文件(.exe
)的同一目录。
- 将
-
调用代码示例:
#include "MyClass.h" #include <iostream>int main() {MyClass obj;std::cout << "Add: " << obj.Add(3, 5) << std::endl;return 0; }
6. 常见问题
- 符号未导出:确保在动态库项目中定义了
MYLIBRARY_EXPORTS
。 - 链接错误:检查
.lib
文件路径是否正确。 - 缺少
.dll
:确保.dll
文件在可执行文件目录或系统路径中。 - 平台不匹配:确保生成库的平台(x86/x64)与调用项目一致。
7.静态库 vs 动态库
类型 | 特点 |
---|---|
静态库 | 代码直接编译到可执行文件中,无需额外依赖。 |
动态库 | 运行时加载,减少可执行文件体积,支持模块化更新。 |
8.设置debug模式生成的*_d.dll和*_d.lib
在 Visual Studio 中,可以通过配置项目属性来区分 Debug 和 Release 模式生成的库文件名(例如 MyLibrary_d.dll
和 MyLibrary_d.lib
)。以下是具体步骤:
8.1修改输出文件名(添加 _d
后缀)
(1) 针对 Debug 配置
-
右键项目 → 属性 → 确保顶部配置为 Debug 和对应的平台(如 x64)。
-
进入 常规 → 目标文件名:
- 默认值为
$(ProjectName)
。 - 修改为
$(ProjectName)_d
,这样生成的库文件会带_d
后缀。
- 默认值为
(2) 针对 Release 配置
- 切换顶部配置为 Release。
- 确保 目标文件名 为
$(ProjectName)
(无后缀)。
8.2调整输出目录(可选)
为了区分 Debug 和 Release 的输出文件,可以设置不同的输出目录:
-
在 常规 → 输出目录 中,修改为:
- Debug:
$(SolutionDir)bin\$(Platform)\Debug\
- Release:
$(SolutionDir)bin\$(Platform)\Release\
- Debug:
8.3链接器导入库名称(自动同步)
- 无需手动修改:当修改 目标文件名 后,链接器生成的
.lib
文件名会自动同步(例如MyLibrary_d.lib
)。
8.4验证生成结果
编译项目后,检查输出目录:
- Debug 模式:生成
MyLibrary_d.dll
和MyLibrary_d.lib
。 - Release 模式:生成
MyLibrary.dll
和MyLibrary.lib
。
8.5调用方项目配置
如果其他项目要链接该库,需根据配置引用对应的文件:
- Debug 配置:
- 附加依赖项:
MyLibrary_d.lib
- 库目录:
$(SolutionDir)bin\$(Platform)\Debug\
- 附加依赖项:
- Release 配置:
- 附加依赖项:
MyLibrary.lib
- 库目录:
$(SolutionDir)bin\$(Platform)\Release\
- 附加依赖项:
8.6完整配置示例
(1) 项目属性(Debug)
配置项 | 值 |
---|---|
目标文件名 | $(ProjectName)_d |
输出目录 | $(SolutionDir)bin\$(Platform)\Debug\ |
运行时库 | MDd (多线程调试 DLL) |
(2) 项目属性(Release)
配置项 | 值 |
---|---|
目标文件名 | $(ProjectName) |
输出目录 | $(SolutionDir)bin\$(Platform)\Release\ |
运行时库 | MD (多线程 DLL) |
8.7自动化配置(使用宏)
若需更灵活的命名规则,可以通过编辑 .vcxproj
文件自定义生成逻辑:
<!-- 在 .vcxproj 文件中添加条件 -->
<PropertyGroup Condition="'$(Configuration)' == 'Debug'"><TargetName>$(ProjectName)_d</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'"><TargetName>$(ProjectName)</TargetName>
</PropertyGroup>
运行 HTML
8.8注意事项
- 运行时库一致性:
- Debug 和 Release 配置的 代码生成 → 运行时库 必须与调用方项目一致(如
MDd
或MTd
)。
- Debug 和 Release 配置的 代码生成 → 运行时库 必须与调用方项目一致(如
- 清理旧文件:
- 修改配置后,先清理解决方案(生成 → 清理解决方案),再重新生成。
- 符号导出宏:
- 如果导出宏(如
JIEBASEGMENTER_API
)依赖项目名称,确保名称修改不影响宏定义。
- 如果导出宏(如
9.隐藏第三方库头文件
很多时候我们制作一个功能类,如果调用第三方库在头文件中有引用,那么在其他人调用的时候也要将第三方库链接到项目中。当第三方库比较多的时候,这样就比较麻烦。我们如何能将第三方库头文件完全隐藏在生成的二进制库文件中呢?下面让我们详细说明一下。
原来的类:
9.1 隐藏前的头文件
// MyLibrary.h
#pragma once
#include <third/third_lib.h> // 引用 OpenCV 头文件#ifdef MYLIBRARY_EXPORTS
#define MYLIBRARY_API __declspec(dllexport)
#else
#define MYLIBRARY_API __declspec(dllimport)
#endifclass MYLIBRARY_API MyLibrary {
public:// 构造函数MyLibrary(const std::string& str);// 成员函数声明std::vector<std::string> getWords(const std::string& sentence);// ......private:ThirdLib* thirdLib;
};
9.2 隐藏前的源码文件
// MyLibrary.cpp
MyLibrary::MyLibrary(const std::string& str):
thirdLib(std::make_unique<ThirdLib>(str))
{// 逻辑
}std::vector<std::string> MyLibrary::getWords(const std::string& sentence)
{// 逻辑
}
9.3 隐藏后的头文件
// MyLibrary.h
#pragma once#ifdef MYLIBRARY_EXPORTS
#define MYLIBRARY_API __declspec(dllexport)
#else
#define MYLIBRARY_API __declspec(dllimport)
#endif// 前向声明实现类(完全隐藏 third)
class ThirdLibImpl;class MYLIBRARY_API MyLibrary {
public:// 构造函数MyLibrary(const std::string& str);// 析构函数~MyLibrary();// 禁用拷贝(避免浅拷贝问题)MyLibrary(const MyLibrary&) = delete;MyLibrary& operator=(const MyLibrary&) = delete;// 移动语义支持(可选)MyLibrary(MyLibrary&&) noexcept;MyLibrary& operator=(MyLibrary&&) noexcept;// 成员函数声明std::vector<std::string> MyLibrary::getWords(const std::string& sentence);// ......private:std::unique_ptr<ThirdLibImpl> thirdLibImpl;
};
9.4 隐藏后的源码文件
// MyLibrary.cpp
#include "pch.h"
#include "MyLibrary.h"//--------------------- 实现类定义(完全隐藏) ---------------------
class ThirdLibImpl{
public:ThirdLibImpl(const std::string& str) {// 逻辑}std::vector<std::string> getWords(const std::string& sentence){// 逻辑}
private:ThirdLib* thirdLib; // 实际持有的 cppjieba 实例
};//--------------------- 接口类实现 ---------------------
MyLibrary::MyLibrary(const std::string& str) : thirdLibImpl(std::make_unique<ThirdLib>(str)) {}MyLibrary::~MyLibrary() = default;// 移动构造函数
MyLibrary::MyLibrary(MyLibrary&& other) noexcept: thirdLibImpl(std::move(other.thirdLibImpl)) {}// 移动赋值运算符
MyLibrary& MyLibrary::operator=(MyLibrary&& other) noexcept {if (this != &other) {pimpl_ = std::move(other.thirdLibImpl);}return *this;
}// 成员函数转发到实现类
std::vector<std::string> MyLibrary::getWords(const std::string& sentence)
{return thirdLibImpl->getWords(sentence);
}