什么是 Reg-Free COM——无需注册使用 COM 的机制
· 小村 豪 · COM, Reg-Free COM, Registration-Free COM, Windows开发, 老旧技术
在 COM / ActiveX / OCX 相关的项目中,每次分发和更新都会遇到同样的老问题:
- 需要
regsvr32 - 往往还需要管理员权限
- 与其他应用安装的不同版本相互冲突
- 卸载后连累到其他产品
- 在开发机上能跑,到干净环境却跑不起来
能大幅减少这些麻烦的就是 Reg-Free COM。 不过,名字听起来虽然厉害,但它并不是那种「COM 的所有麻烦都会消失」的魔法。它主要消除的是 被全局注册拖累的麻烦,而位数(bitness)、依赖 DLL、类型库、线程模型等方面的难题依然存在。
本文将围绕 在 Windows 桌面应用中把 COM DLL / OCX 限定在应用本地使用 这一场景,来梳理 Reg-Free COM。
1. 先说结论(一句话)
先用一种比较粗略但实用的说法来概括:
- Reg-Free COM 是把 COM 的注册信息保存在清单中,而不是注册表里的方式
- 运行时,
CoCreateInstance或CLSIDFromProgID的解析会先查看 激活上下文(activation context) - 因此,COM DLL / OCX 就能够 按应用程序私有化
- 主要优点是 便于 XCOPY 部署、更容易避免版本冲突、卸载时不易破坏其他程序
- 但是,32 位/64 位的问题依然存在,这一点靠意志力也跨不过去
- 此外,依赖 DLL、类型库、设计时引用、非标准的注册依赖 都需要另行考虑
- 在实务中,当你希望 把应用专用的 COM 组件放在一旁独立管理 时,这种方式相当合适
总而言之,Reg-Free COM 是一种 把 COM 的激活方式拉回到应用程序单位 的机制。
2. 本文所说的 Reg-Free COM
Reg-Free COM 是 Registration-Free COM 的简称。中文里有时也写作「免注册 COM」。
这里所说的「免注册」,指的是 使用 COM 时不完全依赖 HKCR / CLSID / InprocServer32 等全局注册表注册,
并不意味着 COM 本身消失,也不意味着 不再需要 GUID。
本文主要涉及以下对象:
- 原生 COM DLL
- 基于 ATL 的 COM 服务器
- ActiveX / OCX
- 基于 .NET Framework 的 COM 互操作
- 使用 .NET 5+ / .NET 8 的 COM host 进行的公开
反过来,本文想强调的重点有两个:
- Reg-Free COM 讨论的是「激活(activation)」这件事
- 类型信息的分发和设计时的引用设置,可能仍是另一个独立话题
如果把这两点混在一起讨论,事情就会变得相当混乱。
3. 先用一张图整理
先看一张整体示意图会更直观:
flowchart LR
APP["MyApp.exe"] --> AM["Application Manifest"]
AM --> DEP["dependentAssembly"]
DEP --> CM["Component / Assembly Manifest"]
CM --> META["file / comClass / typelib"]
META --> DLL["VendorControl.dll / .ocx"]
APP --> ACTX["Activation Context"]
ACTX --> COM["CLSIDFromProgID / CoCreateInstance"]
COM --> DLL
在普通的 COM 中,调用 CoCreateInstance 时会查找注册表来决定 加载哪个 DLL。
而在 Reg-Free COM 中,在此之前会先查看 当前有效的激活上下文,并根据其中记录的清单信息来解析。
因此,在同一台机器上,应用 A 和应用 B 就更容易各自持有 同一系列 COM 组件的不同版本 并同时运行。 这相当于把 COM 原本共享的文化,稍微拉回到应用本地化的方向。
4. 为什么普通的 COM 分发容易变得沉重
普通 COM 分发之所以沉重,并不是因为 COM 本身有问题,而是因为存在 全局注册这一前提。
要使用一个 COM 类,大致需要以下信息:
| 信息 | 作用 |
|---|---|
| CLSID | 唯一标识类的 GUID |
| ProgID | 便于人识别的名称 |
| InprocServer32 | 加载哪个 DLL |
| ThreadingModel | Apartment / Both 等前提 |
| TypeLib | 类型信息 |
一旦这些信息进入注册表,从整机层面来看确实方便,因为容易被多个应用共享。
但在实务中,这种共享经常会带来反效果:
- 某个产品的安装程序覆盖了另一个产品的 COM 注册
- 卸载程序「以为只删除了自己的东西」,结果破坏了共享的 COM
- 开发机上恰好存在某个注册项,而生产机上没有
- 32 位和 64 位的注册对不上,问题现象诡异地偏移
也就是说,比起 COM 本体,分发模型往往才是更让人头疼的部分。 Reg-Free COM 正是为了减轻这种分发模型带来的痛苦而设计的机制。
5. Reg-Free COM 的工作原理
5.1 在应用程序清单中记录依赖关系
首先,应用一侧需要把 自己依赖哪些 side-by-side assembly 写入应用程序清单(application manifest)。
这份清单可以:
- 以
MyApp.exe.manifest的形式放在 EXE 旁边 - 作为资源嵌入到 EXE 中
两种方式都可以。 在实务中,如果希望方便查看和替换分发内容,通常采用外部文件;如果更看重不易损坏和分发的简单性,则倾向于嵌入方式。
需要注意的是,如果外部文件版和嵌入版同时存在,文件系统上的清单会被优先使用。
5.2 在组件清单中记录 COM 信息
接下来,COM 一侧需要把 原本保存在注册表中的信息 保存到组件清单(component manifest)中。
其中通常包含如下信息:
comClassclsidprogidthreadingModeltypelib- 需要的话还有 proxy / stub 或 window class 等
也就是说,这相当于用 XML 来描述 COM 的「身份信息」,取代了注册表的作用。
这份清单可以:
- 作为独立文件与 DLL 放在一起
- 作为资源嵌入到 DLL 中
两种方式都可行。
在实务中,把清单作为 private assembly 嵌入到 DLL 中 往往更不容易出事故。以独立文件方式管理虽然直观,但容易在文件名与 assemblyIdentity 的对应关系、放置位置、复制遗漏等环节上出问题。
5.3 运行时会先查看激活上下文
Reg-Free COM 的关键就在这里。
当应用调用 CLSIDFromProgID 或 CoCreateInstance 时,COM 运行时会查看 当前处于激活状态的上下文(activation context)。
如果其中已经具备所需的 ProgID → CLSID、CLSID → DLL 信息,就可以在不使用注册表的情况下完成解析。
反过来,如果清单中缺少必要信息,就会回退到常规的基于注册表的解析方式。 这种行为会导致一个陷阱——在开发机上恰好能跑。原本以为已经实现了 Reg-Free,实际上却是被本地注册信息「救场」了。
这正是 Reg-Free COM 最令人头疼的陷阱。
6. 好处是什么
Reg-Free COM 的优点在实务中相当明确。
6.1 便于 XCOPY 部署
可以把所需文件一并放入应用文件夹,使安装程序和注册流程变得更轻量。
当然,如果要写入 Program Files 目录,权限问题依然存在,但至少 为 COM 注册所做的管理员操作 是可以减少的。
6.2 更容易减少版本冲突
即使同一台机器上存在多个版本的 COM 组件,也更容易让每个应用分别使用自己所需的版本。 可以在相当程度上避免「因为其他产品的安装而导致行为突然改变」这类事故。
6.3 往往不需要大幅改动现有代码
Reg-Free COM 改变的是 解析方式,而不是从根本上改变现有代码的调用方式。
因此,如果条件合适,往往可以在几乎不修改 CoCreateInstance 相关代码的情况下引入。
6.4 卸载和回滚更轻松
由于是按应用程序单位封闭管理的,更新和回滚会变得相当直接。 极端来说,甚至可以采用 整体替换文件夹 的思路。
7. 适合的场景与需要谨慎对待的场景
7.1 适合的场景
在以下情况下,Reg-Free COM 相当有优势:
| 情况 | 适配程度 |
|---|---|
| 希望随应用一起分发应用专用的 COM DLL / OCX | 非常适合 |
| 希望在同一台 PC 上共存多个版本 | 非常适合 |
| 希望避免供应商组件带来的注册事故 | 适合 |
| 希望在现有桌面应用中私有地使用 ActiveX / OCX | 适合 |
| 希望在不大幅改动现有调用方式的前提下简化分发 | 适合 |
典型的适用场景包括 业务桌面应用、设备联动工具,以及 VB6 / MFC / WinForms 的既有资产。
7.2 不适合,或需要谨慎评估的场景
另一方面,也有一些需要谨慎评估的情况。
| 情况 | 说明 |
|---|---|
| 希望在整台机器范围内共享 COM | Reg-Free 的优势不明显 |
| 位数(bitness)本身不匹配 | Reg-Free 无法解决这个问题 |
| 强烈依赖非标准的注册信息或独有的安装流程 | 难以转化为清单形式 |
| 依赖 DLL 或 VC++ 运行库的分发尚未理清 | 最终会在别处出问题 |
| 设计时工具或 IDE 的引用设置以注册表为前提 | 需要另外设计运维方式 |
最后一点尤其重要。 Reg-Free COM 能帮助解决 运行时的激活,但并不会一次性改变 设计时引用设置界面所依赖的前提条件。
8. 常见误解
8.1 用了 Reg-Free COM,位数问题就会消失
不会消失。 32 位进程仍然只能加载 32 位的 in-proc COM DLL,64 位进程也只能加载 64 位 DLL。 这一点在 Reg-Free 下和以往完全一样。
8.2 用了 Reg-Free COM 就完全不会查看注册表
这也不对。 如果清单中缺少必要信息,就会回退到常规的基于注册表的解析方式。 因此,在开发机上运行成功 并不等于 Reg-Free 配置正确。
8.3 用了 Reg-Free COM,类型库相关的问题也会自动解决
这一点只对了一半。
清单中确实可以写入 typelib 信息,但 VBA 的引用设置、C++ 的 #import、.NET 侧的设计时引用生成 等类型信息的处理,通常仍需要单独设计。
Reg-Free COM 首先解决的是 让程序能够启动 这件事。 如何以类型化方式进行开发,是接下来的另一个话题。
8.4 用了 Reg-Free COM,任何 ActiveX / OCX 都能直接搞定
这一点也很危险。 如果组件遵循标准的 COM 注册信息,处理起来会比较顺利;但如果组件严重依赖自定义的注册表设置、额外的安装步骤、许可证处理,或对其他模块的依赖很深,Reg-Free 化就会突然变得困难。
8.5 Reg-Free COM 在 .NET Framework 和 .NET 8 上基本一样
两者确实有相似之处,但工具链相当不同。
.NET Framework + RegAsm 的场景,和 .NET 5+ / .NET 8 + comhost 的场景,虽然同样是 COM,但所处的基础环境是不同的。
9. 原生 / .NET Framework / .NET 5+ / .NET 8 的差异
这部分容易混淆,这里单独梳理一下。
| 系列 | 大致说明 |
|---|---|
| 原生 COM DLL / OCX | 基本思路是应用程序清单 + 组件清单 |
| 基于 .NET Framework 的 COM 互操作 | 除了 Win32 风格的应用程序清单,还需要托管组件一侧的清单 |
| .NET 5+ / .NET 8 的 COM 公开 | 通过 EnableComHosting 生成 COM host,再通过 EnableRegFreeCom 生成 Reg-Free 用清单 |
9.1 基于 .NET Framework 的 COM
在基于 .NET Framework 的 COM 中,会形成两层结构:COM 应用一侧的 Win32 风格 application manifest,以及 托管组件一侧的 component manifest。
这部分比原生 COM 稍微复杂一些。 一旦「Reg-Free COM 本身理解了,但一旦涉及托管组件,又多出一份清单」这种情况出现,讨论就会再次变得棘手一点。
9.2 .NET 5+ / .NET 8 上的 COM 公开
在 .NET 5+ / .NET 8 中,COM 公开的入口是 *.comhost.dll。
再加上 EnableRegFreeCom=true,就会输出 用于 Reg-Free COM 的 side-by-side manifest。
不过,这里同样重要的一点是,Reg-Free COM 和 TLB 策略是两个独立的话题。 在 .NET Core / .NET 5+ 中,并不像 .NET Framework 时代那样存在「TLB 会自然地从程序集生成」这种情况。如果需要类型化的使用方式,最好单独整理 TLB 的生成、嵌入与注册相关事宜。
10. 最小配置示例
下面展示一个最小示例:MyApp.exe 通过 Reg-Free COM 使用 Vendor.CameraControl.dll。
10.1 文件结构示例
MyApp.exe
MyApp.exe.manifest
Vendor.CameraControl.dll
Vendor.Helper.dll
上面的例子中,假设 组件清单是嵌入在 DLL 一侧的。 也可以采用独立文件的方式,但一开始使用嵌入方式会更容易整理清楚。
10.2 应用程序清单示例
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
type="win32"
name="KomuraSoft.MyApp"
version="1.0.0.0"
processorArchitecture="amd64" />
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Vendor.CameraControl.Asm"
version="1.0.0.0"
processorArchitecture="amd64" />
</dependentAssembly>
</dependency>
</assembly>
10.3 组件清单示例
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
type="win32"
name="Vendor.CameraControl.Asm"
version="1.0.0.0"
processorArchitecture="amd64" />
<file name="Vendor.CameraControl.dll">
<comClass
clsid="{01234567-89AB-CDEF-0123-456789ABCDEF}"
progid="Vendor.CameraControl.1"
threadingModel="Apartment"
tlbid="{89ABCDEF-0123-4567-89AB-CDEF01234567}" />
<typelib
tlbid="{89ABCDEF-0123-4567-89AB-CDEF01234567}"
version="1.0"
helpdir="" />
</file>
</assembly>
在这个例子中,真正重要的不是 XML 的细节,而是 应用一侧的 dependentAssembly 必须与组件一侧的 assemblyIdentity 一致。
一旦这里出现偏差,问题往往会以相当「安静」的方式出现,让人很头疼。
另外,上面的 GUID 和名称只是用于说明的示例。实际使用时,需要根据组件所公开的 CLSID / TLBID / ProgID / threading model 正确填写。
11. 容易踩坑的地方
11.1 开发机上能跑,到部署环境却跑不起来
首先应该怀疑的是,其实是被注册表中的注册信息「顺手」救场了。 验证 Reg-Free COM 时,最好尽量在 干净环境 中进行。
11.2 出现「side-by-side configuration is incorrect」而无法启动
这类问题通常是由清单不一致、依赖 DLL 缺失、VC++ 运行库缺失、架构不匹配等原因造成的。
仅凭表面的错误信息往往不够直观,常见做法是结合 事件日志(Event Log) 和 sxstrace 来追踪。
11.3 组件清单与应用程序清单的对应关系出现偏差
- name 不一致
- version 不一致
- processorArchitecture 不一致
- 以为已经复制过的清单其实是旧版本
这些差异在外观上看起来很小,但在启动时的影响却相当大。
11.4 忘记放置依赖 DLL
如果只关注 Vendor.CameraControl.dll,而忽略了它后续会加载的 Vendor.Helper.dll、VC++ 运行库、proxy / stub DLL,就会漏掉这部分依赖。
Reg-Free COM 能减少 COM 注册方面的问题,但并不会消除 原生依赖解析方面的问题。
11.5 把类型库或引用设置的问题往后拖
即使运行时的激活已经打通,一旦出现以下需求:
- 想在 VBA 中进行早期绑定
- 想在 C++ 中使用
#import - 想在 .NET 一侧的设计时生成 interop
就仍然需要考虑类型信息的分发方式。 Reg-Free COM 并不会自动把这部分全部处理好,因此把 运行时(runtime)与设计时(design-time)分开考虑 非常重要。
12. 总结
如果用一句话概括 Reg-Free COM,那就是:把 COM 的注册信息从整机范围收拢到应用程序单位的机制。
由此带来的好处包括:
- 更容易把 COM DLL / OCX 限定在应用本地
- 更容易减少版本冲突
- 更容易简化分发与回滚
另一方面,以下问题依然重要:
- 32 位 / 64 位
- 依赖 DLL
- TLB / 引用设置
- 非标准的注册依赖
- 在干净环境中进行验证
因此,在引入 Reg-Free COM 时,基本姿态应该是:
- 把它理解为「激活(activation)」层面的问题
- 把运行时与设计时的议题分开考虑
- 在干净环境中进行确认
- 优先理清位数与依赖 DLL 的问题
按这个顺序来看,就能大幅减少踩坑的概率。
13. 相关文章
- 什么是 COM / ActiveX / OCX——差异与关系的整理与解说
- 现在该如何处理 ActiveX / OCX——保留、封装、替换的判断表
- 如何从 VBA 以类型化方式使用 .NET 8 的 DLL——通过 COM 公开 + dscom 生成 TLB
14. 参考资料
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
Media Foundation 入门:用 COM 视角理解 API
本文按照最先需要掌握的顺序,结合 COM、HRESULT、IMFSourceReader、MFT 等 Windows 媒体 API 的基础术语,整理 Media Foundation 到底是什么。
用 PerfView 与 dotnet-trace 定位“变慢”的原因 ── .NET 性能排查实务入门
当业务应用出现“变慢”“CPU 占满”“偶尔卡死”时,该用哪个工具看什么?本文梳理 PerfView 与 dotnet-trace 的分工、CPU 采样的解读方法(inclusive/exclusive)、用 ThreadTime 排查阻塞时间,以及与 EventSourc...
Windows 事件日志・ETW 入门 ── 让业务应用程序的日志接入操作系统标准机制
Windows 业务应用程序的日志,是不是只靠文件日志来解决?事件日志与 ETW,是运维人员和操作系统标准工具所能看到的另一层记录。本文整理三种手段的分工方式、.NET 中的写入方法、通过 EventSource 进行的 ETW 埋点、收集与排查的实务做法,以及来源未注册、...
在 C# 中安全调用 Win32 API —— P/Invoke 实务指南(DllImport / LibraryImport / CsWin32)
本文整理在 C# 中通过 P/Invoke 调用 Win32 API 或原生 DLL 时的实务要点:DllImport 与 LibraryImport 的区别、用 CsWin32 自动生成签名、字符串封送的陷阱、用 SafeHandle 管理句柄、SetLastError ...
C# 操作 Excel 时 EXCEL.EXE 残留的问题 ── COM 引用释放模式与替换判断
本文从 COM 引用计数与 RCW 的运行原理,整理 C# 通过 Microsoft.Office.Interop.Excel 操作 Excel 时 EXCEL.EXE 进程残留的问题。内容涵盖「两个点规则」的陷阱、Marshal.ReleaseComObject 派与 G...
常见问题
汇总了咨询这一主题时常见的问题。
- 什么是 Reg-Free COM?
- Reg-Free COM 是 Registration-Free COM 的简称,指的是把 COM 的注册信息保存在清单(manifest)中而不是注册表里的机制。运行时,CoCreateInstance 或 CLSIDFromProgID 在解析时会先查看激活上下文(activation context),再根据其中记录的清单信息来解析 DLL。这样就可以让 COM DLL / OCX 以应用程序为单位私有化,带来易于 XCOPY 部署、更容易避免版本冲突、卸载时不易破坏其他程序这几个优点。
- 改用 Reg-Free COM 之后,32 位/64 位的问题也能解决吗?
- 解决不了。32 位进程仍然只能加载 32 位的 in-proc COM DLL,64 位进程也只能加载 64 位 DLL,这一点在 Reg-Free 下和以前完全一样。此外,依赖 DLL、VC++ 运行库的分发、类型库、设计时引用设置,以及对非标准注册信息的依赖,都需要另行考虑。Reg-Free COM 主要消除的只是全局注册所带来的麻烦。
- 为什么 Reg-Free COM 配置在开发机上能跑,到部署环境却跑不起来?
- 首先应该怀疑的是,其实之前是被注册表中的注册信息「顺手」救场了。如果清单中缺少必要信息,COM 运行时会回退到常规的基于注册表的解析方式,因此在开发机上可能恰好因为本地注册而正常运行。所以验证 Reg-Free COM 配置时,最好在干净环境中进行。如果出现「side-by-side configuration is incorrect」而无法启动,通常是清单不一致或依赖 DLL 缺失等原因造成的,常见的排查方式是查看事件日志并使用 sxstrace 追踪。
- 用 .NET 8 编写的 COM 组件也可以做成 Reg-Free COM 吗?
- 可以。在 .NET 5+/.NET 8 中,通过 EnableComHosting 可以生成作为 COM 公开入口的 *.comhost.dll,再加上 EnableRegFreeCom=true,就会输出用于 Reg-Free COM 的 side-by-side manifest。不过,Reg-Free COM 和 TLB 策略是两个独立的话题。在 .NET Core/.NET 5+ 中,不像 .NET Framework 时代那样能自然地从程序集生成 TLB,所以如果需要类型化的使用方式(例如 VBA 的早期绑定),最好单独整理 TLB 的生成、嵌入与注册。
作者简介
本文作者的个人简介页面。
Go Komura
小村软件有限公司 代表
以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。