Windows 应用兼容的运作机制——用兼容模式、Shim 与 Compatibility Administrator 为旧应用续命
· 更新日期: · Go Komura · Windows, 兼容模式, Shim, 应用程序兼容性, Compatibility Administrator, 遗留资产, Windows 开发, 现有系统
更新记录(仅首版,2026年08月20日 发布)
- 首次发布
引用本文(DOI(已登记存档): 10.5281/zenodo.22176319)
以下 DOI 指向先前登记的存档,内容可能与当前正文不同。引用当前正文时,请使用本页网址。
Go Komura(2026)。《Windows 应用兼容的运作机制——用兼容模式、Shim 与 Compatibility Administrator 为旧应用续命》。小村软件有限公司。 https://comcomponent.com/zh-CN/blog/windows-appcompat-shims-compatibility-mode/
- DOI(已登记存档)
- 10.5281/zenodo.22176319
- DOI(上次登记版本)
- 10.5281/zenodo.22176320
“一个十年前的业务应用,源代码早已不在,在新的 Windows 11 电脑上无法启动。可是在兼容性选项卡里选了‘Windows XP’,它就跑起来了。这样继续用下去可以吗。”在负责照管旧业务应用的现场,常会接到这类咨询。
兼容模式并不是把整个操作系统退回到旧版 Windows 的功能,而是只针对那一个应用,补上它所假定的旧版 Windows 行为的机制。其核心是插在应用与 Windows API 之间的一小段兼容性修复,也就是 Shim。1
本文面向中小企业的信息系统负责人和 Windows 应用开发者,按能修好什么 → 为什么能跑起来 → 如何应用与分发 → 续命还是迁移怎么判断的顺序梳理。依据 Microsoft Learn 的一手资料,确认勾上复选框之后究竟发生了什么。
1. 先给结论——兼容模式可以用,但要当作续命手段来管理
靠兼容模式维持眼前的业务运转,本身是在使用 Windows 正式提供的机制,属于合理的选择。 Windows 自己也会对已知的应用应用兼容性修复。1 但这并不等于修好了应用。正途是最终把它改成不依赖 Shim 也能运行的形态。
用还是不用,按下面的顺序判断就能理清。
| 判断顺序 | 需要确认的内容 | 阅读章节 |
|---|---|---|
| 1. 判定适用范围 | 问题是否出在应用一侧的 API 用法。是不是驱动程序、16 位、专用硬件的问题 | 第 2~3 章 |
| 2. 选出需要的修复 | 版本判断、路径、权限请求等,补上什么才能跑起来 | 第 4~5 章。现成设置见第 6 章,RunAsInvoker 见第 7 章,单个 Shim 的分发见第 8 章 |
| 3. 定下跑起来之后的运维 | 能否记录设置,并在 Windows 更新时验证。续命到什么时候 | 第 9 章 |
尤其要注意,“对是否为管理员的检查返回成功”和“授予管理员权限”是两回事。Shim 受到与应用相同的安全约束,不会绕过操作系统的保护。12 RunAsInvoker 同样只是抑制权限提升请求、让应用以与调用方相同的权限启动,并不是增加权限的功能。3
理解了机制,就能把“不知道为什么能跑,所以不敢碰”的续命,变成能说清可依赖范围、失效条件与重写时机的续命。
flowchart TB
accTitle: 理解机制会改变续命的质量
accDescr: 不了解机制就使用兼容模式,会变成不敢碰的不稳定续命;理解机制之后,就能有依据地判断能依赖到什么程度、发生什么会失效、何时应该重写
unknown["不了解机制就使用"] --> fear["不敢碰的不稳定续命"]
known["理解机制之后再使用"] --> judge["有依据的判断"]
judge -.-> j1["能依赖到什么程度"]
judge -.-> j2["发生什么会失效"]
judge -.-> j3["何时应该重写"]
图1:同样是续命,不了解机制时的不安与基于理解的判断,质量并不相同。
图中实线表示始终成立的关系,虚线表示带条件的关系(成立条件写在详情页各关系的说明中)。关系的完整列表(共 16 条,附依据与确信度)以及主要概念的定义,汇总在知识地图详情页(日文)。数据:JSON-LD / Turtle
2. 第一步划分范围——这是不是 Shim 能修好的问题
2.1. 能修的只是用户模式下应用一侧的问题
Shim 能做的事,都在修改应用代码同样能做到的范围之内。它是在没有源代码、厂商支持已经结束、当下无法修改等情况下的替代手段,并不比修改代码更强大。1
例如,看到操作系统版本就拒绝启动、假定了旧的写入位置、请求实际上并不需要的管理员权限,这类问题都在考虑范围之内。具体有哪些 Shim,第 5 章再确认。
2.2. 一直调设置也解决不了的情况
下面这些问题需要兼容模式以外的对策。
| 问题 | Shim 无法解决的原因 |
|---|---|
| 内核模式的不兼容 | Shim 在用户模式的进程内运行,因此修不了设备驱动程序。老旧测量仪器、USB 加密狗、打印机的不受支持驱动程序,以及运行在内核中的部分杀毒软件同样如此 |
| 64 位 Windows 上的 16 位应用 | 操作系统不支持运行 16 位应用,启动本身就会失败 |
| 直接访问硬件 | 从用户模式直接操作 I/O 端口或物理内存的前提,超出了 Shim 能伪装的范围 |
| 绕过安全机制 | Shim 无法把应用未被允许的操作变成被允许的操作 |
内核模式的问题和安全方面的限制,都源自 Shim 运行的位置。1 ForceAdminAccess 和 WRPMitigation 只是伪装检查或写入成功、让应用继续往下走,并没有真的改写受保护的资源。2
遇到 16 位的问题时,请不只检查应用本体,也检查安装程序。即使本体是 32 位,只要旧安装包的启动部分(stub)是 16 位,就会出现“应用能跑却装不上”的情况。在 64 位 Windows 上,句柄带有 32 位的有效位,无法截断成 16 位再传递,因此 16 位应用不受支持,启动会以 ERROR_BAD_EXE_FORMAT 失败。4
flowchart TB
accTitle: Shim 不起作用的情形
accDescr: Shim 在用户模式的进程内运行,因此对内核模式的驱动程序问题、16 位应用、直接访问硬件以及绕过安全机制都不起作用
shim["Shim(在用户模式下运行)"] -->|不起作用| drv["内核驱动程序"]
shim -->|不起作用| b16["16 位应用"]
shim -->|不起作用| hw["直接访问硬件"]
shim -->|不起作用| sec["绕过安全机制"]
b16 -.-> fmt["在 64 位上启动本身就失败"]
sec -.-> fake["只是伪装成功让流程继续"]
图2:Shim 仅限用户模式,够不到内核、16 位、直接访问硬件和绕过安全机制。
另外,像旧式复制保护或篡改检测那样,带有自完整性校验的应用可能把 API 挂钩本身判定为异常。并不是只要属于用户模式的应用,就一定能靠 Shim 救回来。
3. 区分相似的机制——Shim、UAC 虚拟化、WOW64、DPI 虚拟化
“旧应用跑起来了”的时候,起作用的机制未必只有 Shim。这里先把 Windows 具备的向后兼容层区分开。实际情况中,也可能是多个机制叠加在一起。
| 层 | 做什么 | 主要对象 |
|---|---|---|
| Shim(兼容模式) | 拦入 API 调用,伪装出与旧版 Windows 相同的响应 | 以旧操作系统为前提编写的各类应用 |
| UAC 虚拟化(文件/注册表) | 把对无权限的 HKLM\Software 或 Program Files 的写入,转发到按用户划分的 VirtualStore |
以管理员权限为前提编写的 32 位应用 |
| WOW64 | 让 32 位应用在 64 位 Windows 上直接运行(提供注册表和文件的 32 位视图) | 各类 32 位应用 |
| DPI 虚拟化 | 让不支持 DPI 的应用按 96 DPI 绘制,再以位图放大显示 | 高 DPI 显示器上的旧应用 |
3.1. UAC 虚拟化:把写入位置按用户分流
UAC 虚拟化是针对不带清单的 32 位交互式进程起作用的过渡措施。Microsoft 自己把它定位为打算从未来的 Windows 中移除的临时技术。5 运行 32 位应用的 WOW64,与把写入位置按用户转发的 UAC 虚拟化,是不同的机制。
关于向 Wow6432Node 的重定向以及 VirtualStore 造成的实际危害与对策,注册表的 32 位/64 位重定向与虚拟化陷阱一文有讨论。本文只写到与 Shim 区分所必需的程度。
flowchart TB
accTitle: UAC 虚拟化的定位
accDescr: UAC 虚拟化是针对不带清单的 32 位交互式进程起作用的过渡措施,会把写入转发到按用户划分的 VirtualStore,但 Microsoft 自己明言这是打算从未来的 Windows 中移除的临时技术
proc["不带清单的 32 位交互式进程"] --> uacv["UAC 虚拟化起作用"]
uacv --> vs["转发到按用户划分的 VirtualStore"]
uacv -.-> tmp["有意在未来移除的临时技术"]
图3:UAC 虚拟化是面向不带清单的 32 位进程的过渡措施,长期上靠不住。
3.2. DPI 虚拟化:把旧的绘制拉伸开
没有声明支持 DPI 的应用,会被当作以 96 DPI(100%)绘制来处理。Windows 会把那张位图拉伸,因此在高 DPI 显示器上显示会发虚。兼容性选项卡中的“替代高 DPI 缩放行为”就是切换这一虚拟化行为的开关。6
flowchart TB
accTitle: DPI 虚拟化的机制
accDescr: 没有声明支持 DPI 的应用会被当作以 96 DPI 绘制来处理,Windows 把位图拉伸后显示因而看起来发虚,兼容性选项卡的替代高 DPI 缩放行为就是切换这一虚拟化行为的开关
app["不声明支持 DPI 的应用"] --> treat["按 96 DPI 绘制来处理"]
treat --> stretch["位图拉伸后显示"]
stretch --> blur["在高 DPI 显示器上发虚"]
tab["替代高 DPI 缩放行为"] -.->|切换虚拟化行为| treat
图4:不支持 DPI 的应用按 96 DPI 处理并被拉伸,兼容性选项卡的替代设置就是这一虚拟化的开关。
4. 为什么兼容模式能让它跑起来——Shim 与启动时的应用
4.1. 替换 API 调用的去向
Windows 的可执行文件(PE 格式)调用外部 DLL 的 API 时,路径上有导入地址表(IAT)。例如调用 GetVersionEx 时,控制会转到 IAT 中写着的地址。
Shim 在应用加载时把这个 IAT 条目改写成 Shim 代码的地址。这就是 API 挂钩。对于用 GetProcAddress 动态取得的 API,则通过挂钩 GetProcAddress 本身来处理。1
拦入的 Shim 会返回旧的操作系统版本,或者改换文件访问的目标,并在需要时调用真正的 API。它向应用展示“应用所期待的 Windows 行为”,向操作系统递交符合当前机制的调用,相当于一个翻译。
flowchart TB
accTitle: Shim 拦入 API 调用的路径
accDescr: 应用的 API 调用要经过 IAT,在加载时把 IAT 条目改写成指向 Shim,Shim 就能拦入其中,先伪装出与旧版 Windows 相同的响应,再按需要调用真正的 API
app["应用"] -->|API 调用| iat["IAT 条目"]
iat -->|加载时改写成指向 Shim| shim["Shim(翻译)"]
shim -->|按需要| api["真正的 Windows API"]
shim -.-> lie["伪装出与旧版 Windows 相同的响应"]
gpa["经由 GetProcAddress 的调用"] -.->|用挂钩处理| shim
图5:Shim 插在应用与 Windows API 之间。被改写的是应用一侧的 IAT,操作系统本身没有变。
改变的是应用一侧的调用路径,不是操作系统本体。 Shim 在与应用相同的安全约束下运行,因此为了使用它也不需要放宽操作系统的安全设置。1
4.2. .sdb 决定“对哪个 EXE 应用什么”
Shim 数据库是扩展名为 .sdb 的二进制文件。它用文件名、大小、校验和、版本这类匹配属性识别可执行文件,并在进程启动时进行比对。把这里用到的术语区分开,如下所示。7
| 术语 | 作用 |
|---|---|
| Appfix(Shim) | 应用 API 挂钩等兼容性修复 |
| Apphelp | 显示“此应用存在兼容性问题”之类的消息 |
| 兼容层(兼容模式) | 把多个 Shim 与标志捆成一束来应用 |
被比对的不只是用户设置了兼容模式的应用。每一次进程启动,都会与操作系统自带的数据库进行比对。 Windows 随附了数千个已知应用的修复,实体位于 %WINDIR%\AppPatch 之下。Microsoft 提供的兼容性修复作为 Windows 的一部分发布,并通过 Windows Update 更新。1
flowchart TB
accTitle: 进程启动时的 Shim 数据库比对
accDescr: 每一次进程启动都会与 Shim 数据库比对,如果存在与匹配属性一致的登记,就会由 Appfix 注入 Shim 或由 Apphelp 显示消息,没有则直接启动
start["进程启动"] --> db["与 Shim 数据库(.sdb)比对"]
db -.-> attr["按文件名、大小等比对"]
db --> hit{"是否有登记?"}
hit -->|是| appfix["Appfix(注入 Shim)"]
hit -->|是| apphelp["Apphelp(显示消息)"]
hit -->|否| plain["直接启动"]
layer["兼容层(兼容模式)"] -.->|多个 Shim 与标志的集合| appfix
图6:比对不只针对设置了兼容模式的应用,而是在每次进程启动时都在进行。
兼容层中不只包含 API 挂钩,也包含启动时的标志。第 7 章的 RunAsInvoker 并不拦截 API,而是作为加载器标志在启动时作用于执行级别的兼容性修复。3
4.3. 也可能是 PCA 发现问题并自行应用
即使管理员没有手动设置,PCA(Program Compatibility Assistant,程序兼容性助手)也可能应用兼容性设置。PCA 监视应用的运行,一旦检测到已知问题的迹象,就会建议应用修复,在部分情况下还会自动应用。8
例如,对于调用已卸载 DLL 中的代码而崩溃的应用会使用 PINDLL,对于写入受保护的 Windows 文件失败的应用会使用 WRPMITIGATION 这类兼容模式。8
flowchart TB
accTitle: PCA 自动应用兼容性设置的流程
accDescr: PCA 监视应用的运行,一旦检测到已知兼容性问题的迹象,就会向用户建议应用修复,在部分情况下则自动应用兼容性设置
run["应用运行"] --> pca["PCA 监视"]
pca --> sign{"是否有已知问题的迹象?"}
sign -->|有| resp{"属于哪种情况?"}
resp -->|以建议方式处理| suggest["建议应用修复"]
resp -->|部分情况| auto["自动应用兼容性设置"]
sign -->|无| none["照常运行"]
auto -.-> ex["例:PINDLL 或 WRPMITIGATION"]
图7:PCA 监视应用的运行,检测到已知问题的迹象时会提出修复建议或自动应用。
“不记得设置过,兼容模式却已经被勾上了”,这种现象多数来自这条路径。它未必是故障或误操作,也可能是 Windows 处理了问题之后的结果。
5. 从症状选择修复——代表性 Shim 与版本判断
5.1. 现成 Shim 能补上什么
下面列出 Microsoft 公开的现成 Shim 中,在业务应用续命时常用的几种。2
| Shim | 能做什么(摘要) |
|---|---|
| WinXPSP3VersionLie 等 VersionLie 系列 | 对操作系统版本查询返回指定的旧版本号(版本伪装) |
| CorrectFilePaths | 把对无法写入或不存在的文件路径的访问改换到别的位置 |
| VirtualRegistry | 对注册表读写进行重定向或伪装(包括伪装版本、模拟不存在的项) |
| ForceAdminAccess | 对“是否属于管理员组”的检查临时返回 True |
| RunAsAdmin / RunAsHighest / RunAsInvoker | 从外部赋予与清单中 requireAdministrator / highestAvailable / asInvoker 声明等效的执行级别 |
| WRPMitigation | 对受保护的操作系统文件与注册表的写入伪装成功,让应用继续往下走 |
| EmulateGetDiskFreeSpace | 把磁盘可用空间最多按 2GB 返回(应对在大容量磁盘上数值溢出的应用) |
| GlobalMemoryStatusLie | 伪装内存状态的报告值(应对启动时内存检查失败的应用) |
| LoadLibraryRedirect | 让应用加载 Windows 一侧的最新 DLL,而不是应用自带的旧系统 DLL |
多数 Shim 所做的,就是返回旧应用所期待的答案。“磁盘容量最多 2GB”“操作系统是 XP”“管理员检查成功”——它们把应用诞生那个年代的前提,只在那个进程内部重现出来。正如第 2 章所见,伪装答案与真正改变权限、资源是两回事。
5.2. 即使不选择兼容模式,GetVersionEx 的返回值也会变
版本伪装并不只发生在手动选择兼容模式的时候。从 Windows 8.1 起,GetVersionEx 返回的值取决于应用的清单。如果 <compatibility> 中没有 <supportedOS> 声明,那么即便实际运行在更新的 Windows 上,返回的也是相当于 Windows 8 的 6.2。有声明时,返回的是所声明的最高操作系统对应的值。例如只声明到 Windows 8.1 GUID 的应用,在 Windows 11 上也是 6.3。910
在此之上,如果应用了 VersionLie 系列的 Shim,就会报告兼容模式中所选操作系统的版本。需要依次查看清单决定的默认响应,以及 Shim 对响应的替换。9
flowchart TB
accTitle: 应用看到的操作系统版本是怎样决定的
accDescr: GetVersionEx 的返回值由清单中有没有 supportedOS 声明决定,没有声明时返回相当于 Windows 8 的 6.2,有声明时返回所声明的最高操作系统对应的值,若应用了 VersionLie 系列 Shim 则被所选操作系统的版本覆盖
q["GetVersionEx 的查询"] --> m{"是否有 supportedOS 声明?"}
m -->|否| v62["返回相当于 Windows 8 的 6.2"]
m -->|是| decl["返回所声明的最高操作系统对应的值"]
v62 --> lie{"是否应用了 VersionLie 系列 Shim?"}
decl --> lie
lie -->|是| fake["返回兼容模式中所选操作系统的值"]
lie -->|否| asis["按原样返回"]
图8:应用看到的 Windows 版本由清单和 Shim 多级决定。
因此,该查看哪里也随症状而分。如果是自家应用把 Windows 11 判定成 Windows 8 而带来困扰,先确认 supportedOS 声明。反过来,如果是旧应用仅凭版本检查就拒绝启动,那么值得试一试 VersionLie。因为这类应用实际运行在新操作系统上多半没有问题,用 Shim 解除启动拒绝的可能性很高。
flowchart TB
accTitle: 版本引起的两类症状与对策
accDescr: 自家应用明明在 Windows 11 上却被判定为 8 时应怀疑清单的 supportedOS 声明,因版本检查而拒绝启动的旧应用则有很高概率能用 VersionLie 突破
sym1["明明是 Windows 11 却被判定为 8"] --> fix1["怀疑 supportedOS 声明"]
sym2["因版本检查而拒绝启动"] --> fix2["尝试用 VersionLie 突破"]
fix2 -.-> why["其运行本身在新操作系统上多半没有问题"]
图9:判定变旧的症状怀疑清单,拒绝启动的症状怀疑 VersionLie。
6. 先在一台机器上确认——兼容性选项卡与 Layers 项
6.1. 查看复选框的保存位置
在属性的兼容性选项卡中保存的设置,会写入注册表的 AppCompatFlags\Layers 项。DXGI 的应用程序兼容设置也用到它,那是兼容层的指定位置。11
reg query "HKCU\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers"
对设置了“Windows XP (Service Pack 3)”“以管理员身份运行此程序”“替代高 DPI 缩放行为”的 EXE,可以看到例如下面这样的值。
HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers
C:\LegacyApp\Gyomu.exe REG_SZ ~ WINXPSP3 RUNASADMIN HIGHDPIAWARE
各项设置与值的典型对应关系如下。这是 Windows 11 的示例,项目名称与值可能随操作系统版本而变化。
| 兼容性选项卡的项目 | 写入的值(示例) | 实质 |
|---|---|---|
| 兼容模式:Windows XP (Service Pack 3) | WINXPSP3 | 把版本伪装等多个 Shim 捆成一束的兼容层 |
| 用 256 色(8 位颜色)运行 | 256COLOR | 放宽到旧的颜色模式 |
| 用 640×480 分辨率运行 | 640X480 | 以低分辨率运行 |
| 禁用全屏优化 | DISABLEDXMAXIMIZEDWINDOWEDMODE | 禁用全屏时的绘制优化 |
| 替代高 DPI 缩放行为(应用程序) | HIGHDPIAWARE | 停止 DPI 虚拟化(位图拉伸)6 |
| 以管理员身份运行此程序 | RUNASADMIN | 启动时请求权限提升 |
6.2. 区分应用给哪些用户,以及何时生效
HKCU 一侧是只对该用户生效的设置。从“更改所有用户的设置”保存时,会写入 HKLM 一侧的同名项,对全部用户生效。做装机配置时,请确认保存在哪一侧。
另外,设置应用到进程的时机,是下一次启动该 EXE 的时候。加载器读取保存下来的值,应用对应的兼容层。
flowchart TB
accTitle: 兼容性选项卡设置生效的流程
accDescr: 兼容性选项卡的设置会以 EXE 路径和值的形式保存到 AppCompatFlags 的 Layers 项,下一次启动该 EXE 时加载器读取这个值,把对应的兼容层应用到进程
tab["在兼容性选项卡中设置"] --> reg["把 EXE 路径与值保存到 Layers 项"]
reg --> boot["下一次启动 EXE"]
boot --> loader["加载器读取该值"]
loader --> apply["把兼容层应用到进程"]
reg -.-> hkcu["HKCU 只对该用户生效"]
reg -.-> hklm["HKLM 对全部用户生效"]
图10:复选框的实质是向 Layers 项写入,生效则在下一次启动时。
6.3. 不要把兼容模式和权限提升设置混为一谈
RUNASADMIN 也与 WINXPSP3 同处在一个 Layers 项里。如果看上去像是“设了兼容模式,连权限提升也一起加上了、或者消失了”,直接查看值就能把兼容层的指定与权限提升的指定区分开。
兼容性选项卡是通往代表性现成兼容层的入口。挑选单个 Shim 再加以组合的用途,由第 8 章的 Compatibility Administrator 承担。在那之前,先看看日常运维中出场很多的 RunAsInvoker。
7. RunAsInvoker——只抑制不必要的权限提升请求
7.1. 区分“请求管理员”与“确实需要管理员权限”
有些旧业务应用因为清单中的 requireAdministrator 声明,或者因为 EXE 名称、内容被误判为安装程序,每次启动都要求 UAC 权限提升。但其中不少只是沿袭 XP 时代的设计才这样请求,实际上并没有使用管理员权限。
RunAsInvoker 会同时覆盖安装程序检测与清单处理,让应用以从父进程继承来的令牌直接启动。调用方是标准用户权限时,就保持这个权限。3
flowchart TB
accTitle: RunAsInvoker 抑制权限提升请求的机制
accDescr: 清单中的 requireAdministrator 声明和被误判为安装程序都会导致启动时请求 UAC 权限提升,应用 RunAsInvoker 后两者都被覆盖,应用以从父进程继承来的令牌直接启动
manifest["requireAdministrator 声明"] --> shim{"是否应用了 RunAsInvoker?"}
detect["被误判为安装程序"] --> shim
shim -->|否| uac["每次启动都请求 UAC 权限提升"]
shim -->|是| token["以父进程的令牌直接启动"]
token -.-> limit["确实需要管理员的处理仍在应用内失败"]
图11:RunAsInvoker 只是覆盖了请求权限提升的原因,并不会增加权限。
7.2. 用环境变量临时试用
即使不制作 .sdb,也可以用环境变量 __COMPAT_LAYER 临时应用同一个兼容层。下面是应用到从该命令提示符或 PowerShell 启动的子进程的示例。
:: 对从这个命令提示符启动的子进程应用 RunAsInvoker
set __COMPAT_LAYER=RunAsInvoker
start "" "C:\LegacyApp\Gyomu.exe"
# PowerShell 的写法
$env:__COMPAT_LAYER = 'RunAsInvoker'
Start-Process 'C:\LegacyApp\Gyomu.exe'
要以与实际使用者相同的标准权限,确认业务所需的操作都能跑通之后再采用。2 如果应用并不需要管理员权限,把命令写成批处理文件、当作快捷方式分发,就能减少本地管理员权限的发放,也能减少每次 UAC 要输密码就得叫信息系统部门的情况。这是一种朝着最小权限原则方向走的兼容技巧。
flowchart TB
accTitle: 分发 RunAsInvoker 批处理的效果
accDescr: 把设置 RunAsInvoker 的两行批处理当作快捷方式分发,就不必给标准用户发放本地管理员权限,也不会因为 UAC 要输密码而叫信息系统部门,运维方式更符合最小权限原则
bat["分发两行批处理"] --> noadmin["不必发放管理员权限"]
bat --> nocall["不会因 UAC 叫信息系统部门"]
noadmin --> lp["符合最小权限原则的运维"]
nocall --> lp
图12:仅靠分发批处理,就能同时减少管理员权限的发放和 UAC 相关的求助。
7.3. 区分生效范围、长期应用与改造
RunAsInvoker 并不会增加权限。 确实需要管理员权限的 HKLM 写入或 Program Files 之下的更新,仍会在应用内部失败。满足条件时,还可能被 UAC 虚拟化转发到 VirtualStore。如果看上去设置保存“不起作用了”,就该怀疑虚拟化。5
环境变量方式只对继承了该环境后启动的子进程生效。要长期应用,就用直接写 Layers 项或分发 .sdb 的办法。兼容性选项卡里没有可以选择 RUNASINVOKER 的项目。
flowchart TB
accTitle: RunAsInvoker 的临时应用与长期应用
accDescr: 用环境变量 COMPAT_LAYER 应用时只对从那里启动的子进程生效,要长期应用则使用直接写入 Layers 项或用 .sdb 分发的办法
env["用环境变量设置"] --> child["只对子进程生效"]
child -.-> tmp["临时应用"]
layers["直接写入 Layers 项"] --> always["长期应用"]
sdb["用 sdb 分发"] --> always
图13:环境变量方式是仅限子进程的临时应用,长期化要靠 Layers 项或 .sdb。
如果能够改造应用,那么比起增加兼容性设置,把配置文件的保存位置移到 %APPDATA% 之下、并在清单中声明 asInvoker,才是本来的修法。3
8. 在组织内部署——Compatibility Administrator 与 sdbinst
8.1. 让应用与工具的 32 位/64 位保持一致
Compatibility Administrator 包含在 Windows ADK(Windows Assessment and Deployment Kit)中。12 安装后 32 位版和 64 位版都会装上,修复 32 位应用用 32 位版,修复 64 位应用用 64 位版。13
另一个注意事项是,不要以管理员身份测试就断定“修好了”。在已提升权限的状态下,UAC 虚拟化和重定向不会按本来的方式工作,可能让人误判结果。修复的效果要用与实际使用者相同的账户和权限来确认。2
flowchart TB
accTitle: 使用 Compatibility Administrator 时的两点注意
accDescr: 修复 32 位应用用 32 位版、修复 64 位应用用 64 位版,修复的效果不要在已提升权限的状态下确认,而要用与实际使用者相同的账户和权限确认
app32["32 位应用"] --> tool32["用 32 位版修复"]
app64["64 位应用"] --> tool64["用 64 位版修复"]
elev["在已提升权限状态下测试"] -.-> wrong["有误判为已修好的风险"]
user["以与实际使用者相同的权限测试"] --> ok["正确确认效果"]
图14:区分使用 32 位版与 64 位版,以及用与实际使用者相同的权限确认,是入门处需要注意的地方。
8.2. 先用兼容层试,再收敛到必要的 Shim 与目标 EXE
自定义兼容性数据库按下面的顺序创建。14
- 在左侧窗格的“Custom Databases”中新建数据库,选择“Create New”→“Application Fix”。
- 输入应用名称与厂商名称,指定目标 EXE 文件。
- 选择“Windows XP 兼容”这类兼容模式(兼容层),先用一整束 Shim 试。
- 需要时再挑选单个兼容性修复。可以收敛成只有 VersionLie、只有 CorrectFilePaths 这样的最小组合。
- 确认文件大小、校验和、版本等匹配条件后保存。
“选出有效的 Shim”与“只对正确的 EXE 生效”两件事都必要。匹配条件通常用默认的基本条件就够,但请保留能够确定应用版本的条件。这是为了在厂商发布修正版之后,不再把旧的修复继续套到新版本上。1415
flowchart TB
accTitle: 创建自定义兼容性数据库的步骤
accDescr: 在新建的数据库中创建 Application Fix,指定应用名称与目标 EXE,先用一整束兼容模式试,需要时再收敛到单个 Shim,最后确认匹配条件并保存
new["新建数据库"] --> fix["选择 Application Fix"]
fix --> info["指定应用名称与目标 EXE"]
info --> layer["先用一整束兼容模式试"]
layer --> single["需要时收敛到单个 Shim"]
single --> match["确认匹配条件并保存"]
match -.-> ver["保留能确定版本的条件"]
图15:Application Fix 先用一整束兼容模式试,再收敛成最小组合,并用匹配条件限定对象。
把做好的 .sdb 在验证机上试用,确认按预期工作之后再进入分发。
8.3. 用 sdbinst 应用与卸载,用 GUID 管理更新
在各台电脑上应用时,以管理员权限运行 sdbinst.exe。除了安装,也可以按文件指定或按数据库 GUID 指定来卸载。15
:: 安装(-q 表示不弹确认的静默方式)
sdbinst -q "C:\Deploy\MyCorpFixes.sdb"
:: 卸载(按文件指定)
sdbinst -q -u "C:\Deploy\MyCorpFixes.sdb"
:: 卸载(按数据库 GUID 指定)
sdbinst -q -u -g {数据库的 GUID}
在兼容性修复越来越多的组织里,比起给每个应用的安装程序各配一个 .sdb,更推荐汇总到全公司一个、或按部门划分的自定义数据库中集中管理的方式。因为比起追踪大量只有一条内容的数据库,更新并重新分发一个汇总的数据库更好管理。15
自定义数据库带有各自的 GUID。安装相同 GUID 的新版本时,旧版本会被自动替换。 分发则搭载在 MSI 包或启动脚本等能够以管理员权限运行的现有渠道上。15
flowchart TB
accTitle: 自定义 .sdb 从创建到分发的流程
accDescr: 用 Compatibility Administrator 创建自定义兼容性数据库并在验证机上测试,用 sdbinst 应用到各台电脑,更新时安装相同 GUID 的新版本,旧版本会被自动替换
make["用 Compatibility Administrator 创建"] --> test["在验证机上测试"]
test --> deploy["用 sdbinst 应用到各台电脑"]
deploy --> update["安装相同 GUID 的新版本"]
update -.-> replace["旧版本被自动替换"]
deploy -.-> inv["会登记到程序和功能中"]
图16:自定义 .sdb 按创建、验证、sdbinst 分发的流程部署,更新用 GUID 管理。
已安装的自定义数据库也会登记到“程序和功能(已安装的应用)”里,可以用于盘点和确认删除。哪台电脑装了哪个 .sdb,也要留在资产管理台账上。
9. 跑起来之后要定的事——续命的维护与迁移的期限
9.1. 区分操作系统提供的修复与本组织的应用判断
即使靠 Shim 跑起来了,也并没有修好应用本身的问题。Shim 是针对特定 API 用法的应急措施,一旦操作系统的实现发生变化,前提就可能不再成立。
Microsoft 提供的 Shim 本身作为 Windows 的一部分,由 Windows Update 来维护。1 另一方面,自定义数据库里应用了什么、用这套组合能否继续支撑业务,要由本组织自己管理。请把每次功能更新时的验证也算进续命的成本里。
flowchart TB
accTitle: 作为应急措施的 Shim 与维护责任
accDescr: Shim 是配合特定 API 用法编出来的谎言,操作系统一侧的实现变化后前提就不再成立,Microsoft 提供的 Shim 由 Windows Update 维护,而自定义数据库里编出来的谎言由本组织照管,每次功能更新的验证都是续命的成本
shim["Shim 是应急性的谎言"] --> break["操作系统实现一变,前提就不成立"]
ms["Microsoft 提供的 Shim"] --> wu["由 Windows Update 维护"]
own["自定义数据库里的谎言"] --> self["由本组织自己照管"]
self --> cost["每次功能更新的验证就是续命的成本"]
图17:Shim 这个谎言的维护责任,在 Microsoft 提供的部分与本组织的自定义部分之间分开。
9.2. 按剩余使用期限、依赖程度和验证体制判断
重要的是,不要仅凭“在兼容模式下跑起来了”这一事实就决定长期使用。能靠 Shim 跑起来,只说明它落进了 Windows 准备好的接盘范围。请结合下面这些维度一起判断。
| 判断维度 | 偏向续命(Shim)的条件 | 偏向迁移、重写的条件 |
|---|---|---|
| 剩余使用期限 | 1~2 年内连同业务一起废止 | 前提是还要继续用 5 年以上 |
| 源代码 | 没有(厂商已消失、已遗失) | 有,或者能够回收这份资产 |
| 依赖的深度 | 只是用户模式 API 兼容性的问题 | 依赖驱动程序、16 位、专用硬件 |
| 替代手段 | 不存在现成产品或新版本 | 迁移目标的产品、技术已经明确 |
| 出故障时的影响 | 停了也能用替代流程维持业务 | 核心业务会被直接冲击 |
| 验证体制 | 每次功能更新都能做运行确认 | 没有验证资源,容易被长期搁置 |
9.3. 决定续命,就把记录、验证、期限凑成一套
- 记录。 留下对哪个 EXE、应用了哪些 Shim 与兼容层、为什么要应用。Layers 项的值和
.sdb的 GUID 也写进台账。不要把“没人知道为什么能跑”的状态交给下一位负责人——这个思路,与接手了没有源代码也没有文档的系统中讨论的保全是一致的。 - 验证。 在 Windows 功能更新时,确认续命中的应用能否启动、主要操作是否正常。并与Windows 10 停止支持后的现实解中讨论的操作系统更换计划联动。
- 定下期限。 像“到下一次核心系统改版为止”“到 2028 年 3 月为止”这样定出续命的终点,同时并行推进迁移的研讨。
flowchart TB
accTitle: 决定续命时的三件套运维
accDescr: 把靠哪些 Shim 才跑起来记入台账,每次功能更新都验证续命中应用的运行,并定下续命的期限、让迁移的研讨并行推进
decide["决定续命"] --> rec["记录:靠什么 Shim 运行,写进台账"]
rec --> verify["验证:每次功能更新都做运行确认"]
verify --> deadline["期限:定下续命的终点"]
deadline --> mig["让迁移的研讨并行推进"]
图18:续命要按记录、验证、期限三件一套来运维,并把迁移研讨的并行也包含进去。
9.4. Shim 用来为迁移的研讨与准备争取时间
迁移的选项随应用所用技术而变。如果是 VB6 写的,就以VB6 应用还能跑多久中整理的全面重写、自动转换、分阶段迁移这三选一为出发点。如果依赖 ActiveX/OCX,可以用ActiveX / OCX 现在该如何处理中“保留、封装、替换”的判断表。
把 Shim 定位成为这类迁移项目安全地争取研讨与准备时间的手段,才是健康的做法。
flowchart TB
accTitle: 迁移一侧的选项与 Shim 的定位
accDescr: 迁移的定式随应用所用技术而变,VB6 写的可选全面重写、自动转换、分阶段迁移三种,依赖 ActiveX 的可用保留、封装、替换的判断表,而 Shim 定位为安全地为迁移项目争取研讨与准备时间的手段
tech{"应用用的是什么技术?"} -->|VB6 编写| vb["重写、自动转换、分阶段迁移"]
tech -->|依赖 ActiveX| ax["保留、封装、替换"]
shim["用 Shim 续命"] -.->|争取研讨与准备的时间| tech
图19:迁移的定式由应用所用技术决定,Shim 定位为为其研讨期争取时间的手段。
10. 小结
兼容模式是只为那一个应用补上以旧版 Windows 为前提的行为的机制。 作为核心的 Shim 通过替换 IAT 等方式拦入 API 调用,补上版本判断与访问目标。Windows 自己也通过默认数据库和 PCA 在使用它。
处理的顺序是:先划分这是不是 Shim 的适用范围,再用兼容性选项卡或 RunAsInvoker 确认所需的设置,组织内部署时用 Compatibility Administrator 和 sdbinst 来管理。工具的 32 位/64 位选择、以实际使用者的权限进行验证、用匹配条件和 GUID 管理对象与版本,是实务上的要点。
不过它仅限用户模式,并不能绕过安全机制。内核驱动程序、64 位 Windows 上的 16 位应用、直接访问硬件这些问题需要另外的对策。RunAsInvoker 同样只是抑制不必要的权限提升请求,并不增加权限。
跑起来之后,记录设置、每次功能更新都验证、定下期限并让迁移并行推进。 把这些都包含进来,才算是依赖兼容模式的完整判断。
下次只勾一个复选框就让旧应用跑起来时,请这样反问一遍。“这个应用是靠哪一句谎言才跑起来的?那句谎言还能通用到什么时候?”答得上来,续命就是一项像样的策略。
相关文章
- 注册表的 32 位/64 位重定向与虚拟化陷阱——Wow6432Node 与“写进去的值不见了”问题
- VB6 应用还能跑多久——运行时的支持状况与务实的 .NET 迁移做法
- ActiveX / OCX 现在该如何处理 - 保留、封装、替换的判断表
- Windows 10 停止支持后的现实解——ESU、LTSC、换机的判断表
- 接手了没有源代码也没有文档的系统——不停机维持运行与维护的实务步骤
- 今日的 Windows 外壳集成——右键菜单、文件关联与 Windows 11 的变化
相关咨询领域
小村软件有限公司承接没有源代码的旧业务应用的行为调查与续命设计(Shim 与兼容模式的选型、自定义 .sdb 的制作与部署)、伴随 Windows 11 迁移的现有应用兼容性验证,以及与续命并行推进的重写、迁移规划。从“在兼容模式下居然跑起来了,可这样下去行不行”这个阶段开始咨询也可以。
参考链接
-
Microsoft Learn, Understanding and Using Compatibility Fixes. 兼容性修复(Shim)通过改写 IAT(导入地址表)重定向 API 调用、动态链接靠挂钩 GetProcAddress 处理、Shim 受与应用相同的安全约束因而无法绕过操作系统的安全机制、仅限用户模式因而修不了驱动程序问题、Shim 能做的修复用改代码同样能做、厂商支持已结束的应用等使用场景,以及 Microsoft 提供的兼容性修复作为 Windows 的一部分发布并通过 Windows Update 更新。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
Microsoft Learn, Compatibility Fixes for Windows 10, Windows 8, Windows 7, and Windows Vista. CorrectFilePaths、VirtualRegistry、ForceAdminAccess、RunAsAdmin/RunAsHighest/RunAsInvoker、WRPMitigation、EmulateGetDiskFreeSpace、GlobalMemoryStatusLie、LoadLibraryRedirect、VersionLie 系列等已知兼容性修复的清单与说明、Compatibility Administrator 的 32 位/64 位版本如何区分使用,以及在已提升权限的状态下测试会让虚拟化与重定向不按预期工作、因而应以实际使用的账户来验证。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Using the RunAsInvoker Fix. RunAsInvoker 兼容性修复让应用以从父进程继承来的令牌启动、同时覆盖安装程序检测与清单处理、不拦截 API 而是作为加载器标志应用,以及在能改代码的情况下正确的修法是在清单中声明 asInvoker。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Running 32-bit Applications. WOW64 是在 64 位 Windows 上运行 32 位应用并隔离文件与注册表冲突的模拟层,以及 64 位 Windows 不支持运行 16 位应用、因句柄有效位数的问题而以 ERROR_BAD_EXE_FORMAT 启动失败。 ↩
-
Microsoft Learn, Registry Virtualization. 注册表虚拟化是把对 HKLM\Software 的全局写入透明重定向到按用户划分的 VirtualStore 的兼容技术、只以 32 位交互式进程为对象而对在清单中指定了 requestedExecutionLevel 的进程和 64 位进程无效,以及它被定位为打算从未来的 Windows 中移除的临时技术。 ↩ ↩2
-
Microsoft Learn, High DPI Desktop Application Development on Windows. 不支持 DPI 的应用会被当作固定以 96 DPI 绘制来处理、在高 DPI 显示器上 Windows 把位图拉伸显示因而看起来发虚,以及 DPI 感知模式(Unaware/System/Per-Monitor)的差异。 ↩ ↩2
-
Microsoft Learn, Application Compatibility Database. 兼容性基础设施以 .sdb 格式的数据库管理问题与解决方案、按可执行文件的属性进行匹配、Apphelp(显示消息)与 Appfix(用 Shim 实现的 API 挂钩),以及把多个 Shim 与标志捆成一束的兼容层(模式)。 ↩
-
Microsoft Learn, Program Compatibility Assistant scenarios for Windows 8. PCA 监视应用运行、检测到已知兼容性问题的迹象后建议应用推荐修复或自动应用(PINDLL、DISABLEUSERCALLBACKEXCEPTION、VIRTUALIZEDELETE、WRPMITIGATION 等),以及从兼容性选项卡和兼容性疑难解答工具应用修复。 ↩ ↩2
-
Microsoft Learn, GetVersionExW function. 从 Windows 8.1 起 GetVersionEx 的返回值取决于清单、未针对 Windows 8.1/10 做清单声明的应用会拿到 Windows 8 的版本值(6.2),以及启用兼容模式时会报告所选操作系统的版本。 ↩ ↩2
-
Microsoft Learn, Targeting your application for Windows. 在应用清单的 compatibility 节中用 supportedOS 元素声明所支持操作系统 GUID 的方法、没有声明时的行为,以及不含 trustInfo 的 32 位 x86 应用会成为 UAC 文件虚拟化(写入重定向到 VirtualStore)的对象。 ↩
-
Microsoft Learn, DXGI overview. 应用程序兼容设置保存在注册表的 HKCU\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers 项中(以 DXGI 的兼容设置为例)。 ↩
-
Microsoft Learn, Download and install the Windows ADK. Windows ADK 中包含 Compatibility Administrator 与 Standard User Analyzer,以及选择 ADK 版本的思路和下载安装方法。 ↩
-
Microsoft Learn, Compatibility Administrator User’s Guide. Compatibility Administrator 提供应用兼容性修复、兼容模式与 AppHelp 消息以及创建自定义数据库的功能,且 32 位版与 64 位版都会安装,32 位应用必须用 32 位版、64 位应用必须用 64 位版。 ↩
-
Microsoft Learn, Creating a Custom Compatibility Fix in Compatibility Administrator. 兼容性修复(旧称 Shim)是拦入 API 调用的一小段代码、在自定义数据库中创建 Application Fix 的步骤(指定应用名称、厂商与目标 EXE、选择兼容模式、选择追加的 Shim、设置匹配条件),以及应在缩小匹配信息的同时保留能正确识别应用的条件。 ↩ ↩2
-
Microsoft Learn, Compatibility Fix Database Management Strategies and Deployment. 自定义兼容性数据库的管理策略推荐集中管理型数据库、兼容性修复应包含版本检查(匹配条件)以免被套用到新版本、用 Sdbinst.exe 进行本地安装(-q、-u、-g 选项)、安装相同 GUID 的新版本时旧版本会被自动卸载,以及用 MSI 或脚本分发的方法。 ↩ ↩2 ↩3 ↩4
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
什么是 OLE 对象 —— 嵌入与链接的机制以及业务文档中的陷阱
在 Word 中嵌入 Excel 表格的功能,本质就是 OLE 对象。本文从嵌入与链接的区别、复合文件与结构化存储、In-Place Activation 的机制,一直讲到链接断开、文件膨胀与安全对策,全部立足于实务视角。
快速启动的真面目 ── Windows 的「关机」为什么和重启不一样
Windows 的「关机」默认会变成混合关机,内核与驱动程序被保存到休眠文件,并在下次启动时还原。本文讲解为什么有些问题只有重启才能解决、对运行时间・更新・Wake on LAN 的影响、确认方法以及是否停用的判断。
Time Travel Debugging ── 把长期运行中不复现的缺陷“录下来”再倒回去
一个月才出一次的缺陷,崩溃转储只拍得到结果。本文讲解如何用 WinDbg 的 Time Travel Debugging(TTD) 录制执行并倒回,涵盖 TTD.exe 的录制设计、环形缓冲区、TTD.Calls 查询,以及与转储的分工。
参数为什么会坏掉 ── Windows 命令行参数的规则
在 Windows 上并不存在参数的数组,传给 CreateProcess 的是一条字符串,切分由接收方完成。本文讲解 CommandLineToArgvW・CRT・.NET 的切分规则,以及 .NET 的 ArgumentList 与 C++ 中正确的组装方法。
Windows 打印驱动程序停止提供 ── 业务应用的报表与标签打印如何应对
Microsoft 正在分阶段推进 v3/v4 打印驱动程序的停止提供,从 2026 年 7 月起会优先选择 IPP 类驱动程序。本文梳理 Windows protected print mode 下会消失什么,并用判断表整理业务应用程序的报表、标签打印中依赖点的盘点方法与...
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
Windows 技术主题
汇整 KomuraSoft LLC 关于 Windows 开发、故障调查与既有资产活用文章的主题中心。
常见问题
汇总了咨询这一主题时常见的问题。
- 勾选兼容模式之后就能运行的应用,可以一直这样用下去吗?
- 就维持眼前的业务运转而言,继续用没有问题。兼容模式的实体是称为 Shim 的用户模式 API 挂钩,是操作系统正式提供的一套设置机制。不过 Shim 终究是不修改应用就让它跑起来的应急措施,一旦操作系统更新改变了前提,它仍可能再次无法运行。请把“靠兼容模式才能运行”这一事实记入台账,并与“重写这个应用,还是有计划地为它续命”的判断放在一起来安排运维。
- 兼容模式的复选框具体做了什么?
- 在属性的兼容性选项卡中保存设置后,目标 EXE 的路径以及“WINXPSP3”“HIGHDPIAWARE”这类值会被写入 HKCU\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers 项。下次启动该 EXE 时,Windows 加载器读取这个值,把对应的兼容层(一束 Shim)应用到进程上。例如选择 Windows XP 兼容模式时,会启用向查询操作系统版本的 API 返回旧版本号的版本伪装等修复。它并没有改变操作系统本身的行为,只是单独对那个进程摆出“旧版 Windows 的样子”。
- 16 位时代的旧应用能在 64 位 Windows 的兼容模式下运行吗?
- 不能。64 位 Windows 用 WOW64 这套机制运行 32 位应用,但不支持运行 16 位应用,尝试启动会以 ERROR_BAD_EXE_FORMAT 失败。这是 Shim 无法绕过的体系结构限制。只有安装程序的启动部分是 16 位的旧安装包,也会因为同样的原因失败。如果确实必需,就只能考虑兼容模式以外的手段,例如使用装有 32 位版 Windows 的虚拟机。
- “不以管理员身份运行就无法启动”的应用,能以标准用户权限运行吗?
- 值得一试的是 RunAsInvoker。在命令提示符里执行 set __COMPAT_LAYER=RunAsInvoker 之后再启动应用,清单中的 requireAdministrator 声明以及安装程序检测所引发的权限提升请求都会被抑制,应用会以与调用方相同的(标准用户)权限启动。如果应用只是“请求管理员权限,实际上并没有使用”,仅凭这一步就能把权限提升从日常运维中去掉。不过权限并不会因此增加,真正需要管理员权限的处理仍会在应用内部失败。请确认运行正常之后再采用。
- Compatibility Administrator 可以从哪里获取?
- 它包含在 Windows ADK(Windows Assessment and Deployment Kit)中。从 Microsoft 网站下载 ADK,安装时选择 Application Compatibility Tools 一类的功能即可使用。32 位版和 64 位版都会被安装,需要注意的是:修复 32 位应用要用 32 位版,修复 64 位应用要用 64 位版。做好的自定义兼容性数据库(.sdb)在各台电脑上用 sdbinst 命令应用。