Windows 应用程序兼容如何运作 ── 用兼容模式、Shim 与 Compatibility Administrator 延续旧应用
· Go Komura · Windows, 兼容模式, Shim, 应用程序兼容性, Compatibility Administrator, 既有资产活用, Windows 开发, 既有系统
“一份十年前的业务应用、源代码已经不在,在新的 Windows 11 PC 上无法启动。我在属性对话框的兼容性选项卡勾了‘Windows XP’,它就动了。——那到底在做什么?继续依赖这个没问题吗?”这是我们常接到的咨询。
单靠一个复选框就让东西动起来,不安感反而会升高。看起来像魔法的兼容模式,真正身份是夹在应用与 Windows API 之间、返回“谎言”的一小段代码集合,称为 Shim。Windows 自己也大规模使用这套权宜之计,让许多世代以前的应用继续跑,并把机制的一部分开放给用户与管理员。
不了解机制就使用,续命会变成“不知道为什么能跑,所以千万别碰”的不稳定状态。了解机制之后,才能带着理由决定 能安心依赖到什么程度、什么会把它弄坏、何时该重写。
flowchart TB
accTitle: 理解机制会改变续命的质量
accDescr: 不了解机制就使用兼容模式,会变成不敢碰的不稳定续命;理解机制后,才能带着理由决定能依赖到哪里、什么会弄坏它、何时该重写
unknown["不了解机制就使用"] --> fear["不敢碰的不稳定续命"]
known["理解机制后再使用"] --> judge["带理由的判断"]
judge -.-> j1["能依赖到什么程度"]
judge -.-> j2["什么会把它弄坏"]
judge -.-> j3["何时该重写"]
图 1: 同样是续命,不了解机制的不安,与建立在理解上的判断,质量并不相同。
本文面向中小企业的 IT 人员,以及负责旧业务应用的 Windows 应用开发者,依 Microsoft Learn 第一手资料整理兼容模式的真正身份——Shim 的机制、代表性 Shim 能做什么、用 Compatibility Administrator 在组织内套用的方法、Shim 救不了的界限,以及续命与迁移之间的判断。
1. 先讲结论
- 兼容模式的真正身份是 Shim(兼容层)。 兼容性选项卡的设置会写入
HKCU\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers,启动时把一组 Shim 套用到该进程。12 - Shim 是改写导入地址表(IAT)的用户模式 API 拦截。 它拦截应用调用 Windows API 的路径,返回旧版 Windows 会给的答案。它不改操作系统本身。3
- Shim 能做的范围,与在应用里改代码能做的范围相同。 它无法绕过安全性机制,也无法修正内核模式(设备驱动程序)的问题。3
- Microsoft 出货大量现成的 Shim——版本谎言、文件路径重新映射、注册表伪装、管理员检查伪装等。可在 Compatibility Administrator 套用到个别 EXE。4
- Windows 自己默认就在用 Shim。 每次启动都会比对 OS 标准兼容性数据库(.sdb),PCA(Program Compatibility Assistant)也可能检测问题并自动套用兼容设置。15
- “假装成较旧的 Windows 来回答”如今已是默认。 从 Windows 8.1 起,
GetVersionEx不会返回应用未在清单声明的 OS 版本。兼容模式是这套机制的延伸。67 - Shim 对 16 位应用、内核驱动程序依赖、直接硬件访问无效。 尤其 16 位应用在 64 位 Windows 上根本无法执行。8
- 对“要求管理员但实际上不需要”的应用,RunAsInvoker 是标准手法。
__COMPAT_LAYER=RunAsInvoker抑制提升权限要求,让应用以标准权限执行。9 - 在 Shim 下能跑,代表目前可以续命,但真正的路仍是“让它不靠 Shim 也能跑”。 若决定续命,请记录是哪些 Shim 让它能跑,并作为重写判断的材料来管理。
2. 应用程序兼容的全貌 ── Windows 已经具备的向下兼容层
谈 Shim 之前,先列出 Windows 为旧应用准备的机制。即使人们说“在兼容模式下开始能跑了”,真正救下应用的,是这些层的其中一层,或好几层的组合。
| 层 | 做什么 | 典型对象 |
|---|---|---|
| Shim(兼容模式) | 拦截 API 调用,伪装成旧 Windows 会给的回应 | 针对较旧 OS 撰写的应用整体 |
| UAC 虚拟化(文件/注册表) | 把对 HKLM\Software 或 Program Files 没有权限的写入,重定向到每位用户的 VirtualStore |
以管理员权限为前提撰写的 32 位应用 |
| WOW64 | 在 64 位 Windows 上原样执行 32 位应用(提供注册表与文件系统的 32 位视图) | 32 位应用整体 |
| DPI 虚拟化 | 让未声明 DPI 感知的应用以 96 DPI 绘制,再拉伸位图来显示 | 高 DPI 显示器上的旧应用 |
UAC 虚拟化是针对没有清单的 32 位交互进程的过渡措施,Microsoft 自己也写明它是“打算从未来 Windows 版本移除的临时技术”。10 Wow6432Node 重定向与 VirtualStore 造成的实际伤害与对策,详见“注册表的32位/64位重定向与虚拟化陷阱 ── Wow6432Node与「写入的值找不到了」问题”;本文以 Shim 为中心,其他层只谈到必要之处。
flowchart TB
accTitle: UAC 虚拟化所在的位置
accDescr: UAC 虚拟化是针对没有清单的 32 位交互进程的过渡措施,会把写入重定向到每位用户的 VirtualStore,但 Microsoft 自己写明这是打算从未来 Windows 移除的临时技术
proc["没有清单的 32 位交互进程"] --> uacv["应用 UAC 虚拟化"]
uacv --> vs["重定向到每位用户的 VirtualStore"]
uacv -.-> tmp["打算日后移除的临时技术"]
图 2: UAC 虚拟化是没有清单的 32 位进程的过渡措施,不能永久依赖。
关于 DPI 虚拟化的补充:未声明 DPI 感知的应用会被视为以 96 DPI(100%)绘制,Windows 再拉伸位图来显示。这就是旧应用在高 DPI 屏幕上看起来“模糊”的原因,兼容性选项卡的“覆盖高 DPI 缩放行为”就是切换这套虚拟化行为的开关。11
flowchart TB
accTitle: DPI 虚拟化如何运作
accDescr: 未声明 DPI 感知的应用会被视为以 96 DPI 绘制,Windows 拉伸位图因而看起来模糊,兼容性选项卡覆盖高 DPI 设定会切换这套虚拟化行为
app["未声明 DPI 感知的应用"] --> treat["视为以 96 DPI 绘制"]
treat --> stretch["为了显示而拉伸位图"]
stretch --> blur["在高 DPI 屏幕上看起来模糊"]
tab["覆盖高 DPI 缩放行为"] -.->|切换虚拟化行为| treat
图 3: 未感知 DPI 的应用被视为 96 DPI 并被拉伸;兼容性选项卡的覆盖就是这套虚拟化的开关。
3. Shim 真正是什么 ── 改写 IAT、在 API 之间拦截
3.1. 站在应用与 OS 之间的“口译”
Windows 可执行文件(PE 格式)通过 导入地址表(IAT) 呼叫外部 DLL 的 API。应用呼叫 GetVersionEx 时,只是跳到 IAT 里写好的地址。Shim 机制就是利用这一点。加载时把目标 API 的 IAT 项目改写成 Shim 代码的地址,把自己插入应用与 Windows 之间。通过 GetProcAddress 动态取得的 API,则靠拦截 GetProcAddress 本身来处理。3
拦截之后,Shim 可能对“目前 OS 版本是什么”返回旧版本号,或把对不可写位置的文件访问重新映射到别处,必要时再呼叫真正的 API。从应用来看像是“跑在旧 Windows 上”;从 OS 来看像是“一个行为端正的应用在跑”——Shim 是两者之间的口译。
flowchart TB
accTitle: Shim 拦截 API 调用的路径
accDescr: 应用的 API 调用经过 IAT;加载时把 IAT 项目改写成 Shim,就能拦截、伪装成旧 Windows 会给的回应,必要时再呼叫真正的 API
app["应用"] -->|API 调用| iat["IAT 项目"]
iat -->|加载时改写成 Shim| shim["Shim(口译)"]
shim -->|必要时| api["真正的 Windows API"]
shim -.-> lie["伪装成旧 Windows 会给的回应"]
gpa["经 GetProcAddress 呼叫"] -.->|以拦截处理| shim
图 4: Shim 拦截在应用与 Windows API 之间。被改写的是应用端的 IAT,操作系统本身没有改变。
这个设计带出三个重要性质。3
- Shim 以应用端代码执行。 它不是 OS 的一部分,因此受与应用相同的安全性约束。Shim 无法绕过 OS 安全性机制,也不需要为了用 Shim 而放宽安全性设定。
- Shim 能修的,应用端改代码也能修。 Shim 是“没有源代码/修不了”时的替代品,并不比改代码更强。
- 仅限用户模式。 在内核模式执行的设备驱动程序兼容问题,Shim 修不了。
3.2. Shim 数据库(.sdb)与比对
“对哪个 EXE 应用哪个 Shim”的对应表就是 Shim 数据库,副档名为 .sdb 的二进制档。目标应用可执行文件以档名、大小、检查码、版本等属性(比对属性)登录,并在进程启动时比对。处方包含注入 API 拦截的 Appfix(Shim),以及显示“此应用有兼容问题”消息的 Apphelp。数个 Shim 与标志的组合就是 兼容层(兼容模式)。1
容易忽略:这套比对不只发生在已设定兼容模式的应用,而是 每一次进程启动。Windows 出货一份涵盖数千个已知应用的 OS 标准数据库(文件在 %WINDIR%\AppPatch 下),今天你的 PC 上几乎一定有某个旧应用在没人注意的情况下带着 Shim 启动。Microsoft 提供的兼容修正作为 Windows 的一部分出货,并经 Windows Update 更新。3
flowchart TB
accTitle: 进程启动时比对 Shim 数据库
accDescr: 每一次进程启动都会与 Shim 数据库比对;若登录符合比对属性,Appfix 会注入 Shim 或 Apphelp 显示消息,否则原样启动
start["进程启动"] --> db["与 .sdb 比对"]
db -.-> attr["档名、大小等"]
db --> hit{"有登录吗?"}
hit -->|是| appfix["Appfix:注入 Shim"]
hit -->|是| apphelp["Apphelp:消息"]
hit -->|否| plain["原样启动"]
layer["兼容层"] -.->|多个 Shim 与标志的组合| appfix
图 5: 比对发生在每一次进程启动,不只发生在已设定兼容模式的应用。
3.3. PCA ── 自动应用 Shim 的机制
另一条管理员并未意图、却仍可能应用 Shim 的路径是 PCA(Program Compatibility Assistant)。PCA 监视应用执行,检测到已知兼容问题的迹象时,会向用户建议应用修正,或在某些情况下自动应用兼容设置。例如呼叫已释放 DLL 内代码而当掉的应用会被指定 PINDLL,写入受保护 Windows 文件失败的应用会被指定 WRPMITIGATION。5
flowchart TB
accTitle: PCA 如何自动应用兼容设置
accDescr: PCA 监视应用执行,检测到已知兼容问题的迹象时,会向用户建议应用修正,或在某些情况下自动应用兼容设置
run["应用执行"] --> pca["PCA 监视"]
pca --> sign{"已知问题的迹象?"}
sign -->|是| resp{"哪一种情况?"}
resp -->|以建议处理| suggest["建议应用修正"]
resp -->|某些情况| auto["自动应用兼容设置"]
sign -->|否| none["原样执行"]
auto -.-> ex["例:PINDLL 或 WRPMITIGATION"]
图 6: PCA 监视应用执行,检测到已知问题迹象时会建议修正或自动应用。
“我从没设定过,不知何时兼容模式复选框却开着”的身份,很多时候就是这个。既不是故障,也不是误按,而是 Windows 依设计在运作。
4. 代表性 Shim 能做什么
从 Microsoft 公开的现成 Shim 里,挑出延续业务应用寿命时实际常出现的项目。4
| Shim | 能做的事(摘要) |
|---|---|
| WinXPSP3VersionLie 等 VersionLie 系列 Shim | 对 OS 版本查询返回指定的较旧版本(版本伪装) |
| CorrectFilePaths | 把对不可写或不存在文件路径的访问重新映射到别处 |
| VirtualRegistry | 重定向或伪装登录读写(含版本伪装与假装不存在的项) |
| ForceAdminAccess | 对“你是不是 Administrators 组成员?”的检查暂时返回 True |
| RunAsAdmin / RunAsHighest / RunAsInvoker | 从外部给予相当于清单 requireAdministrator / highestAvailable / asInvoker 的执行层级 |
| WRPMitigation | 对受保护 OS 文件与登录项的写入假装成功,让应用能继续 |
| EmulateGetDiskFreeSpace | 把磁盘剩余空间回报为最多 2GB(给在大磁盘上溢位的应用) |
| GlobalMemoryStatusLie | 伪装回报的内存状态值(给启动时内存检查失败的应用) |
| LoadLibraryRedirect | 加载 Windows 目前的 DLL,而不是应用随附的旧系统 DLL |
看这份清单,多数 Shim 都是 “返回旧应用所期待答案的谎言”。磁盘最多 2GB、OS 是 XP、你是管理员——它们只在那个进程里,重现应用诞生那个时代的世界观。
版本伪装已成为“官方默认行为”
版本伪装不是特别的骇客手法。从 Windows 8.1 起,GetVersionEx 返回的值取决于应用的清单。清单 <compatibility> 区段没有 <supportedOS> 声明的应用,无论实际 OS 是什么,一律拿到相当于 Windows 8 的 6.2。有声明时,返回的是 已声明之中最高 OS 为止的值(例如声明到 Windows 8.1 GUID,即使在 Windows 11 也会拿到 6.3)。67
因此“应用看到的 Windows 版本”是依这些堆叠阶段决定的。
- 返回清单所声明 OS 为止的值(没有声明则为 6.2)
- 若应用兼容模式(VersionLie 系列 Shim),则返回所选 OS 的版本6
flowchart TB
accTitle: 应用看到的 OS 版本如何决定
accDescr: GetVersionEx 返回的值由清单是否有 supportedOS 声明决定;没有声明则返回相当于 Windows 8 的 6.2,有声明则返回最高已声明 OS 为止的值,若应用 VersionLie 系列 Shim 则覆盖成所选 OS 版本
q["GetVersionEx 查询"] --> m{"有 supportedOS 声明吗?"}
m -->|否| v62["返回相当于 Windows 8(6.2)"]
m -->|是| decl["最高已声明 OS 为止的值"]
v62 --> lie{"应用了 VersionLie 系列 Shim?"}
decl --> lie
lie -->|是| fake["兼容模式所选的 OS 值"]
lie -->|否| asis["值原样返回"]
图 7: 应用看到的 Windows 版本,由清单与 Shim 的堆叠阶段决定。
若内部应用“依 OS 版本分支,明明是 Windows 11 却被奇怪地判定成 8”,先怀疑清单的 supportedOS 声明。反过来说,因版本检查而拒绝启动的旧应用,很高几率能用 VersionLie Shim 过关。很多情况它只看版本号码,实际行为在较新 OS 上也没问题。
flowchart TB
accTitle: 两种版本引起的症状与对策
accDescr: 内部应用明明是 Windows 11 却被判定成 8,先怀疑清单的 supportedOS 声明;因版本检查拒绝启动的旧应用,很高几率能用 VersionLie Shim 过关
sym1["明明是 Windows 11 却被判定成 8"] --> fix1["怀疑 supportedOS 声明"]
sym2["因版本检查拒绝启动"] --> fix2["试用 VersionLie 过关"]
fix2 -.-> why["实际行为在较新 OS 上往往没问题"]
图 8: 判定过旧就怀疑清单;拒绝启动就怀疑 VersionLie。
5. 兼容模式复选框做了什么
内容 → 兼容性选项卡的设置存在登录的 AppCompatFlags\Layers 项。DXGI 应用兼容设置等也用同一个项来指定兼容层。2 实际看一下。
reg query "HKCU\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers"
在兼容性选项卡对某个 EXE 设定“Windows XP (Service Pack 3)”、“以管理员身份执行此程序”、“覆盖高 DPI 缩放行为”时,会看到类似下面的值。
HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers
C:\LegacyApp\Gyomu.exe REG_SZ ~ WINXPSP3 RUNASADMIN HIGHDPIAWARE
复选框项目与值的代表对应(在 Windows 11 确认;项目名称与值可能随 OS 版本改变)。
| 兼容性选项卡项目 | 写入的值(例) | 实际是什么 |
|---|---|---|
| 兼容模式:Windows XP (Service Pack 3) | WINXPSP3 | 捆绑版本伪装与其他数个 Shim 的兼容层 |
| 降低色彩模式(8 位/256 色) | 256COLOR | 对旧色彩模式的放宽 |
| 以 640 × 480 屏幕分辨率执行 | 640X480 | 以低分辨率执行 |
| 停用全屏幕最佳化 | DISABLEDXMAXIMIZEDWINDOWEDMODE | 停用全屏幕时的绘制最佳化 |
| 覆盖高 DPI 缩放行为(应用程序) | HIGHDPIAWARE | 停止 DPI 虚拟化(位图拉伸)11 |
| 以管理员身份执行此程序 | RUNASADMIN | 启动时要求提升权限 |
记住三点。
- “以管理员身份执行此程序”写在同一个地方。 兼容模式与提升权限标志住在同一个 Layers 项,这就是“设定兼容模式后提升权限跟着来/消失了”这种混乱的来源。直接看值就能把两者分开。
- 写在 HKCU 的是“该用户的设置”。 从选项卡的“变更所有用户的设置”设定时,会写到 HKLM 侧的同名项,应用到所有用户。用映像部署时,要意识自己写的是哪一侧。
- 复选框只是现成层的入口。 选项卡只能选代表性层,不能挑个别 Shim 再组合。那是下一章 Compatibility Administrator 做的事。
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 应用到所有用户"]
图 9: 复选框实际上是写入 Layers 项,应用发生在下一次启动。
6. Compatibility Administrator 实务 ── 建立并分发自定义 .sdb
6.1. 如何取得,以及注意事项
Compatibility Administrator 是 Windows ADK(Windows Assessment and Deployment Kit)内含的工具。12 安装后同时有 32 位与 64 位版本,32 位应用必须用 32 位版,64 位应用必须用 64 位版。13
还有一个重要注意事项。若以已提升权限(管理员)启动 Compatibility Administrator 来测试,UAC 虚拟化与重定向的行为会与真实用户不同,可能误判“已经修好”。务必用与实际用户相同的账户与权限确认修正效果。4
flowchart TB
accTitle: 使用 Compatibility Administrator 时的两项注意
accDescr: 32 位应用用 32 位版、64 位应用用 64 位版,并以与实际用户相同的账户与权限确认修正效果,而不是在已提升权限的状态下确认
app32["32 位应用"] --> tool32["使用 32 位版"]
app64["64 位应用"] --> tool64["使用 64 位版"]
elev["在已提升权限下测试"] -.-> wrong["可能误判修正"]
user["以实际用户相同权限测试"] --> ok["确认效果"]
图 10: 选择 32 位或 64 位版本,以及用与实际用户相同的权限确认,是入口处的注意事项。
6.2. 建立自定义兼容性数据库的步骤
大纲如下。14
- 在 Compatibility Administrator 左窗格的“Custom Databases”建立新数据库,选“Create New”→“Application Fix”
- 输入应用名称与厂商名称,并指定目标 EXE 档
- 选择要应用的兼容模式(层)——先试“Windows XP 兼容”这类捆绑是快捷方式
- 必要时再加入个别兼容修正(Shim)——可以收敛到只留 VersionLie 或只留 CorrectFilePaths 的最小集合
- 确认比对条件(文件大小、检查码、版本等)并储存
比对条件是“只应用到这个 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["留下能识别版本的条件"]
图 11: Application Fix 先试兼容模式捆绑,再收敛到最小集合,并用比对条件限定对象。
先在验证机器上测试建立的 .sdb。确认依预期运作后,再推广到组织。
6.3. 用 sdbinst 分发
把自定义 .sdb 应用到各 PC 的命令是 sdbinst.exe(需要管理员权限)。15
:: Install (-q is silent with no confirmation)
sdbinst -q "C:\Deploy\MyCorpFixes.sdb"
:: Uninstall (by file)
sdbinst -q -u "C:\Deploy\MyCorpFixes.sdb"
:: Uninstall (by database GUID)
sdbinst -q -u -g {database GUID}
作为组织部署策略,Microsoft 建议 整合成一份全公司(或各部门)的自定义数据库并集中管理,而不是随每个应用的安装程序各带一份 .sdb。修正越多,更新并重新分发一份数据库,比分发许多单行数据库更容易。自定义数据库有自己的 GUID,用相同 GUID 安装新版本会自动取代旧版本,更新操作也因此单纯。分发本身请挂在能以管理员权限执行的既有路径,例如包装成 MSI 或启动脚本。15
flowchart TB
accTitle: 从建立自定义 .sdb 到分发的路径
accDescr: 在 Compatibility Administrator 建立自定义兼容性数据库,在验证机器上测试,用 sdbinst 应用到各 PC,更新时以相同 GUID 安装新版本,旧版本会自动被取代
make["在 Compatibility Administrator 建立"] --> test["在验证机器上测试"]
test --> deploy["用 sdbinst 应用到各 PC"]
deploy --> update["以相同 GUID 安装新版本"]
update -.-> replace["旧版本自动被取代"]
deploy -.-> inv["登录在程序和功能"]
图 12: 自定义 .sdb 经建立、验证、sdbinst 分发推广,更新以 GUID 管理。
已安装的自定义数据库会注册为“程序和功能(已安装的应用)”中的项目,也可从那里确认库存与移除。哪些 PC 有哪些 .sdb,是属于资产管理台账的信息。
7. 无效的情况与界限
Shim 不是万灵丹。依设计,下列情况无效。
- 内核模式问题。 Shim 在用户模式进程内执行,因此修不了设备驱动程序不兼容。旧测量仪器、USB 加密狗或打印机的驱动程序若不支持 Windows 11,在应用端套用什么都解决不了。杀毒软件部分这类在核心执行的代码也一样。3
- 16 位应用。 64 位 Windows 不支持执行 16 位应用。句柄在 64 位 Windows 上有 32 个有效位,无法截断后传给 16 位应用,因此启动以
ERROR_BAD_EXE_FORMAT失败。8 即使应用本身是 32 位,那个时代软件包里也有 安装程序启动部分是 16 位 的,会表现成“应用能跑,但装不进去”。 - 直接硬件访问。 假设能直接碰 I/O 埠或实体内存的工业应用,在现代 Windows 的用户模式本来就不被允许,超出 Shim 能伪装的范围。
- 绕过安全性机制。 因为 Shim 与应用受相同安全性约束,它无法让“因权限不足做不到的事”变成可能。ForceAdminAccess 与 WRPMitigation 只是 伪装检查或写入成功,让应用能继续,并不是真的改写受保护资源。34
- 检查自身完整性的应用。 带有旧复制保护或窜改检测的应用,可能把 API 拦截本身当成异常而停止运作。
flowchart TB
accTitle: Shim 无效的情况
accDescr: Shim 在用户模式进程内执行,因此对内核模式驱动程序问题、16 位应用、直接硬件访问、绕过安全性机制无效
shim["Shim(在用户模式执行)"] -->|无效| drv["内核驱动程序"]
shim -->|无效| b16["16 位应用"]
shim -->|无效| hw["直接硬件访问"]
shim -->|无效| sec["绕过安全性机制"]
b16 -.-> fmt["在 64 位上启动本身就失败"]
sec -.-> fake["只伪装成功让应用继续"]
图 13: Shim 仅限用户模式,到不了核心、16 位应用、直接硬件访问或安全性绕过。
而所有 Shim 共通的本质界限是:它是权宜之计。Shim 是针对特定 API 特定用法量身打造的谎言,OS 端实作一变,前提就崩塌。Microsoft 提供的 Shim 经 Windows Update 作为 Windows 的一部分维护,3 但照顾你用自定义数据库套上的谎言,是组织的工作。请把每次功能更新都验证“靠 Shim 续命的应用清单”这项操作,算进续命成本。
flowchart TB
accTitle: 作为权宜之计的 Shim,以及谁负责维护
accDescr: Shim 是针对特定 API 用法的谎言,OS 端实作改变时前提会崩塌;Microsoft 提供的 Shim 经 Windows Update 维护,但自定义数据库套上的谎言由组织照顾,每次功能更新的验证是续命成本
shim["Shim = 权宜之计的谎言"] --> break["OS 变更会弄坏它"]
ms["Microsoft Shim"] --> wu["经 Windows Update"]
own["自定义数据库的谎言"] --> self["组织照顾"]
self --> cost["每次更新都验证是续命成本"]
图 14: 维护 Shim 谎言的责任,分成 Microsoft 提供的集合,与组织自己的自定义集合。
8. RunAsInvoker 的实务价值 ── 只让提升权限要求沉默
Shim 之中,日常 IT 工作最常出现的是 RunAsInvoker。
有些旧业务应用在清单声明 requireAdministrator,或因 EXE 名称、内容被误判成安装程序,每次启动都要求 UAC 提升权限。其中许多只是 XP 时代的惯性在要管理员,实际上并未使用管理员权限。应用 RunAsInvoker Shim 会覆盖安装程序检测与清单,应用以从父进程继承的令牌(= 标准用户权限)启动。9
flowchart TB
accTitle: RunAsInvoker 如何抑制提升权限要求
accDescr: 清单的 requireAdministrator 声明或被误判成安装程序会在启动时引起 UAC 提升权限要求,应用 RunAsInvoker 会覆盖两者,应用以从父进程继承的令牌启动
manifest["requireAdministrator 声明"] --> shim{"应用了 RunAsInvoker?"}
detect["被误判成安装程序"] --> shim
shim -->|否| uac["每次启动都要求 UAC 提升权限"]
shim -->|是| token["以父进程的令牌启动"]
token -.-> limit["真正需要管理员的操作在应用内失败"]
图 15: RunAsInvoker 只覆盖提升权限要求的原因;权限并不会增加。
即使不在 Compatibility Administrator 建立 .sdb,也能用 __COMPAT_LAYER 环境变数暂时应用同一层。
:: Apply RunAsInvoker to child processes started from this command prompt
set __COMPAT_LAYER=RunAsInvoker
start "" "C:\LegacyApp\Gyomu.exe"
# In PowerShell
$env:__COMPAT_LAYER = 'RunAsInvoker'
Start-Process 'C:\LegacyApp\Gyomu.exe'
把这两行做成批处理文件并以快捷方式分发,就能避免把本地管理员权限交给标准用户,IT 也不再每次为了 UAC 密码被叫。这是符合最小权限、把防线加厚的兼容技法。
flowchart TB
accTitle: 分发 RunAsInvoker 批处理文件的效果
accDescr: 把设定 RunAsInvoker 的两行批次以快捷方式分发,就能避免把本地管理员权限交给标准用户,IT 不再为 UAC 密码被叫,操作也符合最小权限
bat["分发两行批次"] --> noadmin["可避免交出管理员权限"]
bat --> nocall["IT 不会为 UAC 被叫"]
noadmin --> lp["符合最小权限的操作"]
nocall --> lp
图 16: 只分发批处理文件,就能同时减少交出管理员权限与被 UAC 叫来的次数。
注意事项也说清楚。
- 权限不会增加。 真正需要管理员权限的操作(写入 HKLM、更新 Program Files 底下等)会在应用内出错,或在条件符合时被 UAC 虚拟化重定向到 VirtualStore。10 若保存设置突然“不行了”,先怀疑虚拟化。
- 环境变数方式只应用到子进程。 永久应用请直接写 Layers 项(选项卡没有 RUNASINVOKER 项目)或以 .sdb 分发才可靠。
- 修正写入目的地才是正途。 若能改应用,把设定档移到
%APPDATA%底下,并在清单声明asInvoker,那才是正确形状。9
flowchart TB
accTitle: RunAsInvoker 的暂时应用与永久应用
accDescr: 以 COMPAT_LAYER 环境变数应用只对从那里启动的子进程有效;永久应用请直接写 Layers 项或以 .sdb 分发
env["以环境变数设定"] --> child["只应用到子进程"]
child -.-> tmp["暂时应用"]
layers["直接写 Layers 项"] --> always["永久应用"]
sdb["以 sdb 分发"] --> always
图 17: 环境变数方式是限于子进程的暂时应用;要永久化,靠 Layers 项或 .sdb。
9. 续命与迁移的判断 ── Shim 让它能跑之后要想什么
在 Shim 下能跑的那一刻是松一口气,但重要的是不要在那里停止思考。在 Shim 下能跑,只代表它碰巧对上 Windows 准备好的插座。 判断轴整理成表。
| 判断轴 | 倾向续命(Shim)的条件 | 倾向迁移/重写的条件 |
|---|---|---|
| 剩余使用期间 | 计划 1–2 年内随业务退役 | 默认还要再用 5 年以上 |
| 源代码 | 没有(厂商消失,或遗失) | 存在,或资产可恢复 |
| 依赖深度 | 仅用户模式 API 兼容问题 | 依赖驱动程序、16 位或专用硬件 |
| 替代方案 | 没有套装产品或新版本 | 目的地产品与技术清楚 |
| 失败时的影响 | 业务可用备援程序运转 | 核心业务直接受害 |
| 验证能量 | 每次功能更新都能确认行为 | 没有验证资源,容易被冻结 |
若决定续命,请把以下三点成套放进操作。
- 记录。 哪个 EXE、哪个 Shim/层、为什么。把 Layers 项值与 .sdb GUID 留在台账。“没人知道为什么能跑”是留给下一个人的最大负债。这与“接手了没有源代码也没有文档的系统 ── 在不中断运行的前提下开展运维保守的实务步骤”所谈的保存心态相同。
- 验证。 把靠 Shim 续命的应用的启动与主要操作,纳入 Windows 功能更新的验证项目。也与 OS 汰换计划绑在一起(Windows 10 停止支持后的现实解决方案 ── ESU・LTSC・更换设备的判断表)。
- 订期限。 决定续命的终点——“到下次核心系统重整为止”、“到 2028 年 3 月”——并并行推进迁移评估。
flowchart TB
accTitle: 决定续命后的三点操作组合
accDescr: 把让它能跑的 Shim 记入台账,每次功能更新都验证靠 Shim 续命的应用行为,订下续命终点的期限,并并行推进迁移评估
decide["决定续命"] --> rec["记录:哪个 Shim 让它能跑,记入台账"]
rec --> verify["验证:每次功能更新确认行为"]
verify --> deadline["期限:决定续命终点"]
deadline --> mig["并行推进迁移评估"]
图 18: 续命以记录、验证、期限三点成套运作,也包含并行推进迁移评估。
在迁移这一侧,标准选项随应用技术而变。VB6 是“VB6 应用能用到什么时候 ── 运行时支持现状与务实的 .NET 迁移做法”整理的全面重写、自动转换、分阶段迁移三选一;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. 总结
- 兼容模式的真正身份是 Shim。兼容性选项卡的设置写入 AppCompatFlags\Layers 项,启动时以改写 IAT 的 API 拦截注入进程。
- Shim 是“返回旧应用所期待答案的谎言”集合。版本伪装、路径重新映射、注册表伪装、管理员检查伪装等现成 Shim 都有提供。
- Windows 自己默认就大量使用 Shim,PCA 也可能自动套用。依赖兼容模式本身,是搭上 OS 正式机制的合理选择。
- 原则上的界限是仅限用户模式、不能绕过安全性,内核驱动程序、16 位应用、直接硬件访问救不了。
- 组织推广是在 Compatibility Administrator(Windows ADK)建立自定义 .sdb,再用 sdbinst 分发。选择 32 位/64 位版本、用实际用户账户测试、以 GUID 管理更新,是实务重点。
- “要求管理员但实际上不需要”的应用,可用
__COMPAT_LAYER=RunAsInvoker降到标准权限。这是让提升权限要求沉默、而不是把权限交出去的防御技法。 - 在 Shim 下能跑是续命,不是方案。记录是什么让它能跑、每次功能更新都验证、订期限让迁移并行——这三点组合,包含在“依赖兼容模式”这项决定里。
下次旧应用因为兼容模式复选框而开始能跑时,再问一次。“这个应用是靠哪一句谎言在跑?那句谎言还能有效多久?” 答得出来,续命就是堂堂正正的策略。
相关文章
- 注册表的32位/64位重定向与虚拟化陷阱 ── Wow6432Node与「写入的值找不到了」问题
- VB6 应用能用到什么时候 ── 运行时支持现状与务实的 .NET 迁移做法
- ActiveX / OCX 现在该如何处理 - 保留、封装、替换的判断表
- Windows 10 停止支持后的现实解决方案 ── ESU・LTSC・更换设备的判断表
- 接手了没有源代码也没有文档的系统 ── 在不中断运行的前提下开展运维保守的实务步骤
- Windows 外壳集成的现在 ── 上下文菜单、文件关联,以及 Windows 11 的变化
相关咨询领域
小村软件有限公司处理没有源代码的旧业务应用行为调查与续命设计(选定 Shim 与兼容模式、建立并推广自定义 .sdb)、为迁移到 Windows 11 所做的既有应用兼容验证,以及与续命并行的重写或迁移规划。从“在兼容模式下开始能跑了,但就这样放着可以吗?”这个阶段来咨询也没问题。
参考链接
-
Microsoft Learn, Application Compatibility Database. 兼容基础建设以 .sdb 格式数据库管理问题与处方、依可执行文件属性比对、Apphelp(显示消息)与 Appfix(经 Shim 的 API 拦截),以及捆绑数个 Shim 与标志的兼容层(模式)。 ↩ ↩2 ↩3
-
Microsoft Learn, DXGI overview. 应用程序兼容设置存在注册表项 HKCU\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers(以 DXGI 兼容设置为例)。 ↩ ↩2
-
Microsoft Learn, Understanding and Using Compatibility Fixes. 兼容修正(Shim)以改写 IAT(导入地址表)重定向 API 调用、动态链接靠拦截 GetProcAddress 处理、Shim 受与应用相同的安全性约束而无法绕过 OS 安全性机制、仅限用户模式而修不了驱动程序问题、Shim 能做的修正改代码也能做、厂商支持已结束的应用等使用情境,以及 Microsoft 提供的兼容修正作为 Windows 一部分出货并经 Windows Update 更新。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
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
-
Microsoft Learn, Program Compatibility Assistant scenarios for Windows 8. PCA 监视应用执行、检测已知兼容问题迹象并建议套用推荐修复或自动应用(PINDLL、DISABLEUSERCALLBACKEXCEPTION、VIRTUALIZEDELETE、WRPMITIGATION 等),以及从兼容性选项卡与 Program Compatibility Troubleshooter 套用修复。 ↩ ↩2
-
Microsoft Learn, GetVersionExW function. 从 Windows 8.1 起 GetVersionEx 返回值取决于清单、未针对 Windows 8.1/10 做清单的应用会拿到 Windows 8 版本值(6.2),以及启用兼容模式时会回报所选 OS 的版本。 ↩ ↩2 ↩3
-
Microsoft Learn, Targeting your application for Windows. 在应用清单 compatibility 区段以 supportedOS 元素声明支持 OS GUID 的方法、没有声明时的行为,以及不含 trustInfo 的 32 位 x86 应用会成为 UAC 文件虚拟化(写入重定向到 VirtualStore)的对象。 ↩ ↩2
-
Microsoft Learn, Running 32-bit Applications. WOW64 是在 64 位 Windows 上执行 32 位应用并隔离文件与注册表冲突的模拟层,以及 64 位 Windows 不支持执行 16 位应用,因句柄有效位数而以 ERROR_BAD_EXE_FORMAT 启动失败。 ↩ ↩2
-
Microsoft Learn, Using the RunAsInvoker Fix. RunAsInvoker 兼容修正以从父进程继承的令牌启动应用、覆盖安装程序检测与清单处理、以加载器标志套用而不拦截 API,以及能修正代码时正确修法是在清单声明 asInvoker。 ↩ ↩2 ↩3
-
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, 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
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
Win32 线程池 API ── 用 CreateThreadpoolWork 实现不自建线程的并发
原生代码里是否到处调用 CreateThread?本文根据一手资料讲解 Vista 重新设计的 Win32 线程池 API——work、timer、wait、io 四种对象、清理组,以及回调中禁止做的事。
命名管道实务 ── 从设计到安全的 Windows 标准 IPC
以实务视角讲解 Windows 标准进程间通信——命名管道。根据一手资料整理字节模式与消息模式的选择、同时处理多客户端的服务器设计、ACL 与模拟的安全性,以及 .NET 的命名管道流。
从睡眠恢复后损坏的应用 ── Windows 电源事件机制与扛得住恢复的业务应用
打开笔记本后业务应用的连接全断了——原因是设计从未考虑睡眠。本文根据一手资料整理 WM_POWERBROADCAST 通知流程、Modern Standby 行为、断开/重连设计、睡眠抑制与调查命令。
DllMain 与加载器锁 ── 「DLL 初始化里什么都别做」的真正原因
为何不得从 DllMain 调用 LoadLibrary 或与其他线程同步。本文根据一手资料说明加载器锁如何串行化每一条 DLL 通知、结构上必然死锁的经典场景、推迟初始化的正确设计,以及如何调查挂起。
「无响应」的真正含义 ── Windows 如何判定应用已挂起,以及如何设计不挂起的应用
Windows 的「无响应」是操作系统判定窗口已 5 秒未取出消息并换成幽灵窗口的机制。本文说明该判定的内部、挂起的经典原因、把重活移出 UI 线程的设计,以及调查挂起的步骤。
相关主题
与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。
Windows 技术主题
汇整 KomuraSoft LLC 关于 Windows 开发、故障调查与既有资产活用文章的主题中心。
与本主题相关的服务
本文与以下服务页面相关联,欢迎从最接近的入口查看。
Windows 应用程序开发
支持包含常驻处理、设备联动、运行日志与可维护结构的 Windows 桌面应用程序。
既有资产活用 & 迁移支持
在持续活用 COM / ActiveX / OCX 资产、原生代码与 32 位依赖的同时,协助规划阶段性的迁移。
常见问题
汇总了咨询这一主题时常见的问题。
- 勾选兼容模式后应用就能跑了,这样继续用可以吗?
- 就短期维持业务运转而言,可以。兼容模式的本质是称为 Shim 的用户模式 API 拦截,是操作系统正式提供的机制。不过 Shim 仍是“不修正应用也能跑”的权宜之计,另一次 OS 更新可能改变前提、再次让它失效。请把“靠兼容模式才能跑”这件事记入台账,并把这份记录纳入“重写应用”或“有计划地续命”的判断材料。
- 兼容模式的复选框实际上做了什么?
- 在属性对话框的兼容性选项卡保存设置时,Windows 会把目标 EXE 路径与“WINXPSP3”“HIGHDPIAWARE”这类值写入 HKCU\Software\Microsoft\Windows NT\CurrentVersion\AppCompatFlags\Layers 项。下次启动该 EXE 时,Windows 加载器读取这个值,并把对应的兼容层(一组 Shim)套用到进程。例如 Windows XP 兼容模式会让查询版本的 API 返回旧值,借此伪装 OS 版本。操作系统本身没有被改写,只是对那个进程假装成较旧的 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)在各 PC 上以 sdbinst 命令应用。