Microsoft Visual C++ 静态和动态库






4.93/5 (75投票s)
Microsoft Visual C++ 静态库和动态库的简单介绍。
阶段
引言
本文旨在对 Microsoft Visual C++ 的静态库和动态库进行基本介绍。本文假设您对 C 语言有基本了解,并熟悉 Microsoft Visual C++ IDE。我将向您展示如何编译和引用动态库和静态库,以便您可以共享和分发自己的库。您需要拥有一个版本的 Microsoft Visual C++。如果您没有,可以在 此处 下载 Express 版本。
本教程将逐步介绍如何编译和引用您的库,并附有大量图示。本文旨在通过重复讲解来帮助初学者掌握并开发自己的 Microsoft Visual C++ 库。我强烈建议您阅读我之前关于使用 MinGW 编译库的文章。该文章提供了静态库和动态库的对比。总的来说,我认为 GCC 编译器更容易理解和开发库。这将有助于初学者开发自己的库。
静态库
静态库的代码与我在上一篇关于为 MinGW 编译库的文章中的代码相同,可以在 此处 找到。这是一个过于简化的库,旨在强调创建过程,而不是库的概念。 该库相当标准,函数在头文件中声明,并在 'c/cpp' 文件中定义。文件列表如下:
#ifndef ADD_H
#define ADD_H
int add(int a, int b);
#endif // ADD_H
#include "add.h"
int add(int a, int b) {
return a + b;
}
向导(推荐用法:计划创建静态库)
向导会自动设置所有必要的静态库配置。此方法适用于您最初计划开发库时。
启动 Microsoft Visual C++ 时,请确保选择 Win32 控制台项目,而不是 C/C++ CLI 项目。Win32 控制台项目是本机应用程序,会生成机器码,而不是中间语言代码。 C/C++ CLI(公共语言基础设施)代码是托管代码,编译成一种字节码,由虚拟机解释。 “托管语言”和“CLI”的实际定义超出了本项目范围;如果您感兴趣,请自行查阅。Win32 项目是本机语言,只能在安装了 .NET Framework 的 Windows 操作系统上运行。
启动 Microsoft Visual C++ 后,选择 *文件->新建项目->Win32 控制台应用程序*。选择 *下一步*,然后选择 *静态库*。这将创建一个空项目。添加提供的文件,或右键单击“*解决方案资源管理器*”并添加新的头文件 **\*.h** 和新的 **\*.cpp** 文件。将解决方案源代码复制到您新创建的头文件和 CPP 文件中。
项目属性(推荐用法:设计更改)
项目属性方法允许您在开发后期更改库类型。
使用向导的替代方法是使用项目属性。第二种选项允许用户在遵循向导后实现静态库。这的一种“用例”可能是您原本打算实现一个应用程序,但在决定之后,最好将其作为静态库。要实现此更改,请转到 *项目->添加属性->常规*,然后在配置字段中选择静态库(参见上图)。要编译解决方案,请右键单击解决方案并选择 *生成*。
引用静态库
库的实现相当标准。您必须能够访问头文件并引用它。如果您没有头文件,或者不想引用它,您必须使用 `extern` 标签来指示函数定义。*main.c* 文件相当标准,并演示了如何运行静态库。您会在下一个图中注意到,一个新的项目已添加到库解决方案文件中。要向解决方案添加新项目,请右键单击解决方案文件并选择 *添加新项目*。
然后,通过右键单击新项目的*源文件*并选择*添加新项*,具体来说是“C++”文件,向您的新项目添加一个新文件。将以下代码添加到您的项目中
#include "../Add/add.h"
#include <stdio.h>
//extern int add(int a, int b);
int main() {
int a = 2;
int b = 1;
printf("a=%d, b=%d\n", a,b);
printf("add: %d\n", add(a,b));
getchar();
return 0;
}
引用方法(推荐用法:访问原始项目文件)
引用方法允许您轻松地添加对您拥有的另一个 MSVC(Microsoft Visual C++)项目文件的依赖。例如,此方法最适合我们现在正在创建的库,因为我们有原始项目的访问权。
要实现此方法,请将您想要的项目包含到当前解决方案中。(在此示例中,您应该已经将新项目添加到了库解决方案中。)如果还没有,您需要右键单击*解决方案文件->添加->现有项目*。现在右键单击将依赖于您的静态库的项目:选择*通用属性->框架和引用->添加新引用*,然后选择具有所需依赖项的项目。如果您已成功完成此操作,您应该会看到与下图类似的内容。
拖放方法(推荐用法:第三方库)
当您无法访问原始项目文件时,拖放方法非常适用。开发者可能不希望与您共享源代码,在这种情况下,您只会得到一个头文件 **\*.h** 和一个库文件 **\*.lib**。.
为了引用您新编译的库(或第三方库),您可以导航到您的“*Debug*”或“*Release*”目录来选择您新编译的库文件。抓住标记为 *Add.lib* 或任何其他库的 **\*.lib** 文件,并将其拖到您的项目资源中。拖放方法比接下来的使用配置的方法要简单得多。您还必须识别您的库文件,以便链接器能够正确识别已编译的函数。为此,您必须包含您的库头文件,或添加一个 `extern` 引用来定义您要调用的函数。
配置方法(推荐用法:具有特殊配置的第三方库)
此库的原因与拖放方法中提到的原因相似。但是,此方法为您提供了更多控制。这最适合第三方库。
配置的目的与拖放方法完全相同,但更难。导航到 *项目->属性->输入->附加依赖项*,然后添加库源的目录路径。附加依赖项是使用您的解决方案文件作为基目录的引用,因此 '.' 表示当前解决方案目录后跟直接库文件的路径。您也可以显式写出完整的目录路径,例如 *C:\Users\....*。如果您需要进一步的帮助,请参阅 MSDN 文章,该文章非常具体但图形化或解释性不强。
动态库
动态库的教程旨在尽可能与静态库相似。您会注意到唯一的添加是 `__declspec(dllexport)`,这是一个 Microsoft 特定标识符,它标识该函数将用作 **DLL**(动态链接库)导出。**C** 文件与之前的示例完全相同。
#ifndef ADD_H
#define ADD_H
int __declspec(dllexport) add(int a, int b);
#endif // ADD_H
#include "add.h"
int add(int a, int b) {
return a + b;
}
向导(推荐用法:计划创建动态库)
向导会自动设置所有必要的动态库配置。此方法适用于您最初计划开发库时。
如上所述,使用向导创建动态库的方法与静态库非常相似。我从“*应用程序类型*”中选择了 *DLL*,然后选择了“*空项目*”。如果您不选择“*空项目*”,您将不得不删除 IDE 提供的文件。虽然向导提供的项目文件有特定用途,但它们会使本教程过于复杂。
项目属性(推荐用法:设计更改)
项目属性方法允许您在开发后期更改库类型。
项目属性页面与静态库方法相同。要实现此更改,请转到 *项目->添加属性->常规*,然后在配置字段中选择动态库。
引用动态库
您首先应该注意到的是,头文件已修改为使用 `__declspec(dllimport)` 而不是 `__declspec(dllexport)` 来导入库。这告诉编译器这是在引用动态库。显式声明导入或导出语句似乎有些多余,因为您要么在编译库,要么在导出库,很可能不会在同一个项目中同时进行。这就是为什么我更喜欢 GCC 编译器来生成动态库。此外,Microsoft Visual C++ IDE 要求您在生成共享/动态库时链接 **\*.lib** 文件(尽管有可能可以使用 Windows API 显式链接动态库;这将在下一篇文章中介绍)。另外,请记住,您仍然需要头文件或函数定义。
#ifndef ADD_H
#define ADD_H
int __declspec(dllimport) add(int a, int b);
#endif // ADD_H
#include <stdio.h>
#include "add.h"
//extern int __declspec(dllimport) add(int a, int b);
int main() {
int a = 2;
int b = 1;
printf("a=%d, b=%d\n", a,b);
printf("add: %d\n", add(a,b));
getchar();
return 0;
}
引用方法(推荐用法:访问原始项目文件)
引用方法允许您轻松地添加对您拥有的另一个 MSVC 项目文件的依赖。例如,此方法最适合我们现在正在创建的库,因为我们有原始项目的访问权。
要实现此方法,请将您想要的项目包含到当前解决方案中。(在此示例中,您应该已经将新项目添加到了库解决方案中。)如果还没有,您需要右键单击*解决方案文件->添加->现有项目*。现在右键单击将依赖于您的静态库的项目:选择*通用属性->框架和引用->添加新引用*,然后选择具有所需依赖项的项目。在我们的库的情况下,您应该只能看到一个项目可供引用。
拖放方法(推荐用法:第三方库)
这是引用只有 **\*.lib** 和 **\*.h** 文件的第三方库最快、最简单的方法。
拖放方法与静态库方法相同,需要您包含 **\*.lib** 文件。包含 **lib** 文件称为隐式链接,因为机器码在编译时已知,但实际上并未包含在实际应用程序中。(静态库将机器码直接包含在应用程序中,而动态代码则在运行时加载它。)
配置方法(推荐用法:具有特殊配置的第三方库)
此配置方法最适合可能具有特殊配置的第三方库,从而为您提供更多控制。您仍然需要 **\*.h** 和 **\*.lib** 文件。
配置方法与静态库相同。导航到 *项目->属性->输入->附加依赖项*,然后添加库源的目录路径。
可重用头文件
#ifndef ADD_H
#define ADD_H
#ifdef BUILD_DLL
#define PORT_DLL __declspec(dllexport)
#else
#define PORT_DLL __declspec(dllimport)
#endif
int PORT_DLL add(int a, int b);
#endif // ADD_H
可重用头文件可能是 Windows 上最常见的动态库头文件类型。它允许您为库编译或链接该库的实际应用程序重用您的头文件。`PORT_DLL` 如果在编译时声明,将定义导出。如果编译时未定义,则表示它供应用程序使用。这是重用头文件的一种非常常见的方式,因为库文件的创建者将定义导出,而用户只需包含头文件。
命令行选项
要定义您的导出,您必须转到*项目->属性->C/C++->命令行*并添加标志 **/D**。这代表定义。在这种特定情况下,您定义 `BUILD_DLL`,这意味着您打算将此库实现为导出。库的用户如果打算使用该库,则不会定义该变量。有关动态隐式链接的更多详细信息,请参阅 MSDN。
C 运行时库 (CRT)
C 运行时库可能会在以后造成很多麻烦,尤其是在您尝试链接第三方库时。它也可能给您的客户带来很多麻烦,因为他们无法引用您的库。您必须了解 CRT 如何影响您的库,才能成功地将您的工作与他人的工作集成。
CRT 库
CRT 简而言之就是您开发代码所依赖的所有库函数。它包括标准 C++ 库以及 Microsoft 函数和声明。以下是您可以集成的库的摘要列表。有关完整列表,请参阅 MSDN。
- LIBCPMT.LIB - 多线程(标准 C++ 库) /MT
- LIBCMT.LIB - 多线程(C 运行时库) /MT
- MSVCPRT.LIB - 多线程动态链接库(带 *MSVCP90.dll*)(标准 C++ 库) /MD
- MSVCRT.LIB - 多线程动态链接(C 运行时库)(带 *MSVCR90.dll*) /MD
还有其他库;但是,MT(多线程)和 MD(多线程 DLL)标志是最常见的,并且从 VS2008 开始,它们是唯一可用的标志(不包括调试标志)。从 VS2008 开始,我无法使用单线程库。各种库允许您将项目链接到您当前版本的 Visual Studio 的 CRT。要设置您的 CRT 版本,请转到“*项目属性->代码生成->运行时库*”,然后选择 /MT、/MTd、/MD 或 /MDd(多线程[调试]或多线程 DLL[调试])。
不幸的是,VS 的每一个新版本都有一个新的 CRT。链接库的主要问题是旧库依赖于其发布版本的 CRT,这可能与新版本的 VS 冲突。这主要与内存分配有关。MSDN 提供了详细信息,但这里有一个摘录:
“如果您设计的 DLL 会跨越边界传递 CRT 对象或分配内存并期望在 DLL 外部释放,那么您将 DLL 用户限制为使用同一份 CRT 库副本。只有当 DLL 和其用户链接到同一版本的 CRT DLL 时,它们才使用同一份 CRT 库副本。” - MSDN
第三方库
如果您有一个包含 DLL、lib 和头文件的第三方库,您应该可以编译和链接您的项目到该库。但是,如果您遇到 CRT 问题,您需要确定第三方供应商是用哪个 CRT 版本编译他们的库的。要做到这一点,您需要进入 MSVS 命令行工具并输入
dumpbin /imports <dllname.dll>
这会列出编译库时使用的所有导入。在我们的库“*Add.lib*”的情况下,由于我们没有使用任何 CRT 函数,因此我们只有“*kernel32.dll*”作为资源。但是,在大多数情况下,您会看到类似“*msvcr80.dll*”或“*libcmt.lib*”的内容。需要从 MSDN 参考确切的库来将 DLL 名称转换为特定库和必要的编译器标志(参考 MSDN:C 运行时库 和 MS 支持:C 运行时库)。如果您参考 MSDN 上的各种 CRT 库,您会注意到“*msvcr80.dll*”是用 /MD 开关编译的。您必须按照上一节中的说明配置您的库。如果此操作不成功,那么您很可能需要 8.0 版本(即 VS2005),并使用 /MD 进行编译。
自定义 CRT
强迫您的客户依赖过时的库,或者更糟的是,强迫您的客户升级到新的昂贵版本的 Visual Studio,这可能会让您的客户非常恼火。幸运的是,MS 提供了一种方法来编译 CRT 库,以便您可以将它们与您的库打包在一起。要编译您自己的 CRT 版本,请转到 VS 命令行工具。(通过右键单击 VS 命令行工具并选择“以管理员身份运行”来确保您拥有管理员权限。)键入 'set vctools=C:\Program files\Microsoft Visual Studio x.x\VC'(其中 x.x 是您的 VS 版本)。然后键入 'crt\src\bldnt.cmd'。这将构建 CRT 库。这将运行一分钟左右,为您的系统编译所有必需的库。完成后,我收到了 *'libcmt.lib*、*'libcpmt.lib*、*'_sample_.dll'* 和 *'_sample_.lib*。示例文件实际上是 *msvcrXX.dll*(其中 XX 是您的 VS 版本)的副本。它们被重命名,以免与实际的 MS 库冲突。有关完整的命名约定,请参考 MSDN:构建运行时库。
构建自定义库后,您需要将其链接到您的项目中。要将其链接到您的项目中,您必须返回到项目属性,并按照之前教授的方法添加它们。由于您正在链接自定义 CRT 库,因此需要通过转到*“项目属性->链接器->输入->忽略所有默认库->是”*来移除非默认库。这将覆盖默认的 MT 或 MD 标志,这些标志会自动包含您 VS 版本的标准 CRT。但是,请注意,一旦这样做,您将需要手动导入所有附加依赖项。*“附加依赖项=.\_sample_lib”*,或者无论您的依赖库是什么。另一种选择是直接忽略默认 CRT 库,方法是转到*“忽略特定库=msvcr9.0.lib”*并将*“附加依赖项 _sample_.lib”*放在那里,以便您的库依赖于您编译的库而不是系统默认库。这使得与客户共享库时具有更大的灵活性,因为您的依赖项会随您的库一起分发。
比较
正如我在之前的 文章 中所述,动态库在**运行时**加载,这意味着您的 **\*.dll** 文件必须可供您的应用程序使用。动态库允许应用程序减小其总体文件大小。**DLL** 文件最适合需要完全相同的函数调用的项目。请注意,在下图所示的情况下,如果您删除/移除该库,可执行文件将无法运行。静态库将机器码直接包含在您的应用程序中,因此不需要外部库。静态库适用于在不直接提供源代码的情况下共享项目。
DLL 要求
静态库属性
- 小型/中型项目,具有不常用共享的特定代码
- 应用程序可执行文件相对较大
- 无动态链接开销(已编译到应用程序中)
- 在不放弃源代码的情况下共享项目
- 运行时无链接
动态库属性
- 最适合共享跨应用程序的冗余代码的大型项目
- 应用程序可执行文件相对较小
- 动态链接的开销
- 在不放弃源代码的情况下共享项目
- 运行时需要库
结论
本文旨在演示如何构建自己的静态和动态隐式链接库。创建库有两种方法:最初使用向导,或稍后通过项目属性进行。库可以利用项目引用方法、拖放方法和配置方法。项目引用方法最适合您能够访问源代码的库。拖放方法和配置方法更适合您只有库文件和定义头文件的第三方库。静态库适用于您希望共享工作但不提供源代码的小型/中型项目。动态库提供了在不提供源代码的情况下共享工作并允许您减小总体应用程序大小的机会。尽管动态库的运行开销更大,但静态库和动态库都在您的项目中占有一席之地。
特别感谢
如果不是因为以下人员,本文就不会包含引用方法
- Aescleal - 引用方法(理由和说明)。
- Randor - 引用方法(补充理由)。
- Rolf Kristensen - CRT 评论。