VC++ 2005 Redist (带 MSI 3.1) 的引导程序






4.97/5 (64投票s)
2005年12月12日
24分钟阅读

619360

1996
关于 Visual C++ 2005 部署的讨论,以及包含 MSI 3.1 的 vcredist_x86.exe 的修订版本。
目录
引言
这是修订版 Vcredist_x86.exe 的源代码,该可再发行组件由 Microsoft 分发。Microsoft 忘记在该可再发行组件中包含所需的 Windows Installer 3.1。以上文件附带一个引导程序,可检测是否有任何先决条件缺失并安装所需的运行时。
本文也是对 Visual C++ 2005 中 C++ 部署主题的各种论坛/新闻组/留言板帖子和发现的汇编。
在另一台计算机上运行 VC++ 2005 应用程序
命令行构建
本节主要适用于从命令行构建的程序(使用 cl 或 nmake)。虽然您可以通过 IDE 重现相同的结果,但这需要更改许多项目设置,如果您正在这样做,我假设您已经知道自己在做什么!
您已经设置了 Visual Studio 环境和编译器,您已成功构建了您的项目,现在您已准备好测试您的 VC++ 2005 应用程序。然而,在它启动之前,您会收到一个错误
您想知道发生了什么。
使用 Microsoft Visual C++ 编译的程序,如果动态链接到 C 运行时(/MD 或 /MDd),则必须捆绑 C 运行时 DLL 的副本(通常称为 MSVCRT.DLL 或 MSVCRxx.DLL,其中 xx 代表 Visual C++ 的版本)。如果您只复制 .EXE 文件而忘记同时复制 MSVCRxx.DLL,您将收到上述错误。
好的,您注意到 MSVCR80.dll 不在 System32 中。它位于另一个目录中(C:\WINDOWS\WinSxS\x86_Microsoft.VC80.CRT_1fc8b3b9a1e18e3b_8.0.50727.42_x-ww_0de06acd)。您将其从那里复制到 System32(或者如果您是 DLL hell 时代的资深用户,您会更清楚地将其复制到您的应用程序目录),然后您尝试再次运行它
.
无论您将其复制到应用程序目录还是 System32,都会出现此错误。发布版本会给您一个稍微不同的错误消息,但大体相同。
Visual Studio 2005 对 C 运行时库的链接方式进行了一些更改。第一个更改是单线程库现在已消失。如果您需要单线程库提供的性能提升并愿意牺牲线程安全,您应该使用 CRT 库函数的无锁变体。第二个更改是使用 VC2005 IDE 创建的项目现在默认动态链接到 C 运行时库。在 VC2003 中,只有 MFC 和托管 C++ 应用程序默认动态链接到 CRT。
最后,C 和 C++ 运行时现在实现为并行 DLL。仅仅将 MSVCR80.DLL/MSVCP80.DLL/MSVCM80.DLL(此后称为 CRT DLL)复制到 System32 目录已不再足够。您现在必须通过清单加载 CRT DLL。如果您尝试不使用清单加载 CRT DLL,系统将检测到此情况,引发 R6034 断言,并 abort()
。这就是为什么 CRT DLL 现在位于 WinSXS 而不是 System32 目录中的原因。
如果您返回到构建目录,您会注意到有一个新的清单文件,名为 <appname>.exe.manifest(如果不存在,则从链接器命令行中删除 /MANIFEST:NO 开关)。您可以将此清单复制到本地目录,也可以将其嵌入为可执行文件的资源
#include <winuser.h>
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST helloconsole.exe.manifest
使用 rc.exe 工具编译并使用链接器嵌入。或者,您可以使用 mt.exe 工具创建并将清单嵌入到可执行文件中
mt.exe /manifest helloconsole.exe.manifest /outputresource:helloconsole.exe
一旦您嵌入了清单,您的构建现在就类似于 IDE 中创建的应用程序。
IDE 构建
(这部分适用于在 VS IDE 中进行的构建以及遵循上一节的命令行构建)。您已经构建了您的应用程序,现在您尝试在您的机器上运行它。您的程序终于正常运行了
但是,当您尝试在另一台计算机(未安装 Visual C++ 的计算机)上运行时,您会收到此消息
一些故障排除(Dependency Walker)告诉您这与 CRT DLL 有关,但您无法弄清楚这些该死的 DLL 应该放在哪里。您已将 msvcr80.dll 复制到 System32 目录、应用程序目录,甚至是 WinSXS 目录。它仍然不起作用。发生了什么?
现在您的应用程序已配置为通过清单加载 CRT,CRT DLL 必须放置在并行技术识别的目录中。您可以通过共享并行程序集或通过应用程序本地程序集加载 CRT DLL。应用程序本地程序集将在后面的部分中介绍(请参阅下文)。首先,我们将介绍如何将 CRT 作为共享并行程序集安装。
安装共享 CRT
如果您选择安装共享并行程序集,则需要使用 Windows Installer 安装项目进行安装(复制不起作用)。安装程序将创建策略、清单和一些注册表项到 HKLM 注册表(清单和策略对于并行引擎识别隔离库很重要)。
您不能使用第三方安装程序来安装共享并行程序集。安装程序的这部分必须由 Windows Installer 包执行。如果您使用的是 Express Edition(它没有创建安装项目的能力),这是个坏消息。
对于较旧的 Windows(Win2K 及以下),没有并行 DLL 的概念,CRT DLL 需要安装在 System32 目录或与应用程序相同的文件夹中。所有这一切意味着您必须创建一个复杂的安装程序,它在不同的操作系统上表现不同(甚至操作系统的服务包级别也会改变安装程序的行为)。
幸运的是,Microsoft 已经编写了一个合并模块来为您完成所有这些工作(包括注册并行 DLL)。请查看“\Program Files\Common Files\Merge Modules”以查找一组 MSM 文件(忽略那些显示 Beta 2 的属性。它实际上安装的是最终版本)。名称中包含“CRT”的文件是您需要重新分发的(如果您使用 MFC/ATL,您还需要其他 MSM)。通过在您的安装项目中包含这些文件,您的安装程序将把运行时 DLL 正确安装到正确的位置。
如果您不使用 Windows Installer,您可以使用位于“\Program Files\Microsoft Visual Studio 8\SDK\v2.0\Bootstrapper\Packages\vcredist_x86\vcredist_x86.exe”的可执行文件。您所要做的就是在您的第三方安装程序中执行此可再发行组件。
然而,当前版本的合并模块存在一个重大错误。在您安装运行时可再发行组件之前,您必须首先确保安装了以下组件
操作系统 | 可安装 | 所需服务包 | 其他软件 |
Windows 3.x/NT/95 | 否 | N/A | N/A |
Windows 98/ME | 是 | 需要 Internet Explorer 5.0(包含在 Win98SE/ME 中) | 需要 Windows Installer 2.0 |
Windows 2000 | 是 | 需要 Service Pack 3(包含 Windows Installer 2.0) | 需要 Windows Installer 3.0 |
Windows XP | 是 | 推荐 Service Pack 2 | 需要 Windows Installer 3.0(包含在 Service Pack 2 中) |
Windows Server 2003 | 是 | 推荐 Service Pack 1 | 需要 Windows Installer 3.0(Windows Installer 3.1 包含在 Service Pack 1 中) |
Windows Vista | 是 | 无 | 无 |
例如,如果您尝试在 Win2K 计算机上运行您的应用程序,您必须下载最新的 Windows 2000 服务包 (SP4),安装它,重新启动,然后下载 Windows Installer 3.1,安装它并重新启动。最后,您需要复制 vcredist_x86 文件并安装它。如此多的步骤(这些步骤显著改变了操作系统,需要多次重新启动,并且可能会破坏现有应用程序)对于最终用户来说非常不便。如果这些组件中的任何一个不存在,合并模块将以非描述性错误失败,由最终用户承担费用(我在测试时收到错误 1723)。这种复杂的安装过程是 Microsoft 做出的设计决策。
虽然 SP1 将会有一些变化,但我等不及它发布,所以我提出了我自己的安装程序,其中包含了所有必要的更新。这就是本文的主题。
使用代码
该代码是一个引导程序,用于检测 C 运行时的存在,如果不存在,则安装它们。如果 Internet Explorer 或 Windows Installer 需要更新,则会向用户显示一个对话框,要求也安装它们。主要组件以交互模式启动,并设置了 /norestart
开关,以防止它们重新启动计算机。如果您想更改此设置,您需要修改我的源代码中的 InstallRequiredApps()
函数。您还需要单独下载每个组件
- Windows Installer 2.0 可再发行 ANSI (InstMSIA.exe)
- Windows 2000 Service Pack 4 Express 安装程序 (SP4Express_EN.exe)
- Internet Explorer 6.0 Express 安装程序 (ie6setup.exe)。仅当您使用 MFC 时。
- Windows Installer 3.1 可再发行 (WindowsInstaller-kb893803-v2-x86.exe)
- VcRedist_x86.exe(从您的 Visual Studio 目录获取)
将所有这些包放在与解决方案文件相同的目录中。
通过检查 HKLM\Software\Microsoft\Internet Explorer 中“Version”字符串的值来检测 Internet Explorer。通过调用 MSI.DLL 中的 DllGetVersionInfo
导出函数来检查 Windows Installer 的版本。通过 GetVersionEx()
API 检查 Windows 版本。为了检测是否安装了 CRT,我创建了一个简单的 DLL,它动态链接到 C 运行时。如果此 DLL 的 LoadLibrary()
调用失败,我们就知道未安装 C 运行时,应该安装它们。
引导程序的二进制文件将使用 IExpress(Windows 捆绑的打包和部署向导)进行压缩。IExpress 打包程序在所有 32 位 Windows 版本中都有效,检查二进制完整性(例如,不良下载),并自动确定可写入的临时目录(用于 LUA)。虽然我们可以自己实现这些功能(这是本文早期版本所做的),但我宁愿使用经过外部测试的代码,而不是尝试重新发明轮子。
当您制作一个引导程序应用程序(需要在从 NT3.x 到 Windows Vista 的所有操作系统上运行)时,您必须非常小心您调用的 API,以及您如何运行您的应用程序(仅静态链接,无 shlwapi,无 MFC,无 internet,无 ATL,无 .NET,无调试助手等)。不幸的是,Visual C++ 2005 制作的代码在较旧的 Windows 中本质上是无法运行的(它需要 Kernel32.dll 中存在 GetLongPathNameW()
和 IsDebuggerPresent()
)。如果您希望我的引导程序在较旧的 Windows 操作系统上运行(即使它只是一个消息框,告诉用户程序无法在此操作系统上运行),您必须使用较旧的编译器,例如 VC6。
就是这样。我的引导程序返回的返回码是
- 0:如果安装成功或不需要。
- 3:如果抛出未捕获的异常(例如,如果系统资源分配失败),或者调用了
abort()
。 - 192:如果操作系统过时。
- 1223:如果用户取消了安装过程。
- 1613:如果 Windows 2000 需要更新的服务包。
- 其他错误:某个组件安装的返回码。
MFC 特定问题,以及为什么您需要更新 Internet Explorer
如果您动态链接到 MFC DLL 并使用其数据访问类,则需要包含大量的可再发行组件(例如更新的通用控件、MDAC 和 DCOM 更新)。如果您使用其与 Internet 相关的类,则需要更新 Internet Explorer 的版本。请注意,IE 的要求仅适用于 MFC。如果您不使用 MFC,则无需重新分发 Internet Explorer(实际上,如果您使用我的引导程序 #define EXCLUDE_IE60
,则可以编译一个较小的引导程序,其中删除了与 IE 相关的内容)。
MFC 运行时本身将由 vcredist_x86.exe 安装,它还将安装 ATL 和 OpenMP 运行时。如果您使用的是 MFC DLL 的 Unicode 版本,请注意 Microsoft 提供的 DLL 不会在 Win9x 上运行(因为 Win9x 不支持 Unicode)。请按照 Michael Kaplan 的博客中说明的步骤来重建 MFC DLL 以使用 MSLU。
Express 版本
如果您正在运行 Visual C++ Express,即使您无法创建安装项目(并且您缺少 redist 文件夹和 vcredist_x86),您仍然可以使用我的引导程序应用程序。在 Nikola Dudar 的博客中概述了从那些合并模块编译 CRT MSI 的步骤,您可以将其分发给最终用户。第一步是下载并将 WiX 二进制文件提取到一个文件夹(实际上,第一步是下载并安装平台 SDK,但您已经做过了,不是吗?)。
安装 WiX 后(我假设您将其安装到“C:\WiX”),您需要打开 SDK 命令提示符。运行以下命令两次
uuidgen.exe
这应该为您提供两个 GUID(每次运行 uuidgen 时一个),这些 GUID 稍后会有用。记住这两个 GUID。现在创建一个名为“C:\WiX\vccrt.wxi”的 XML 文件。
C:\WiX\vccrt.wxi 的内容应如下所示
<Include>
<?define PRODUCT_ID=00000000-0000-0000-0000-000000000000?>
<?define PACKAGE_ID=00000000-0000-0000-0000-000000000000?>
</Include>
现在不要保存文件,用您之前记住的两个 GUID 替换那些 0。替换完这些 GUID 后,您可以保存文件(您也可以忘记那些您记住的 GUID)。现在创建一个名为 C:\WiX\vccrt.wxs 的文件并给出以下内容
[编辑注释:使用换行符以避免滚动。]
<?xml version="1.0" encoding="utf-8"?>
<?include 'vccrt.wxi' ?>
<Wix xmlns="http://schemas.microsoft.com/wix/2003/01/wi">
<Product Id="$(var.PRODUCT_ID)"
Name="Visual C++ 8.0 Runtime Setup Package"
Language="1033"
Version="1.0.0.0"
Manufacturer="Your Company">
<Package Id="$(var.PACKAGE_ID)"
Description="MSI to redistribute my app"
Comments="MSI to redistribute my app"
Manufacturer="Your Company"
InstallerVersion="300"
Compressed="yes" />
<Media Id="1"
Cabinet="VCCRT.cab"
EmbedCab="yes" />
<Directory Id="TARGETDIR" Name="SourceDir">
<Merge Id="CRT"
Language="0"
src="C:\Program Files\Common Files\Merge
Modules\Microsoft_VC80_CRT_x86.msm"
DiskId="1" />
<Merge Id="CRT Policy"
Language="0"
src="C:\Program Files\Common Files\Merge
Modules\Policy_8_0_Microsoft_VC80_CRT_x86.msm"
DiskId="1" />
</Directory>
<Feature Id="CRT_WINSXS"
Title="CRT WinSXS"
Level="1">
<MergeRef Id="CRT" />
<MergeRef Id="CRT Policy"/>
</Feature>
<InstallExecuteSequence>
<RemoveRegistryValues />
<RemoveFiles />
<InstallFiles />
<WriteRegistryValues />
</InstallExecuteSequence>
</Product>
</Wix>
现在,保存这两个文件后,在 C:\WiX 处打开一个命令提示符,然后键入以下命令
candle.exe vccrt.wxs -out vccrt.wixobj
light.exe vccrt.wixobj -out vccrt.msi
最后,您应该有一个名为 vccrt.msi 的文件。如果您遇到任何错误,您应该查看 Nikola 博客的故障排除部分。创建 vccrt.msi 后,在您打算运行应用程序的所有机器上运行此文件。
如果您希望我的引导程序应用程序调用 vccrt.msi 而不是 vcredist_x86.exe,您需要修改我的代码,使其执行 MSI(说明在 install.txt 中)。
部署的替代方案
Microsoft 提供了多种不使用安装项目即可部署 C 运行时的替代方案。所有这些替代方案都允许您的 VC2005 应用程序在另一台计算机上运行。
安装私有程序集
假设您的应用程序名为 helloconsole.exe,并安装在 C:\test\ 中,您可以将 CRT DLL 复制到应用程序文件夹。
完成此操作后,打开您的 \Program Files\Microsoft Visual Studio 8\VC\Redist\x86\Microsoft.VC80.CRT 文件夹,查找名为 Microsoft.VC80.CRT.manifest 的文件。如果您运行 C++ Express 或没有 redist 文件夹,Microsoft.VC80.CRT.manifest 看起来像这样
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!-- Copyright © 1981-2001 Microsoft Corporation-->
<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
manifestVersion="1.0">
<noInheritable/>
<assemblyIdentity
type="win32"
name="Microsoft.VC80.CRT"
version="8.0.50608.0"
processorArchitecture="x86"
publicKeyToken="1fc8b3b9a1e18e3b" />
<file name="msvcr80.dll"/>
<file name="msvcp80.dll"/>
<file name="msvcm80.dll"/>
</assembly>
将其保存为名为 Microsoft.VC80.CRT.manifest 的 UTF-8 XML 文件(并确保版权符号正确显示)。
获取清单后,将其连同 CRT DLL 一起复制到 C:\Test\ 目录,然后您的应用程序应该成功运行。您的目录现在应该看起来像
- C:\Test\helloconsole.exe
- C:\Test\MSVCR80.dll
- C:\Test\MSVCP80.dll
- C:\Test\MSVCM80.dll
- C:\Test\Microsoft.VC80.CRT.Manifest
如果您正在使用 MFC/ATL/OpenMP,您还需要复制相应的运行时及其清单(请参阅您的 redist 文件夹)。现在您的应用程序应该可以运行了。
练习:当用户(或者更准确地说,管理员)在您复制了这些“应用程序本地”DLL 之后,随后安装 vcredist_x86.exe 包时会发生什么?哪个版本将被加载?
在本文的早期版本中,我假设应用程序本地 DLL 将覆盖 WinSXS DLL(如果共享程序集是 CRT 的更高版本,则不可取)。然而,为了确定起见,我们通过执行一些 PSAPI 测试来确认这一点
#include <vector> #include <iostream> #include <windows.h> #include <psapi.h> #include "stlunicode.h" #pragma comment (lib, "psapi") static const int MAX_MODULES=1024; int main(void) { std::vector<HMODULE> names(MAX_MODULES); DWORD lpcbNeeded = 0; if(EnumProcessModules(GetCurrentProcess(), &names[0], names.size() * sizeof(HMODULE), &lpcbNeeded)) { names.resize(lpcbNeeded / sizeof(HMODULE)); for each(HMODULE item in names) { std::vector<TCHAR> module_name(FILENAME_MAX); DWORD charsCopied = GetModuleFileName(item, &module_name[0], FILENAME_MAX); std::tcout << TEXT("*") << &module_name[0] << TEXT("\n"); } } return 0; }
在同时安装了应用程序本地 DLL 和 WinSXS DLL 的 XP 机器上,输出看起来像这样
*C:\Test\helloconsole.exe
*C:\WINDOWS\system32\ntdll.dll
*C:\WINDOWS\system32\kernel32.dll
*C:\WINDOWS\WinSxS\x86_Microsoft.VC80.
CRT_1fc8b3b9a1e18e3b_8.0.50727.
42_x-ww_0de06acd\MSVCP80.dll
*C:\WINDOWS\WinSxS\x86_Microsoft.VC80.
CRT_1fc8b3b9a1e18e3b_8.0.50727.
42_x-ww_0de06acd\MSVCR80.dll
*C:\WINDOWS\system32\msvcrt.dll
*C:\WINDOWS\system32\PSAPI.DLL
这表明我之前的假设是错误的。共享程序集确实优先于应用程序本地 DLL 加载。除非管理员被强制安装共享 CRT DLL,否则您的应用程序可以使用应用程序本地 DLL。一旦安装了共享 CRT,您的应用程序将开始使用共享 CRT DLL,而您的应用程序本地 DLL 将成为未使用的文件。这种方法有两个优点(不需要安装程序,可以在未打补丁的 Windows 2000/XP/2003 系统上运行),我找不到这种方法的任何其他问题,我认为这种方法为使您的应用程序在另一台计算机上运行提供了一个有吸引力的解决方案。Microsoft 完全支持这种部署方法,尽管将 CRT DLL 的排列方式略有不同。
Microsoft 推荐的方法
如果您查看您的 redist 文件夹,您会注意到文件位于奇怪命名的子目录中,例如 Microsoft.VC80.CRT 或 Microsoft.VC80.MFC。这些目录是特意命名的,因为当您安装您的应用程序时,Microsoft 建议您将这些文件夹原样复制到您的应用程序目录。
根据 Microsoft 的说法,对于 WinXP 及更高版本,您的设置应如下所示
- C:\Test\helloconsole.exe
- C:\Test\Microsoft.VC80.CRT\Microsoft.VC80.CRT.manifest
- C:\Test\Microsoft.VC80.CRT\msvcr80.dll
- C:\Test\Microsoft.VC80.CRT\msvcp80.dll
- C:\Test\Microsoft.VC80.CRT\msvcm80.dll
对于 Win2K 及以下版本,它应该看起来像
- C:\Test\helloconsole.exe
- C:\Test\msvcr80.dll
- C:\Test\msvcp80.dll
- C:\Test\msvcm80.dll
(练习:您将如何安排 DLL,使其同时适用于 WinXP 和 Win2K 的安装?)
您不仅需要 CRT DLL 的冗余副本,Microsoft 还承认这种方法仅适用于您的应用程序是可执行文件 (EXE) 的情况。它不适用于 DLL。对于 DLL,Microsoft 强制您遵循与我描述的类似的设置。但是,如果我的方法适用于 EXE 和 DLL,为什么我们不总是以我描述的方式安排 CRT DLL 呢?
根据 Microsoft 的说法,在 Windows 2000 及以下版本上不支持安装私有并行程序集,但它似乎运行良好。他们更喜欢将 CRT DLL 安装在 System32 目录中。在我看来,这听起来更像是 Microsoft 的恐吓策略。他们说它可能有效也可能无效,但如果您这样做,他们将不会提供帮助。
安装 .NET Framework 2.0 版
如果您使用 CLR 的任何部分(例如,您正在编写 C++/CLI 应用程序),那么您必须安装 .NET Framework。如果是这种情况,那么您可以通过只重新分发 .NET Framework 来一石二鸟。请注意,.NET Framework 是一个更大的可再发行组件,如果您没有 Internet Explorer 6.0 或 Windows 2000(或 XP 或 2003 或 Vista)的最新服务包,它将拒绝安装。同样,您还需要 Windows Installer 3.0。但是,使用 .NET Framework,至少您会收到一个更友好的错误消息,指示缺失的组件(不需要我的引导程序)。
总的来说,最终用户可能需要下载 360 MB 的补丁才能安装 .NET Framework。但是,一旦安装了 .NET Framework,用户可能几乎拥有运行应用程序所需的一切(包括 CRT、MSIE、最新服务包、MSI 3.0)。除了 MFC / ATL / OpenMP 库——如果您使用它们,它们仍然需要重新分发。
重新构建 CRT
如果您拥有 C 运行时的源代码(仅在完整安装且您不是 Express Edition 时可用),那么您可以重建整个 CRT,使其按照您想要的方式运行(在这种情况下,从您自己的目录加载)。但是,分发链接到被修改的 CRT 的应用程序存在法律问题(请务必与您的法律团队审查 VS EULA)。最重要的是,这些重建的 DLL 的名称不得称为 MSVCR80.DLL(或 MSVCP80.DLL 或 MSVCM80.DLL)。您可能有其他原因使用自定义 CRT(例如,为 CRT 启用 MSLU 支持)。如果是这种情况,您应该选择此解决方案。重建 CRT 的步骤可在 MSDN 网站中找到。
与 Microsoft 的 CRT 不同,这个私有 CRT 不应放置在 WinSXS 目录甚至 System32 目录中——它应该放置在与您的应用程序相同的目录中。您的私有 CRT 和 Microsoft 的 CRT 之间的主要区别在于您的构建中没有定义 _CRT_NOFORCE_MANIFEST
。这意味着检查 DLL 是否通过清单加载的代码(它也恰好是阻止您的应用程序在 Windows NT 下运行的代码)被预处理掉了。因此,您可以将此 DLL 与您的应用程序一起本地部署,而无需包含清单(Windows NT 兼容性是一个额外的优势)。
要使用修改后的 CRT,您需要在链接器命令行中添加 /NODEFAULTLIB 开关(项目 -> 项目属性 -> 配置属性 -> 链接器 -> 输入 -> 忽略所有默认库,设置为是)。然后,您需要将重建后的 CRT 的完整路径添加到 附加依赖项 字段(以及任何被排除的 Win32 库)。这需要为您的所有项目重复执行。
另请注意,如果您使用重建的 CRT,并且调用了一个期望 STL 参数(或 FILE*
或任何其他 CRT 特定构造)的 DLL,并且该 DLL 不使用您修改后的 CRT,那么您将遇到严重问题。此类问题包括堆损坏、未释放的锁、调试断言和其他崩溃(即,所有那些只发生在最终用户机器上而不是您的机器上的崩溃)。
这种方法的主要缺点是,如果您在 CRT DLL 中使用了有 bug 的函数,而 Microsoft 发布了该函数的修复程序,您的应用程序将不会得到更新。解决此 bug 的唯一方法是修补您的 Visual Studio 版本并再次重建 CRT。
静态链接可执行文件
最后一个选项是将您的可执行文件静态链接到 CRT(顺便说一句,这就是引导程序能够在没有 CRT 的情况下运行的方式)。要静态链接到可执行文件,请转到 项目 -> 项目属性 -> 配置属性 -> C/C++ -> 代码生成 -> 运行时库(从“多线程 DLL”更改为“多线程”)。
如果您足够小心,您甚至可以让应用程序在 Windows NT 上运行!CRT 中需要 InterlockedCompareExchange
和 GetLongPathNameW
的部分将从您的最终可执行文件中丢弃,因此您的应用程序可以在 Windows NT 上运行。为了实现这一点,您必须小心您调用的 C 函数(不要使用 iostream
、locale
、algorithm
),并且您可能需要升级到 Windows NT 的最新服务包(以及所有服务包后补丁)。
使用此方法并不比重建 CRT 好多少。再次强调,如果您的应用程序包含多个 DLL,并且每个 DLL 都期望 STL/CRT 参数,那么您将遇到严重的错误,直到您通过动态链接 CRT 进行重建。
如果 Microsoft 发布了他们的例程的修复程序,并且您使用了此方法,那么您将全权负责修复您自己和 Microsoft 的错误。
故障排除
如果您使用的是 Windows 2000,请将自己视为我应用程序的测试人员。我尚未在此操作系统上测试我的引导程序,我的一些最复杂的代码专门在此操作系统上运行。如果您使用的是 Win2K,请告诉我它的运行情况。
我的引导程序中有很多硬编码。它不像这个安装程序那样可配置。它不支持 .NET 的安装(尽管只需稍作修改即可支持)。目前它只支持一个命令行选项 (/silent
),而且这个开关的效果也不尽如人意。
如果用户选择安装 Microsoft 的组件,但在安装过程中取消,我的引导程序行为会相当不可靠。如果即使您已部署应用程序后仍然收到错误,那么...
如果您收到与 MSVCR80D.DLL/MSVCP80D.DLL/MSVCM80D.DLL 相关的错误(请注意 D),请重新编译您的应用程序以生成发布版本(转到 构建 -> 配置管理器,并在 活动解决方案配置 处选择“发布”)。如果您是最终用户,请要求应用程序开发人员以发布版本重新构建其程序。
如果您收到与缺失 DLL 相关的其他错误,请尝试使用 Dependency Walker 打开您的应用程序(它位于平台 SDK 文件夹的某个位置——在您安装平台 SDK 的文件夹中搜索 depends.exe。如果不在那里,请从此处下载)。Dependency Walker 应该会告诉您 DLL 是如何加载的,以及是否有任何 DLL 缺失或过时。缺失的 DLL 用问号标记,过时的 DLL 用红色标记。
如果您收到 R6034 错误,请确保您的应用程序包含清单。然后确保您的文件夹包含第二个清单,名为 Microsoft.VC80.CRT.Manifest(参见上文)。请注意,如果您正在运行 Windows 95/NT 或更低版本,则除非您升级操作系统,否则无法解决您的错误。
如果您正在构建 DLL 并且该 DLL 无法加载,请确保该 DLL 具有嵌入式清单。DLL 的清单应使用以下命令创建
mt.exe /manifest helloconsole.dll.manifest /outputresource:helloconsole.dll;#2
根据 MSDN,DLL 的清单需要作为资源嵌入,资源 ID 等于 2。外部清单不适用于 DLL,它们只适用于应用程序,因此除非您能够更改应用程序的清单,否则您应该将 mt.exe 生成的清单嵌入到您的 DLL 中。
一个似乎解决了某些问题的技巧是启用项目属性中的“使用 FAT-32 解决方法”设置。尽管 Microsoft 尚未透露此设置的实际作用,但一些用户回复称它解决了他们遇到的任何问题。
如果您未收到 R6034 错误,Dependency Walker 也未显示任何问题,并且您已完全修补 Windows 且其 Windows 版本受支持,那么问题可能与 DLL 本身有关。请尝试查看以下链接,看它们是否有帮助。
更新
自从本文发布以来,Microsoft 已经注意到了这些抱怨,并在 Visual Studio 2005 的 Service Pack 1 中,修复了他们软件中一些最紧迫的问题。 vcredist_x86.exe 不再需要 MSI 3.0 才能运行,它现在可以在 Windows Installer 版本为 2.0 的机器上运行。这意味着 vcredist 的安装序列变得相对简单
- 运行 InstMSIA.exe(如果已安装 MSI2.0,则此操作不执行任何操作)
- 运行 vcredist_x86.exe(如果已安装 CRT,则此操作不执行任何操作)
- 安装您的应用程序。
您不再需要检查 Windows 版本或服务包级别(上表不再相关)。更重要的是,vcredist_x86.exe 将确保 CRT DLL 安装到正确的位置。此过程适用于所有受支持的 Windows 版本。如果您等不及 Service Pack 1 发布,此修复程序以 QFE 形式提供。
资源、链接和致谢
- Visual Studio 2005 中的部署
- 使用合并模块进行再分发
- 部署示例
- 选择部署方法
- 为什么 VC8 将库安装到 WinSXS
- 在另一台计算机上运行由 C++ Express 构建的 C++ 应用程序
- Michael Kaplan
- Visual C++ 可再发行组件 vcredist_x86.exe (Microsoft 论坛主题)
- VC2005 支持的目标平台
- .NET Fusion
- 在没有 VC++2005 的计算机上运行简单的 Win32 应用程序
- Codeproject Visual C++ 论坛 FAQ(为 VC2002 编写,但今天仍然相关)
感谢
- 泰德·W
- 尼古拉·杜达
- 艾曼·舒克里
- 马丁·洛维尔
历史
- 12/12/2005
- 发布到 CodeProject。
- 13/12/2005
- 为 VC Express 用户添加了几个缺失的步骤。
- 21/12/2005
- 修复了 WiX 步骤的问题。添加了重新构建 CRT 的步骤。
- 22/12/2005
- 更正了 vcredist_x86.exe 的位置。
- 24/02/2006
- 更新了文章,包含了 2006 年发现的后续信息。更改了引言,以展示如果遗漏某些步骤会发生什么。将可执行文件更改为使用 IExpress。