简介:这款 Microsoft Visual C++ 2008 Redistributable(x86)运行时组件包面向32位 Windows 用户及使用 VC++ 2008 开发环境的人员,主要用于补齐应用程序依赖的运行库,缓解因缺少对应组件而出现的启动失败、动态链接库缺失等兼容问题。压缩包共2个文件,包含可执行安装程序与说明网页文档,整体约1.72MB;安装程序负责部署运行时库并注册相关组件,说明文档则交代安装要求、兼容性信息与已知问题,方便使用前核对系统环境。目前已有2377人学习下载,在旧版软件运行、教学实验和遗留程序维护等场景中仍有较稳定的需求。读者可通过它快速恢复32位程序的正常运行,也能借助说明文档排查依赖缺失与安装异常,并了解不同版本运行库之间的差异,适合需要处理VC++ 2008依赖问题或掌握运行库分发机制的用户参考。
1. 一个 0xc000007b,让三个人没法继续推进
某开发者把本地编译好的工具打包发到测试机,双击之后弹出一行「应用程序无法正常启动 (0xc000007b)」;换一台机器,同一个包变成「由于应用程序配置不正确,应用程序未能启动」;再换一台,干脆提示找不到 msvcr90.dll。三个弹窗、三台机器、同一个根因:Microsoft Visual C++ 2008 Redistributable (x86) 这套 VC90 运行库没装、没装对,或者装对了但那个 32 位进程压根没去加载它。这类故障在只做 64 位开发、或者系统刚重装过的环境里出现频率高得离谱,修复成本却低得离谱:一个几兆的安装包、一次干净的安装,就能把整条启动链路接通。下面按报错出现的先后顺序,把每一步的定位、验证和回退都写清楚。
2. 缺 msvcr90.dll:先搞清楚是谁在找这个文件
2.1 三种弹窗其实是同一件事的三张脸
同一个程序在不同机器上能报出完全不同的文案,很多人因此以为是三个 bug,分别去查,越查越乱。实际上 VC90 运行库缺失时,Windows 的加载器只会在不同阶段报错,译文和弹窗时机就成了三种长相。
第一种是系统直接把缺文件的事实告诉你:「无法启动此程序,因为计算机中丢失 MSVCR90.dll。尝试重新安装该程序以解决此问题」。这条最直白,因为在解析导入表时就没找到 msvcr90.dll 或 msvcp90.dll,加载器连进程都没建起来。
第二种是0xc000007b,即 STATUS_INVALID_IMAGE_FORMAT。字面意思是"映像格式无效",多数人第一反应是文件损坏,其实更常见的是位宽不匹配:一个 32 位 exe 通过某种方式加载到了一个 64 位 DLL,或者反过来。典型场景是有人把C:\Windows\System32下的 64 位 msvcr90.dll 拷到了 32 位程序的目录里,进程加载时校验 PE 头失败,直接返回这个状态码。
第三种是「由于应用程序配置不正确,应用程序未能启动。重新安装应用程序可能会修复此问题」(0xc0150002),或者事件日志里出现 SideBySide 错误。这条最容易被误解成"程序坏了",其实是清单(manifest)里声明的程序集版本在机器上找不到匹配项,SxS 解析失败,进程还没来得及执行第一行代码就被掐掉了。
| 弹窗/状态码 | 加载器停在哪一步 | 最先该查什么 |
|---|---|---|
| 丢失 MSVCR90.dll | 解析导入表 | exe 目录、SysWOW64、WinSxS 是否存在 x86 版 VC90 |
| 0xc000007b | 映像头校验 | DLL 位宽与进程位宽是否一致 |
| 0xc0150002 / SideBySide | 程序集清单解析 | 清单里的 version 与机器上的 WinSxS 版本 |
记住这张表的顺序,后面所有排查都是按它走的。三种报错里,只有第一种能靠"补文件"缓解,后两种补文件基本没用,因为问题不在文件存不存在,而在身份(位宽、版本、公钥标记)对不对。
2.2 为什么把 dll 丢进 System32 是最贵的省事
「缺 dll 就拷 dll」大概是流传最广的一条血泪经验,也是把一台好机器拖进麻烦里最快的方式。它的问题不在于拷进去会不会生效,而在于它破坏了 Windows 加载 VC90 的那套机制,后面再想恢复就得动系统组件。
Windows 从 Vista 之后对 VC90 这类并行程序集(side-by-side assembly)的加载走的是 WinSxS:安装包把Microsoft.VC90.CRT以"程序集"为单位写进C:\Windows\WinSxS,同时在C:\Windows\WinSxS\Manifests下写一份清单,进程启动时由 SxS 服务按 exe 清单里声明的name + version + processorArchitecture + publicKeyToken四元组去匹配。手动把三个 dll 拷进 System32,文件是有了,但 WinSxS 里没有对应的程序集记录,任何依赖清单校验的程序照样报 0xc0150002。
更麻烦的是目录重定向。64 位系统上,32 位进程访问C:\Windows\System32会被重定向到C:\Windows\SysWOW64,而资源管理器里看到的 System32 是真的 64 位目录。于是很多人从网上下了个"vc90 运行库 dll 包",解压出 64 位文件拷进 System32,再让 32 位程序去用——位宽不匹配,正好撞上 0xc000007b。反过来把 32 位 dll 拷进 System32,64 位程序也会以同样方式失败。
还有一层:System32 和 SysWOW64 里的 msvcr90.dll 受 Windows 资源保护(WRP)管辖,覆盖之后系统文件检查会把它当成篡改,下一次sfc /scannow或者系统更新可能把文件换回去,于是"昨天还好好的,今天又报错了"这种无法复现的现象就出现了。我一般会跟人说:拷 dll 能救急,但它把问题从"没装运行库"变成了"系统里混着半个损坏的程序集",后者难查十倍。
注意:
C:\Windows\System32在 64 位系统上装的是 64 位 DLL,32 位程序实际读的是C:\Windows\SysWOW64。往 System32 里补 32 位文件,既不会生效,还会污染受保护目录。
2.3 装一遍、验一遍:四步确认运行库还在
真正的解法只有一个:装 x86 版的 VC90 运行库。但"装过"和"装对了"是两件事,装完不验证,等于没装。下面四步是我每次交付前都会走的固定流程。
第一步,确认机器上登记了哪些版本的 VC++ 2008 运行库。32 位程序读的是WOW6432Node这套 32 位视图,很多排查失败就是因为只翻了 64 位视图。
# 同时查 64 位与 32 位两套卸载项,避免只看一半 $paths = @( 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*' ) Get-ItemProperty $paths -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like '*Visual C++ 2008*' } | Select-Object DisplayName, DisplayVersion, InstallSource | Format-Table -AutoSizeDisplayName里通常会标注x86或x64,DisplayVersion是 9.0.21022 或 9.0.30729 这类 VC90 版本号。如果只看到 x64 那条,说明 32 位程序要的那份根本没装,这就是后面所有报错的起点。Format-Table只是为了读起来清楚,脚本里可以去掉。
第二步,确认文件真的落到了 32 位系统目录。
# 用 32 位 PowerShell 查才准,64 位 PowerShell 查 System32 不会重定向 foreach ($f in 'msvcr90.dll','msvcp90.dll','msvcm90.dll') { $p = Join-Path $env:WINDIR "SysWOW64\$f" if (Test-Path $p) { $v = (Get-Item $p).VersionInfo.FileVersion "{0,-14} 存在,版本 {1}" -f $f, $v } else { "{0,-14} 缺失" -f $f } }$env:WINDIR避免硬编码路径;VersionInfo.FileVersion能直接告诉我们这是 9.0.21022.8 还是 9.0.30729.x,后面对清单匹配时用得上。msvcm90.dll只有托管 C++ 程序才需要,缺它不影响纯原生程序。
第三步,确认 WinSxS 里有 x86 的程序集。这一步是为了区分"文件在、但程序集没注册"的中间状态。
# 只看 x86 架构的 VC90 CRT 程序集,名字里带 x86_ 前缀 Get-ChildItem "$env:WINDIR\WinSxS" -Directory -Filter 'x86_microsoft.vc90.crt_*' | Select-Object -ExpandProperty Name正常情况下至少有一条x86_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.30729.*。如果这里为空但 SysWOW64 有文件,说明曾经被手工拷过或安装被回滚过,属于典型的半损坏状态。
第四步,打开事件查看器看有没有 SideBySide 报错——Windows 日志 → 应用程序,来源筛选SideBySide。这条日志的价值在第 4 章细讲,它的信息量比弹窗大得多。
2.4 装不了运行库时,应用本地部署这条退路
有些机器不让装系统级组件:离线内网、受管控的终端、只读的系统盘、不允许弹安装界面的演示环境。这种情况下有官方支持的替代方案,叫应用本地部署(private assembly):把Microsoft.VC90.CRT整个目录放到 exe 旁边,再让 exe 的清单指向它。
目录结构是这样的:
YourApp\ YourApp.exe YourApp.exe.manifest Microsoft.VC90.CRT\ msvcr90.dll msvcp90.dll msvcm90.dll Microsoft.VC90.CRT.manifest关键在于那个Microsoft.VC90.CRT.manifest必须和 dll 一起来自同一个运行库包,不能自己手写版本号。exe 侧的清单片段长这样:
<!-- 嵌入或旁挂的 exe 清单,四元组要与 Microsoft.VC90.CRT 目录里的清单一致 --> <dependency> <dependentAssembly> <assemblyIdentity type="win32" name="Microsoft.VC90.CRT" version="9.0.30729.6161" processorArchitecture="x86" publicKeyToken="1fc8b3b9a1e18e3b" /> </dependentAssembly> </dependency>name、publicKeyToken、processorArchitecture三个字段是固定的,唯一变化的是version:它必须等于Microsoft.VC90.CRT.manifest里写的版本,多一个数字少一个数字都会让 SxS 解析失败,然后又是熟悉的 0xc0150002。processorArchitecture="x86"也要与 dll 的实际位宽一致,32 位程序配 64 位 dll 就是 0xc000007b。
这条路有个前提:程序本身得允许走本地部署。如果 exe 内嵌的清单是编译时由链接器生成的,并且写死了version="9.0.21022.8",而手上只有 9.0.30729 的包,那就得靠 policy 重定向(第 4 章讲)。另外本地部署的代价是每个应用各自带一份几兆的 dll,几十个程序就是几十份,升级时容易漏。所以它能救急,但不该当成默认方案。
3. 安装包本身装不上:1935、0x80070643、0x80070666 逐个拆
3.1 错误 1935:程序集写进 WinSxS 时被拦下
「错误 1935。安装程序集组件 {…} 的过程中发生错误。HRESULT: 0x80070…」这条报错出现在安装进度条走到一半的时候,弹窗之后往往自动回滚,最后提示安装失败。很多人看到 HRESULT 那一串十六进制就放弃,其实 1935 的含义很固定:MSI 正在把一个程序集写入组件存储,写入过程失败了。
失败点通常在三个地方。一是 Windows Installer 服务状态不对,服务被停、被策略限制成手动、或者被某些清理工具改过启动类型,MSI 拿不到写入组件存储的通道。二是临时目录权限,MSI 会在%TEMP%下展开文件,如果 TEMP 指向一个不存在、空间不足、或者被安全软件锁定的路径,展开阶段就会失败。三是组件存储本身损坏,WinSxS 里的清单文件被破坏,写新程序集时冲突。
先把最外层的东西查干净,再动系统组件:
# 1. 看 Windows Installer 服务是否在跑、启动类型是否为 Manual 或 Automatic Get-Service msiserver | Select-Object Name, Status, StartType # 2. 看 TEMP 指向哪里、是否可写、剩余空间多少 "$env:TEMP" Test-Path $env:TEMP Get-PSDrive C | Select-Object Used, Freemsiserver的状态应该是Stopped(它是按需启动的,平时停着正常),但StartType必须是Manual。如果被改成Disabled,任何 MSI 安装都会以 1935 或 1719 收场。TEMP 那一步是很多人跳过的:曾经有一台机器 TEMP 指向一个已经被删掉的用户配置目录,所有安装包都报 1935,重建目录就好了。
如果这两项都正常,再看组件存储。sfc /scannow检查系统文件,DISM /Online /Cleanup-Image /RestoreHealth修复映像,这两条要按顺序跑,前者快但修复能力弱,后者慢但能补回损坏的组件清单。跑完重启,再装一次运行库。
3.2 0x80070666 与 0x80070643:一个说"已经有了",一个说"装到一半回滚"
这两个错误码长得像,含义完全相反,搞混了会做很多无用功。
0x80070666展开是ERROR_INSTALL_ALREADY_EXISTS,直译"另一个版本已安装"。触发条件很明确:机器上已经存在同一产品的更高或相同版本,安装包拒绝覆盖。它最常出现在两种场景下。第一种是机器上已经有 9.0.30729(SP1),又去装 9.0.21022(RTM),MSI 判断已有版本更新,直接拒装。第二种更绕:x86 和 x64 两套运行库装混了,或者同一版本被不同语言的包重复登记,卸载项里看着只有一条,实际组件存储里已经有一份,安装程序发现自己重复。
处理 0x80070666 的正确做法不是"强行装",而是先搞清楚到底已有什么版本。回到 2.3 第一条 PowerShell 命令,看DisplayVersion。如果已经是 9.0.30729.x 或更高,那这次"装不上"其实不是故障——程序跑不起来是另有原因,去查第 2 章和第 4 章的路子。如果版本比目标低,先卸载再装,不要叠加。
0x80070643是ERROR_INSTALL_FAILURE,属于通用失败码,含义是安装过程中途出错然后回滚。它比 1935 更笼统,日志里必须往上翻才能找到真正的原因,很多时候往上翻几屏就能看到一条 1935 或者 1603 作为根因。所以看到 643 不要单独查,把它当成"去翻日志"的信号。
| 错误码 | 含义 | 典型动作 |
|---|---|---|
| 0x80070666 | 已有同版本或更高版本 | 查版本,必要时先卸载再装 |
| 0x80070643 | 安装中途失败并回滚 | 抓日志,往上看根因错误 |
| 1935 | 程序集写入组件存储失败 | 查 msiserver、TEMP、组件存储 |
| 1603 | 致命错误,常为以上几类的包装 | 必须看日志,无通用解法 |
提示:
/l*v生成的详细日志是这类问题的唯一可靠依据,别靠猜版本和重复点击安装程序。
3.3 带日志强装:从 Return value 3 往上翻三屏
图形界面的安装程序报错只会给你一个数字,日志才给链路。VC90 运行库的安装包本质是个自解压的 MSI,所以正确姿势是先用静默参数跑一遍,把完整日志留下来。
:: /qn 静默无界面,/norestart 抑制自动重启,/l*v 输出详细日志 :: 先用自解压包跑一次,失败后再手工提 msi 出来装 vcredist_x86.exe /q /norestart :: 如果自解压阶段就失败,把安装包解开,直接驱动 msi 并带日志 msiexec /i "vcredist_x86.msi" /qn /norestart /l*v "%TEMP%\vc90_x86.log"/qn表示完全静默,好处是不会被弹窗打断,退出码能直接判断成败;/norestart是必须的,否则安装程序可能自己重启机器,把现场的临时文件清掉;/l*v里的v是 verbose,会记录每一步的自定义动作和属性值,日志动辄几兆,但正因如此才能定位。注意vcredist_x86.exe是自解压壳,它自己也接受/q,两种方式都可以,出问题时用第二种更容易控制。
装完立刻看退出码和日志关键行:
# 退出码 0 成功,3010 成功但需重启,其他基本都是失败 $p = Start-Process msiexec -ArgumentList '/i','vcredist_x86.msi','/qn','/norestart','/l*v',"$env:TEMP\vc90_x86.log" -Wait -PassThru "ExitCode = $($p.ExitCode)" # 抓失败上下文:Return value 3 是 MSI 标记某个动作失败的固定写法 Select-String -Path "$env:TEMP\vc90_x86.log" -Pattern 'Return value 3','Error 1935','Note: 1: 2262','Installation success' -Context 2,4Return value 3是 MSI 的通用失败标记,它本身不含信息,价值在于它前面那几行——真正的原因(哪个自定义动作、哪个 HRESULT、哪个文件路径)都在Return value 3上方三到十行。我的习惯是先搜Return value 3,定位行号,再往回读二十行,基本一次就能看到根因。ExitCode里 3010 是个好数字,它表示成功但需要重启才完全生效,很多人看到非零就以为失败了,白折腾一轮。
3.4 修复顺序:停服务、清临时目录、修组件、再装
排到这一步,剩下的失败原因基本就那几类。按下面的顺序走,不要跳步,因为后面的步骤会覆盖前面的现场。
第一步,停掉可能干扰的服务并清临时目录。安全软件、索引服务、某些同步盘客户端会锁住%TEMP%下的文件。清空临时目录是低风险高收益的操作:
# 先停 Windows Installer(按需服务,停掉无副作用) Stop-Service msiserver -Force -ErrorAction SilentlyContinue # 清理当前用户临时目录里超过一天的文件,避免锁冲突又不误删正在用的 Get-ChildItem $env:TEMP -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.LastWriteTime -lt (Get-Date).AddDays(-1) } | Remove-Item -Recurse -Force -ErrorAction SilentlyContinue-ErrorAction SilentlyContinue是必要的,临时目录里必然有删不掉的文件,报错刷屏会掩盖真正的问题;按最后写入时间过滤是为了不误删刚解压出来的安装文件。
第二步,跑sfc /scannow。它检查的是受保护的系统文件,包括 System32 和 SysWOW64 下的运行库 dll。如果之前有人手工覆盖过 msvcr90.dll,这一步会把文件换回系统版本——注意是换回系统自带的版本,未必是你要的 9.0.30729,所以修完还得重装运行库。
第三步,跑DISM /Online /Cleanup-Image /RestoreHealth。它修的是组件存储,也就是 WinSxS 里的清单和备份。1935 反复出现、sfc又修不好时,这一步是最后一招。跑之前确保系统盘至少有十几个 G 的空间,跑完必须重启。
第四步才是重装运行库。顺序不能反:先修系统再装组件,否则装进去的东西还是坏的。
如果四步走完还失败,最后一招是走"绕过安装"路线,也就是第 2.4 节的应用本地部署。这不是妥协,对于只在少数几台机器上跑的程序,它反而是更可控的方案。
4. SideBySide 配置错误:清单里写死的版本号对不上
4.1 事件查看器里那三条信息决定一切
「应用程序配置不正确」这个弹窗几乎不携带任何有效信息,真正的信息在事件日志里。打开事件查看器 → Windows 日志 → 应用程序,按来源SideBySide筛选,会看到类似这样的一条:
激活上下文生成失败。找不到依赖项程序集 Microsoft.VC90.CRT, processorArchitecture="x86", publicKeyToken="1fc8b3b9a1e18e3b", type="win32", version="9.0.21022.8"。 请使用 sxstrace.exe 进行详细诊断。这条日志把三件事一次说清了:缺的是哪个程序集(Microsoft.VC90.CRT)、要的是哪个位宽(processorArchitecture="x86")、要的是哪个版本(version="9.0.21022.8")。三条信息缺一不可,因为同名程序集在不同位宽、不同版本下是完全不同的东西。
最常见的误判是看到"找不到程序集"就去装运行库,装完发现还是报错。原因通常是版本对不上:程序清单要 9.0.21022.8,机器上装的是 9.0.30729.x,两个版本不是同一个程序集标识,SxS 不会自动向上兼容,除非有 policy 重定向。这跟"装了更高版本就能兼容"的直觉正好相反,也是这类问题最反直觉的地方。
还有一种情况是位宽对不上。日志里写processorArchitecture="x86",机器上只装了 x64 的运行库,那 SysWOW64 里确实没有 x86 的 msvcr90.dll,SxS 自然找不到。这条日志里那个x86是排查的第一顺位,比版本号还重要。
4.2 32 位进程在 64 位系统上的两套视图
64 位 Windows 为了兼容 32 位程序,把注册表和系统目录都做了重定向,形成两套并行的视图。VC90 的登记信息在两边各有一份,只查一边就会得出错误结论。
| 位置 | 32 位进程看到 | 64 位进程看到 |
|---|---|---|
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall | 被重定向到WOW6432Node下 | 真实位置 |
C:\Windows\System32 | 被重定向到C:\Windows\SysWOW64 | 真实目录 |
msvcr90.dll实际路径 | SysWOW64\msvcr90.dll | System32\msvcr90.dll |
这张表解释了两个高频困惑。其一,用 64 位 PowerShell 跑Test-Path C:\Windows\System32\msvcr90.dll返回 True,不代表 32 位程序能用——那检查的是 64 位文件。其二,在注册表编辑器里翻到Uninstall下只有一条 VC++ 2008,不代表只装了一个,32 位那份藏在WOW6432Node里。
验证方法很直接,用两套视图分别查一遍:
# 分别在 64 位与 32 位注册表视图下读 WinSxS 的注册信息 $keys = @( 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\SxS', 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\SxS' ) foreach ($k in $keys) { "[$k] 存在: $(Test-Path $k)" } # 直接看 SysWOW64 里文件的机器类型,确认是 32 位 PE $bytes = [System.IO.File]::ReadAllBytes("$env:WINDIR\SysWOW64\msvcr90.dll") $peOffset = [BitConverter]::ToInt32($bytes, 0x3C) $machine = [BitConverter]::ToUInt16($bytes, $peOffset + 4) "Machine = 0x{0:X4} (0x014C = x86, 0x8664 = x64)" -f $machine读 PE 头这一步看着底层,但它是判断"这个 dll 到底能不能被 32 位进程加载"的唯一硬证据。0x014C是 x86,0x8664是 x64,读到0x8664就说明目录被污染了,怎么装运行库都没用,得先把错的文件清掉。
4.3 sxstrace:把一次失败的加载过程录成文本
事件日志只告诉你"哪个程序集没找到",不告诉你"它按什么顺序找过哪些地方"。要把这个过程录下来,用系统自带的sxstrace.exe。
:: 第一步:开始跟踪,日志写到临时目录 sxstrace trace -logfile:%TEMP%\sxs.etl :: 此时切到另一个窗口,复现一次程序启动失败(双击 exe) :: 看到报错弹窗后,回到这个窗口按回车结束跟踪 :: 第二步:把二进制 etl 解析成可读文本 sxstrace parse -logfile:%TEMP%\sxs.etl -outfile:%TEMP%\sxs.txtsxstrace有几个必须注意的点。一是它同一时间只能跟踪一个会话,第二次trace会报错说已有跟踪在进行,得先结束前一个。二是跟踪期间必须真的去复现失败,什么都不做录出来的是空的。三是在服务器核心版或者被精简过的系统上,这个工具可能不存在,那就只能靠事件日志加上文件系统核对来推。
解析出来的sxs.txt里,找ERROR和not found附近的行,会看到类似"正在查找 ... \WinSxS\Manifests\x86_microsoft.vc90.crt_...manifest"的记录,以及最终"未找到匹配项"的结论。这份记录的价值在于:它证明了系统确实找过、确实没有,从而排除"程序没声明依赖"这类误判——有时候报 SideBySide 错误根本不是运行库的问题,而是 exe 清单里引用了某个自研的私有程序集,那个程序集没跟着发布。
提示:
sxstrace录的是全局激活上下文,一次只录一个问题。同时开多个程序复现,日志会混在一起,反而难读。
4.4 清单重定向与 policy:版本号对不上的正规解法
程序清单写死9.0.21022.8,机器上只有9.0.30729.x,这是 VC90 时代最经典的版本错配。这个问题的正规解法不是改程序清单(那是编译产物,改了签名就废),也不是把旧版本硬塞进 WinSxS,而是用 publisher policy 做重定向。
policy 文件是随运行库一起安装的,位置在C:\Windows\WinSxS\Policies下,名字形如x86_policy.9.0.microsoft.vc90.crt_...。它的作用是告诉 SxS:当有人请求 9.0.21022.8 时,重定向到 9.0.30729.x。装完 SP1 版的运行库后,这个 policy 通常会自动到位,程序不用改就能跑。
判断 policy 是否生效,看两个地方。一是Policies目录下有没有对应的 x86 条目:
# policy 目录里应存在 x86 架构的 9.0 policy 条目 Get-ChildItem "$env:WINDIR\WinSxS\Policies" -Directory -Filter 'x86_policy.9.0.microsoft.vc90.crt_*' | Select-Object -ExpandProperty Name # 注册表里对应的 policy 登记,32 位程序读 WOW6432Node 这一份 Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\Win32Assemblies' -ErrorAction SilentlyContinue | Where-Object { $_.Name -like '*Microsoft.VC90.CRT*' } | Select-Object -ExpandProperty Name第一条返回空,说明 policy 没装,需要重装 SP1 版运行库让安装程序把 policy 写进去。第二条用来交叉验证,如果注册表里有登记但目录里没有文件,属于典型的半损坏,同样靠重装修复。
要注意 policy 只在一个方向上有效:新版本可以重定向旧请求,旧版本不能冒充新请求。所以如果程序清单写的是9.0.30729.6161,而机器上只有 9.0.21022.8,policy 救不了,只能装更新的运行库。这也是为什么我建议交付环境统一用 SP1 及以上版本的 VC90 运行库——它能向下兼容 RTM 时期的程序,反过来不行。
还有一种情况值得单独提:某些程序把清单里的version写成了四段式的完整版本(比如9.0.30729.6161),而不是常见的三段式。SxS 的版本匹配是精确的四段比较,机器上装的是9.0.30729.4148,差一个修订号就匹配失败。这种错配从事件日志里一眼能看出来——日志里的 version 值和 2.3 节查到的FileVersion不一致,那就是它了。
5. 避坑:装 VC++ 2008 运行库时最容易出问题的五个动作
5.1 只装了 x64 版,32 位程序照样报缺 dll
现象:明明在"程序和功能"里看到 Microsoft Visual C++ 2008 Redistributable 装着,版本也对,但 32 位程序启动还是提示缺 msvcr90.dll,或者报 0xc000007b。
原因:装的是 x64 那一份。VC++ 2008 的 x86 和 x64 运行库是两个独立产品,可以并存,DisplayName里分别带(x86)和(x64)。64 位版会把 dll 放进System32,32 位程序读的是SysWOW64,两边互不相通。很多人在 64 位机器上顺手点了推荐下载的 x64 包,以为"系统是 64 位就装 64 位",正好装反。
解决:按 2.3 节的 PowerShell 命令查DisplayName,确认有带x86的那一条。没有就补装 x86 版,不要卸载 x64 版——两者可以共存,卸载反而可能破坏依赖 64 位的其他程序。补装完用第 2.3 节第二步的命令确认SysWOW64下的三个 dll 都到位。
顺带说一句,x86 版的运行库在 64 位系统上是完全合法的组合,不是"装错架构"。32 位进程只能加载 32 位 dll,这是一条物理限制,跟系统本身的位宽无关。
5.2 拿"绿色版运行库合集"覆盖系统目录
现象:用某个工具包里的一键安装或者解压覆盖,程序确实能跑了,但过一段时间又报错,重装运行库时报 1935 或者装完没反应,sfc /scannow跑出一堆文件损坏。
原因:这类合集通常直接往 System32、SysWOW64 里写文件,绕过了 MSI 的程序集注册。文件是有了,但 WinSxS 里没有清单,SxS 加载路径依然失败;同时覆盖写入了受 WRP 保护的文件,系统文件检查会把它们判为篡改。于是就出现了"能用一段时间、然后突然坏掉、还修不好"的循环。
解决:先跑sfc /scannow把被覆盖的系统文件还原,如果sfc报无法修复,再跑DISM /Online /Cleanup-Image /RestoreHealth。修完重启,然后用官方安装包重装一遍运行库。如果之前已经装过、现在处于半损坏状态,先按 3.2 节的方法卸载再装,不要叠加安装。
预防的办法很简单:只在确实装不了系统级组件的机器上走应用本地部署(2.4 节),别去动系统目录。本地部署最坏情况是"这个程序跑不起来",而覆盖系统目录最坏情况是"这台机器上的其他程序也开始报错"。
5.3 在已装 SP1 的机器上再装 RTM,装完反而报 0x80070666
现象:机器上本来就有 VC++ 2008 运行库,为了修某个程序又装了一遍,结果安装程序弹0x80070666,提示已有更新版本。再去看原来的程序,还是跑不起来。
原因:0x80070666是"已存在更高版本",属于 MSI 的版本保护逻辑,不是故障。机器上装的是 9.0.30729(SP1),再装 9.0.21022(RTM)会被拒绝。而原程序跑不起来的原因根本不是运行库版本低,可能是清单版本不匹配(第 4 章)、可能是位宽不对(5.1 节),装 RTM 压根不解决。
解决:先看清已装版本,再决定动作。已有 9.0.30729.x 或更高,就不要动运行库,转头去查清单和位宽。用sxstrace录一次加载过程,看程序到底在要哪个版本——如果它要 9.0.21022.8 而机器上只有 9.0.30729,那就是 policy 没生效,重装 SP1 让它把 policy 写进去,比装 RTM 有用得多。
另外要警惕一种情况:卸载项里看到两条版本不同的 VC++ 2008,那是早期版本升级时留下的残留。先卸载旧的那条,保留新的,再验证程序。
5.4 安全软件拦截 msiexec 写 WinSxS,日志里相关记录都是 1935
现象:安装进度到一半失败,报 1935,日志里能看到某个Microsoft.VC90.CRT程序集的写入动作失败,HRESULT 是拒绝访问类的值。换一台没装安全软件的机器,同一个包一次就装好了。
原因:安全软件的文件防护或者"系统目录保护"功能会拦下对C:\Windows\WinSxS的写入。MSI 写程序集的动作在它眼里跟"释放可疑文件到系统目录"高度相似,于是被阻断。这类拦截通常不弹窗通知,只在安全软件自己的日志里留一条,所以从表面看就是"莫名其妙装不上"。
解决:临时把安全软件的文件防护关掉,或者把msiexec.exe和安装包加入白名单,重装。装完立刻恢复防护,并核对 WinSxS 里确实有x86_microsoft.vc90.crt_*条目。如果企业环境不允许关防护,走应用本地部署,把 dll 放在程序自己的目录,不触碰系统目录,就不会被拦。
判断是不是这个原因,有个快办法:看安装日志里失败动作的目标路径。如果全是 WinSxS 下的路径,且错误码是拒绝访问,基本可以确定。换成用户目录或者程序目录写入能成功,就是权限拦截。
5.5 装完不重启就去验证,SxS 缓存还是旧的
现象:装完运行库,SysWOW64下文件都在,版本也对,但程序还是报"应用程序配置不正确",事件日志里的 SideBySide 报错跟装之前一模一样。重启一次,什么都没改,问题消失了。
原因:SxS 的激活上下文有缓存,安装程序写清单和最后一步注册之间存在时间差,部分场景下需要重启才能刷新。另外 MSI 安装程序返回 3010(成功但需重启)时,很多静默安装脚本把它当成失败,继续往下走,结果后续的验证全在旧状态上做。
解决:把退出码 3010 当成成功处理,脚本里判断0和3010都算通过,并且在两者之后都重启一次再验证。验证前先确认事件日志里的 SideBySide 报错时间戳早于当前时间,避免拿旧日志当新问题。
这条看似琐碎,但它是"装了没用、重启就好"这类玄学现象最常见的解释。我现在做任何运行库相关的验证,都先看一眼机器上次启动时间,如果安装之后没重启过,结论一律不算数。
6. 五分钟定位:从弹窗到结论的四步路径
排到这儿,剩下的工作是把前面几章压缩成一条能背下来的路径。我带人排查时只让对方走四步,每步都有明确的产出,走完要么问题解决,要么手里攥着能定位的日志。
第一步,抄下弹窗原文和状态码,别凭记忆复述。缺 msvcr90.dll、0xc000007b、0xc0150002 这三类,对应的是三条完全不同的排查线(2.1 节的表)。抄完先做一件事:确认程序是 32 位还是 64 位。
:: 读 PE 头判断 exe 位宽,0x014C 是 32 位,0x8664 是 64 位 dumpbin /headers YourApp.exe | findstr /i "machine"没有dumpbin就用 4.2 节那段 PowerShell 读 PE 头,效果一样。这一步决定了后面所有结论——32 位程序必须配 x86 运行库,没有例外。
第二步,用两套注册表视图确认已装运行库,别只看一边。查完对照下表,基本能锁定八成的故障类型。
| 已装情况 | 程序位宽 | 最可能的结论 | 下一步 |
|---|---|---|---|
| 只有 x64 版 | 32 位 | 装错了架构 | 补装 x86 版 |
| 有 x86 版且版本 ≥ 程序要求 | 32 位 | 不是运行库缺失 | 走 sxstrace 查清单 |
| 有 x86 版但版本更低 | 32 位 | 版本错配 | 确认 policy 是否生效 |
| 没装 | 任意 | 直接缺失 | 装 x86 版并重启验证 |
| 有登记但 WinSxS 无对应条目 | 任意 | 半损坏状态 | 卸载后重装 |
第三步,报 0xc0150002 或 SideBySide 错误时,直接上sxstrace,不要猜。录一次、解析一次,日志里那个version=和processorArchitecture=就是答案,和机器上实际有的东西一一对照,差在哪一眼可见。
第四步,安装阶段就失败的,抓/l*v日志,搜Return value 3,往上看二十行。日志里没有Return value 3说明安装其实成功了,问题在别处,别在安装器上耗。
这套路径的价值在于顺序:先定位宽,再定版本,最后才动安装器。顺序反了,就会在"重装第八遍运行库"上浪费一下午。我在一个跨平台桌面的项目上就吃过这个亏——交付前发现测试机报 0xc000007b,第一反应是重装运行库,装了三遍都没用,最后dumpbin一跑,程序是 32 位的,机器上只有 x64 版运行库,中间有人"顺手"把 32 位那份卸了。从那以后我每次交付前都强制走一遍位宽确认加双注册表核对,把这份检查写进发布清单,再没在客户现场因为这个卡过。希望帮到你。
本文还有配套的精品资源,点击获取