Windows下VC运行时库的部署、兼容性
本文适用于对Visual Studio C、C++开发使用动态链接库有经验的读者
本文适用于Visual Studio 2015及之后版本,对于之前的版本,只做必要的提及
什么是VC运行时库
VC运行时库,简单来说,就是Windows平台上运行的包含了C、C++标准库以及通用C++标准实现的一组动态链接库。
在2015版本之前的VC运行时库中,包含了C++运行时库和C运行时库,在2015版本以及之后的VC运行时库中,C++运行时库和C运时行库被分离为两个部分。
是VC运行时库的版本
Visual Studio所包含的C++工具集的版本和Visual Studio的版本并不一致。
VC运行时库是跟随C++工具集一起配合使用的,但是VC运行时库的安装版本是和包含发布C++工具集的Visual Studio的版本一致。而随着Visual Studio发布工具集的更新,VC运行时库内文件的次版本号也会有变化。
也即是Visual Studio2015中发布了C++的140工具集,C++的140工具集构建的程序依赖VC运行时库2015版本,VC运行时库2015版本中的文件版本是14.0
其对应关系如下:
VS版本 | C++工具集版本 | 库文件版本 |
---|---|---|
2010 | 100 | 10.x.x.x |
2012 | 110 | 11.x.x.x |
2013 | 120 | 12.x.x.x |
2015 | 140 | 14.0.x.x |
2017 | 141 | 14.1x.x.x |
2019 | 142 | 14.2x.x.x |
2022 | 143 | 14.3x.x.x |
C++运行时库
在2015版本之后的VC运行时库中C++运行库时由以下的动态链接库dll文件组成:
dll | 描述 | 适用于 |
---|---|---|
vcruntime[version].dll | 本机代码的运行时库。 | 使用普通 C 和 C++ 语言启动和终止服务的应用程序。 |
vccorlib[version].dll | 托管代码的运行时库。 | 将 C++ 语言服务用于托管代码的应用程序。 |
msvcp[version].dll和msvcp[version_dotnumber].dll | 本机代码的 C++ 标准库。 | 使用C++ 标准库的应用程序。 |
concrt[version].dll | 本机代码的并发运行时库。 | 使用并发运行时的应用程序。 |
mfc[version].dll | Microsoft 基础类 (MFC) 库。 | 使用MFC 库的应用程序。 |
mfc[version][language].dll | Microsoft 基础类 (MFC) 库资源。 | 为 MFC 使用特定语言资源的应用程序。 |
mfc[version]u.dll | 支持 Unicode 的 MFC 库。 | 使用MFC 库并需要 Unicode 支持的应用程序。 |
mfcmifc80.dll | MFC 托管接口库。 | 使用带有Windows 窗体控件的MFC 库的应用程序。 |
mfcm[version].dll | MFC 托管库。 | 使用带有Windows 窗体控件的MFC 库的应用程序。 |
mfcm[version]u.dll | 支持 Unicode 的 MFC 托管库。 | 将MFC 库与Windows 窗体控件一起使用并需要 Unicode 支持的应用程序。 |
vcamp[version].dll | 用于本机代码的 AMP 库。 | 使用C++ AMP 库代码的应用程序。 |
vcomp[version].dll | 用于本机代码的 OpenMP 库。 | 使用C++ OpenMP 库代码的应用程序。 |
调试版本的dll文件通常在文件名末尾附件额外的d
。
其用于开发的导入库包含在Visual Studio的C++开发组件中。
C运行时库
在VC运行时库2015版本之前版本中,C运行时库 (CRT)是作为VC运行时库的一部分被包含在名称为msvc[version].dll的动态链接库中。
在Visual Studio 2015中,C运行时库函数被分离并重构为vcruntime和UCRT(Universal CRT-统一C运行时)。
vcruntime被继续包含在VC运行时库中,而UCRT则作为Windows10及更高版本中的系统组件的一部分,并且由WindowsUpdate负责更新管理。
UCRT由ucrtbase.dll和一组名为api-ms-win-*.dll的动态链接库组成。
其用于开发的导入库由WindowsSDK提供。
VC运行时库部署
VC运行时库依赖
当使用Visual Studio编写一个C、C++语言程序时,如果编译时使用了/MD
、/MDd
选项,就会以动态链接库的形式使用VC运行时库。那么这个程序运行时就需要依赖C++运行时库和C运行时库。
如果程序运行时无法找到对应的C++运行时库和C运行时库,程序将无法运行。
集中部署
在集中部署中,库dLL文件安装在该Windows\System32
目录中,对于x64系统上的32位库文件,则在Windows\SysWow64
目录。
集中部署的库文件可以被操作系统上任意的程序使用而无需指明位置。
集中部署通常使用Redistributable packages(重新分发包文件)的方式进行部署,
Redistributable packages是一个独立的安装包文件,包含所有的运行时库文件。
Redistributable packages通常分为3个独立的安装包:
架构 | 文件 | 备注 |
---|---|---|
X86 | vc_redist.x86.exe | 用于32位程序的VC运行时库 |
X64 | vc_redist.x64.exe | 用于64位程序的VC运行时库 |
ARM64 | vc_redist.arm64.exe | 用于ARM平台64位程序的VC运行时库(仅在2015以及之后的版本中提供) |
安装包文件在以下路径:
- vs2015:
%VCINSTALLDIR%redist\<locale>
- vs2017:
%VCToolsRedistDir%
- vs2019:
%VCINSTALLDIR%Redist\MSVC\v142
- vs2022:
%VCINSTALLDIR%Redist\MSVC\v143
<locale>
是分发包的语言代码,%VCINSTALLDIR%
可以在Visual Studio开发命令提示的环境变量中解析。
在2015之前版本VC运行时库中,C运行时库和C++运行时库的dll被一起安装到系统目录中,但在2015以及之后的版本中,C运行时库独立成为了系统组件UCRT。
UCRT的最早的版本是KB2999226
,后来的更新版本为KB3118401
。
在Window10之前的版本上,UCRT作为一个单独的系统更新组件被安装,最低的可支持操作系统是Windows 7 SP1。
VC运行时库2015以及之后的版本中包含了UCRT的系统更新组件,并在Window10之前的版本安装时尝试自动安装UCRT组件。
本地部署
本地部署就是将库dll文件安装到应用程序的目录中,程序启动时会按照dll搜索路径顺序进行加载时会优先尝试加载应用程序目录中的dll。
2015以及之后C++运行时库dll文件在以下路径:
- vs2015:
%VCINSTALLDIR%redist\<architect>\<module>
, - vs2015之后:
%VCINSTALLDIR%redist\MSVC\<version>\<architecture>\<module>
<locale>
是分发包的语言代码,,<version>
是库文件版本,<architecture>
是架构,\<module>
是模块划分,%VCINSTALLDIR%
可以在Visual Studio开发命令提示的环境变量中解析。
由于VC运行库文件之间有可能会依赖,所以哪怕应用程序只依赖了C++运行库中的部分dll,也建议按照VC运行库的模块划分将整个模块的文件进行部署。
UCRT的C运行时库dll文件在以下路径:
Program Files\Windows Kits\10\Redist\ucrt\DLLs\[architecture]\
Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs\[architecture]\
<architecture>
是架构。
UCRT从Windows10不再支持本地部署,哪怕进行了本地部署,程序也会使用系统全局的UCRT
VC运行时库的版本兼容性
首先不同架构的VC运行时库是绝对不能兼容的,32位的程序必须使用x86架构的VC运行时库。64位程序和ARM程序同理。
关于同架构下的不同版本VC运行时库兼容性,以2015版本作为分界线。
2015之前的VC运行时库兼容性
2015版本之前的VC运行时库不同版本不可以兼容。这种不兼容不仅仅是ABI的不兼容,连内存的分配释放、标准库中的资源的使用,也是不兼容的。不同的工具集构建出的模块会使用对应版本的VC运行时库。
具体兼容性问题如下:
- 内存分配释放:一个模块中分配了内存,可以传递给不同工具集构建的模块使用,但是不能让其他不同工具集构建的模块释放。因为不同的VC运行时库里有各自独立的内存堆,从哪个堆中分配的对象只能由这个堆释放。
- 标准库资源:一个模块中打开的标准库资源,例如文件,是不能传递给不同工具集构建的模块使用的。因为标准库打开的资源通常会关联一些全局变量,而不同的VC运行时库里有各自独立的状态,如果跨不同工具集模块使用可能会引用到错误的全局变量。
- C++对象:C++标准库中的对象在不同版本的VC运行时库中可能会修改其内存布局,同时C++对象的RTTI运行时信息结构也可能不一样。所以不能将C++对象传递给不同工具集构建的模块。
- ABI:不同的的VC运行时库可能使用不同的ABI,所以一个模块不能调用不同的工具集构建的模块中的接口。如果要使用不同的工具集构建的模块中的接口,必须使用
extern C
将接口导出为纯C的接口,然后使用。
在使用静态链接的VC运行时库时,分别构建的可执行文件和动态链接库哪怕链接的是相同版本的VC运行时库,但是依然要存在上述内存分配释放和标准库资源的问题。
如果程序的不同工具集构建的模块避免了上述的兼容性问题,那么就可以正常运行。当然,运行时需要部署所有依赖的运行时库版本。
从2015开始的VC运行时库兼容性
从Visual Studio 2015,也就是140工具集开始,之后的VC运行时库都保持了向后兼容性。但是在使用使用静态链接的VC运行时库时,依然存在上节同样的问题。
另外的限制是在使用构建时使用/GL(全程序优化)
编译器开关编译或使用/LTCG(链接时代码生成)
链接的静态库或目标文件 不是跨版本二进制兼容的。
使用旧工具集构建的模块可以直接使用新版本的VC运行时库而无需重新构建,反之不能。UCRT亦是如此。
也正是因为这个原因,C++工具集开始了频繁的小版本更新,UCRT可以由Windows系统进行自动更新。
从2015开始的VC运行时库开始,新版本的VC运行时库安装包安装时会自动覆盖旧版本的VC运行时库。
引用
https://learn.microsoft.com/en-us/cpp/windows/determining-which-dlls-to-redistribute?view=msvc-140
https://learn.microsoft.com/en-us/cpp/windows/redistributing-visual-cpp-files?view=msvc-140
https://learn.microsoft.com/en-us/cpp/c-runtime-library/crt-library-features?view=msvc-170
https://learn.microsoft.com/en-us/cpp/porting/binary-compat-2015-2017?view=msvc-170