什么是 Reg-Free COM——无需注册使用 COM 的机制

· 更新日期: · · COM, Reg-Free COM, Registration-Free COM, Windows开发, 老旧技术

更新记录(2 条,最后更新 2026年09月03日)

本文的修改记录。已保存的更新前版本,可通过带有 DOI 的永久链接阅读。

本文此前是日文原文的节译,缺少大量章节、表格、Mermaid 图、图题、脚注与 FAQ。现已改写为日文原文的完整译文,技术主张与日文版一致,并补上了此前缺失的图与表,同时统一了全篇的术语译法。 查看更新前的版本 (DOI: 10.5281/zenodo.22276852)
补充了日文原文中已有的咨询引导(consultation_services)。正文内容没有改动。 查看更新前的版本 (DOI: 10.5281/zenodo.21615495)
首次发布
引用本文(DOI: 10.5281/zenodo.21615494)

本文保存于 Zenodo。以下同时提供始终指向最新版本的 DOI,以及固定于您正在阅读版本的 DOI。

小村 豪(2026)。《什么是 Reg-Free COM——无需注册使用 COM 的机制》。小村软件有限公司。https://doi.org/10.5281/zenodo.21615494 https://comcomponent.com/zh-CN/blog/2026/03/16/011-what-is-reg-free-com/

DOI(最新版本)
10.5281/zenodo.21615494
DOI(此版本)
10.5281/zenodo.22282015

在 COM / ActiveX / OCX 的项目中,每次分发和更新都会遇到同样的问题。

  • 需要 regsvr32
  • 往往还要管理员权限
  • 与其他应用装入的别的版本相互冲突
  • 卸载之后连带影响到别的产品
  • 开发机上能跑,到干净环境却跑不起来

能把这些麻烦大幅减少的,就是 Reg-Free COM。 不过,它并不像名字听上去那样,是一种“COM 的麻烦全部消失”的魔法。消失的主要是 被全局注册拖累的那部分麻烦,位数(bitness)、依赖 DLL、类型库、线程模型上的难点并不会随之消失。

Reg-Free COM 消除的部分与遗留的部分说明 Reg-Free COM 消除的主要是被全局注册拖累的麻烦,而位数、依赖 DLL、类型库、线程模型上的难点并不会消失。Reg-Free COM全局注册的麻烦会减少也有遗留下来的难点位数 / 依赖DLL / 类型库

图1:它不是魔法,要把会消失的麻烦和会留下的麻烦分开来期待。

本文围绕 在 Windows 桌面应用中把 COM DLL / OCX 封闭在应用本地使用 这一场景来梳理 Reg-Free COM。

目标读者与前提

本文写给 正在分发使用现有 COM DLL / OCX 的 Windows 桌面应用、并希望摆脱 regsvr32 与管理员权限的开发者。前提是你至少接触过一次 COM 的基础(CLSID、ProgID、CoCreateInstance、in-proc 服务器)。如果这部分还比较模糊,先读“什么是 COM / ActiveX / OCX——差异与关系的整理与解说”会更快。

操作步骤部分会用到 Windows SDK 的 mt.exe 和 sxstrace,因此假定环境中已安装 Visual Studio 或 Windows SDK。

1. 先说结论(一句话)

先给一个略显粗糙但管用的说法。

  • Reg-Free COM 是把 COM 的注册信息保存在清单而不是注册表里的做法
  • 运行时,CoCreateInstance 或 CLSIDFromProgID 的解析会先查看 激活上下文(activation context)
  • 因此,COM DLL / OCX 可以 按应用程序私有持有
  • 主要优点是 便于 XCOPY 分发、更容易避免版本冲突、卸载时不易造成破坏
  • 但是,32 位 / 64 位的问题不会消失。这一点靠清单怎么写是绕不过去的
  • 另外,依赖 DLL、类型库、设计时引用、对非标准注册信息的依赖 都需要另行考虑
  • 在实务中,当你 想把应用专用的 COM 组件放在应用旁边 时,它相当合适

概括地说,Reg-Free COM 是一种 把 COM 的激活拉回到应用程序单位的机制。

注册信息的存放位置发生了变化说明 Reg-Free COM 把 COM 的注册信息保存在清单而不是注册表里,解析 CoCreateInstance 等调用时会先查看激活上下文,因此可以按应用程序私有持有 COM 组件。注册信息保存在清单里解析时先查看激活上下文COM 组件可按应用程序私有持有便于 XCOPY 分发与避免冲突

图2:从注册表转到清单,解析的入口变了,这才是本质。

图中实线表示始终成立的关系,虚线表示带条件的关系(成立条件写在详情页各关系的说明中)。关系的完整列表(共 26 条,附依据与确信度)以及主要概念的定义,汇总在知识地图详情页(日文)。数据:JSON-LD / Turtle

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 进行公开

反过来说,本文想强调的有两点。

  1. Reg-Free COM 讲的是“激活”这件事
  2. 类型信息的分发和设计时的引用设置,可能作为另一个议题保留下来

把这两点混在一起,讨论就会相当浑浊。

不该混为一谈的两个议题说明 Reg-Free COM 讲的是激活这件事,而类型信息的分发与设计时的引用设置会作为另一个议题保留下来,把两者混在一起会让讨论变得浑浊。Reg-Free COM讲的是激活这件事类型信息分发、设计时引用作为另一个议题保留混在一起讨论会变浑浊

图3:运行时的话题和设计时的话题,从一开始就分开处理。

3. 先用一张图整理

进入图之前,先把本文反复出现的 4 个词定义好。

术语 含义
激活上下文(activation context) 保存“当前这个线程使用哪个 assembly 的哪个版本”的运行时数据结构。CoCreateInstance 会先查看它,然后才轮到注册表
side-by-side assembly 让同名组件的不同版本在同一台机器上共存的 Windows 机制。它由清单来标识,assemblyIdentity 相当于它的名牌
应用程序清单 附在 EXE 一侧的 XML,用来写明“自己依赖哪些 side-by-side assembly”
组件清单(assembly manifest) 附在组件一侧的 XML,用来写明“这个 assembly 包含哪些文件、公开哪些 COM 类”。本来放在注册表里的信息转到了这里

激活上下文是按线程持有的。COM 会把创建方线程的激活上下文交给宿主线程,然后才调用 LoadLibrary 和 DllGetClassObject,所以调用方不需要做特别的准备工作。

在此基础上,用一张图看整体会更快。

MyApp.exeApplication ManifestdependentAssemblyComponent / Assembly Manifestfile / comClass / typelibVendorControl.dll / .ocxActivation ContextCLSIDFromProgID / CoCreateInstance

图4:从应用的清单顺着找到组件的清单,不经注册表就抵达 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 就是为减轻这种分发模型之苦而存在的机制。

全局共享适得其反的过程说明 COM 的注册信息进入注册表后整机共享虽然方便,但在实务中容易表现为被其他产品覆盖、卸载时连带破坏、开发机与生产机的环境差异,比起 COM 本体更让人头疼的是分发模型。注册信息在整机范围共享多个应用都能用,很方便实务中共享会适得其反覆盖 / 连带破坏 / 环境差异分发模型让人头疼

图5:痛点的真身不是 COM 本身,而是全局注册这个前提。

5. Reg-Free COM 的工作原理

5.1 在应用程序清单中写明依赖关系

首先,应用一侧要把 自己依赖哪些 side-by-side assembly 写进应用程序清单。

这份清单可以

  • 像 MyApp.exe.manifest 那样放在 EXE 旁边
  • 作为资源嵌入 EXE

两种方式都能处理。 在实务中,如果希望分发和替换看得清楚就用外部文件,如果更看重不易损坏和分发的简单就用嵌入,这样区分使用的情况比较多。

另外,当外部文件版和嵌入版同时存在时,文件系统上的清单优先。

5.2 在组件清单中写明 COM 信息

接下来,COM 一侧要把 本来放在注册表里的信息 交给组件清单来持有。

这里会写入的,比如是下面这些信息。

  • comClass
  • clsid
  • progid
  • threadingModel
  • typelib
  • 需要的话还有 proxy / stub、window class 等

也就是说,这相当于用 XML 来描述 COM 的面貌,取代注册表。

这份清单可以

  • 作为独立文件与 DLL 分开放置
  • 作为资源嵌入 DLL

两种方式都可以配置。

在实务中,作为 private assembly 嵌入 DLL 往往更不容易出问题。独立文件方式虽然直观,但在文件名与 assemblyIdentity 的对应、放置位置、漏拷贝这些地方容易绊倒。

组件清单的持有方式说明组件清单用 XML 持有本来放在注册表里的 comClass、clsid、typelib 等信息,可以选择与 DLL 分开放置或作为资源嵌入 DLL,实务中嵌入方式更不容易出问题。把注册表里的信息用XML持有作为独立文件放置嵌入DLL容易因漏拷贝等绊倒实务中往往不容易出问题

图6:信息内容相同,但存放方式的选择会左右出问题的概率。

5.3 运行时会先查看激活上下文

Reg-Free COM 的要害就在这里。

当应用调用 CLSIDFromProgID 或 CoCreateInstance 时,COM 运行时会查看 当前处于活动状态的激活上下文。 如果其中已有所需的 ProgID → CLSID、CLSID → DLL 信息,就能不使用注册表完成解析。

反过来,如果清单中缺少必要信息,就会回退到常规的基于注册的解析。 正是这种行为,制造了 在开发机上恰好能跑 的陷阱。以为已经做成 Reg-Free 了,实际上却一直被本地注册救着场。

这是 Reg-Free COM 最难缠的陷阱。

回退到基于注册的解析这个陷阱说明解析 CoCreateInstance 时会先查看激活上下文,但如果清单中缺少必要信息就会回退到常规的基于注册的解析,因而产生开发机上靠本地注册恰好能跑的陷阱。够不够解析时先查看激活上下文清单里的信息够不够不使用注册表完成解析回退到基于注册的解析开发机上恰好能跑的陷阱

图7:这种悄无声息的回退,是该机制最大的陷阱。

6. 好处是什么

Reg-Free COM 的优点,在实务中相当明确。

6.1 便于 XCOPY 分发

可以把所需文件一并放进应用文件夹,安装程序和注册处理都会变轻。 当然,如果要写入 Program Files 目录,权限是另一回事,但至少 为 COM 注册而做的管理员操作 是容易减少的。

6.2 更容易减少版本冲突

即使同一台机器上存在多个版本的 COM 组件,也更容易让每个应用分别使用自己那一版。 因为其他产品的安装程序,行为突然变了 这类问题就容易避免得多。

6.3 多数情况下不必大改现有代码

Reg-Free COM 改变的是 解析的方式,而不是从根本上改变现有代码的调用方式。 因此只要用对了场合,往往几乎不用动 CoCreateInstance 一侧的代码就能引入。

6.4 删除和回滚更轻松

因为是按应用程序单位封闭的,更新和回滚都会变得相当自然。 说得极端一点,整个文件夹替换掉 这种思路会更容易采用。

按应用程序封闭带来的好处说明把 COM 组件按应用程序封闭起来,会带来便于 XCOPY 分发、容易避免版本冲突、几乎不改现有代码即可引入、删除与回滚简单这几个好处。按应用程序封闭起来便于XCOPY分发能减少版本冲突可以整个文件夹替换调用代码几乎不用动

图8:这些好处都从“封闭起来”这一个性质里长出来。

7. 适合的场景与不适合的场景

7.1 适合的场景

在这些情况下,Reg-Free COM 相当有力。

情况 契合度
想把应用专用的 COM DLL / OCX 随应用一起分发 非常好
想在同一台 PC 上共存多个版本 非常好
想避免供应商组件带来的注册问题 好
想在现有桌面应用中私有地使用 ActiveX / OCX 好
想让分发变轻,同时不大改现有调用方式 好

典型来说,它与 业务桌面应用、设备联动工具、VB6 / MFC / WinForms 的既有资产 契合度很好。

7.2 不适合,或需要谨慎评估的场景

另一方面,也有一些需要谨慎评估的情况。

情况 说明
想在整台机器范围内共享 COM Reg-Free 的好处很淡
位数本来就对不上 Reg-Free 解决不了
强烈依赖非标准的注册信息或自有的安装流程 难以改写成清单
依赖 DLL 或 VC++ 运行库的分发还没理清 最终会在别处栽跟头
设计时工具或 IDE 的引用设置以注册表为前提 需要另外设计运维方式

最后一点尤其重要。 Reg-Free COM 能帮上 运行时的激活,但 设计时的引用设置界面以什么为前提 并不会因此一次性改变。

运行时得救而设计时仍在说明 Reg-Free COM 能帮上运行时的激活,但设计时的工具或 IDE 的引用设置如果以注册表为前提,就不会因此改变,需要另外设计运维方式。运行时的激活Reg-Free 能帮上忙设计时的引用设置界面有时以注册表为前提需要另外设计运维方式

图9:跑起来的机制齐备了,开发用的机制还要另外备齐。

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 首先讲的是 让程序能启动 这件事。 如何以类型化的方式开发,是下一个议题。

启动的话题与类型的话题之间的先后说明清单里虽然也能写 typelib 信息,但 VBA 的引用设置、C++ 的 import、.NET 的设计时引用生成等类型信息处理需要另行设计,Reg-Free COM 首先讲的是让程序能启动,类型化开发是下一个议题。配置 Reg-Free COM先让程序能启动再考虑如何以类型化方式开发VBA引用设置 / import / interop生成

图10:能写 typelib,和类型信息的运维已经办妥,是两回事。

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 用的 manifest

9.1 基于 .NET Framework 的 COM

在基于 .NET Framework 的 COM 中,会形成两层结构:COM 应用一侧的 Win32 风格 application manifest,以及 托管组件一侧的 component manifest。

也就是说,比原生 COM 时多出一份 manifest。10.4 关于名称和资源 ID 的约束,对这里的 component manifest 同样生效。

9.2 在 .NET 5+ / .NET 8 上公开 COM

在 .NET 5+ / .NET 8 中,COM 公开的入口变成 *.comhost.dll。 再加上 EnableRegFreeCom=true,就会输出 用于 Reg-Free COM 的 side-by-side manifest。

类型信息的处理是另一个议题,这一点如 8.3 所述,而在 .NET Core / .NET 5+ 上还会进一步变化。因为这里不再是 .NET Framework 时代那种 TLB 会自然地从程序集里冒出来 的世界,所以如果需要类型化使用,就得另行搭建 TLB 的生成、嵌入与注册。具体步骤整理在“如何从 VBA 以类型化方式使用 .NET 8 的 DLL——通过 COM 公开与 dscom 生成 TLB”。

.NET 5 以后的 Reg-Free COM说明在 .NET 5 以后用 EnableComHosting 生成作为 COM 公开入口的 comhost.dll,启用 EnableRegFreeCom 后会输出用于 Reg-Free COM 的 side-by-side 清单,但 TLB 的生成与注册需要另行搭建。用EnableComHosting构建comhost.dll成为入口启用EnableRegFreeCom输出Reg-Free用的清单TLB的处理另行搭建

图11:在 .NET 8 上两个属性就能搭好台子,类型信息仍是另一份工作。

10. 最小配置示例

这里给出一个最小示例:MyApp.exe 通过 Reg-Free COM 使用 Vendor.CameraControl.dll。

10.1 文件结构示例

MyApp.exe
MyApp.exe.manifest
Vendor.CameraControl.Asm.manifest
Vendor.CameraControl.dll
Vendor.Helper.dll

上面的例子里,假定 组件清单是以独立文件放置的。 也可以做成嵌入 DLL 的形式,但那样一来,assembly 的名称和资源 ID 就会带上固定的约束。连同步骤一起放在 10.4 讲。

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 正确填写。

两份清单的一致要求说明应用一侧的 dependentAssembly 与组件一侧的 assemblyIdentity 在名称和版本上必须一致,一旦对不上就会变成只看错误外观判断不出原因的启动失败。一致对不上应用侧的dependentAssembly名称和版本是否一致组件侧的assemblyIdentity解析得以成立看不出原因的启动失败

图12:比起 XML 的细节,两边的名牌是否一致才决定生死。

10.4 清单放在哪里、怎么嵌入

这是操作步骤中最容易卡住的地方。是作为独立文件放置,还是嵌入二进制,会改变可以取什么名字。

side-by-side 会以下面的顺序,相对于应用文件夹去查找 private assembly。

  1. WinSxS 文件夹
  2. <appdir>\<assemblyname>.DLL
  3. <appdir>\<assemblyname>.manifest
  4. <appdir>\<assemblyname>\<assemblyname>.DLL
  5. <appdir>\<assemblyname>\<assemblyname>.manifest

如果先找到了与 assembly 同名的 DLL,查找就到此为止。 由此可知,只有下面两种组合能成立。

放置方式 assembly 名 文件 嵌入的资源 ID
独立文件 取一个与 DLL 名 不同 的名字。例:Vendor.CameraControl.Asm 把 Vendor.CameraControl.Asm.manifest 放在 DLL 旁边 不嵌入
嵌入 DLL 与 DLL 名 相同 也可以。例:Vendor.CameraControl 只有 Vendor.CameraControl.dll 1

也就是说,如果像 10.2 和 10.3 的例子那样使用 Vendor.CameraControl.Asm 这个名字,那就是 独立文件 的做法。若要改成嵌入,就把 assemblyIdentity 的 name 改成 Vendor.CameraControl,并把 dependentAssembly 一侧也统一成同样的名字。

查找中止的机制说明 side-by-side 会按 WinSxS 到应用文件夹的顺序查找 private assembly,如果先找到与 assembly 同名的 DLL 查找就会中止,因此独立文件方式必须让 assembly 名与 DLL 名不同。找到了没找到开始查找private assembly查找与assembly同名的DLL查找到此为止查找同名的manifest文件独立文件方式要把名字取得不同

图13:命名上的约束,来自查找会中途停止这个规格。

还有一个容易搞错的约束。组件清单不能放进 EXE 的资源里。 能放进 EXE 的是应用程序清单。

嵌入使用 Windows SDK 的 mt.exe(清单工具)。请从 Visual Studio 的开发者命令提示符中执行。

rem 1. 嵌入之前,先通过语法检查
mt.exe -manifest MyApp.exe.manifest -validate_manifest
mt.exe -manifest Vendor.CameraControl.manifest -validate_manifest

rem 2. 把应用程序清单嵌入 EXE
mt.exe -manifest MyApp.exe.manifest -outputresource:MyApp.exe;#1

rem 3. 把组件清单嵌入 DLL(资源 ID 为 1)
mt.exe -manifest Vendor.CameraControl.manifest -outputresource:Vendor.CameraControl.dll;#1

rem 4. 取出来确认是否真的嵌入成功
mt.exe -inputresource:Vendor.CameraControl.dll;#1 -out:extracted.manifest

把第 4 条命令取出的 extracted.manifest 与原始 XML 对照,就能排除“以为嵌进去了其实没有”的情况。

用 mt.exe 嵌入的步骤说明使用 mt.exe 时先用 validate_manifest 通过语法检查,再把应用程序清单嵌入 EXE、把组件清单以资源 ID 1 嵌入 DLL,最后取出来与原始 XML 对照确认的流程。通过语法检查把应用侧嵌入EXE把组件侧嵌入DLL〔ID 1〕取出来与原始XML对照

图14:嵌入要连同确认一起,当作一个步骤来跑完。

mt.exe 有两点需要注意。

  • 清单所引用的文件,必须与清单放在同一个目录下。 如果写了 <file name="Vendor.CameraControl.dll">,就要先把那个 DLL 放到清单旁边再执行。构建输出与清单的管理位置分开时,会在这里卡住
  • -outputresource 省略资源 ID 时会使用 CREATEPROCESS_MANIFEST_RESOURCE(= 1)。为了不因为无意中变成 1 而出问题,明确写上 ;#1 读起来也更清楚

如果只是替换已经嵌入好的内容,可以用 -updateresource:<文件>;#1。它等同于给 -inputresource 和 -outputresource 传入相同的参数。

10.5 包含干净环境在内的确认步骤

对 Reg-Free COM 来说,“在开发机上跑通了”几乎没有意义。因为始终存在只是被本地注册表注册救了场的可能。按下面的顺序确认。

  1. 构建,并把分发物整套集中到一个文件夹里。 包括 EXE、清单、COM DLL、依赖 DLL、VC++ 运行库、proxy / stub DLL
  2. 准备验证环境。 理想情况是目标 COM 从未被注册过的环境。使用 Windows 沙盒 可以每次都从全新状态开始试
  3. 先在该环境中确认目标 COM 未被注册。 用下面的命令,如果报出找不到键的错误就说明未注册
  4. 复制文件夹,直接启动。 不执行安装程序,也不执行 regsvr32
  5. 确认能一直走到 COM 对象的创建。 只是启动起来,无法验证延迟创建的组件。要实际操作到会执行 CoCreateInstance 的界面或功能
  6. 失败的话,按 11.2 的步骤采集日志

步骤 3 中使用的命令是这些。

reg query "HKCR\CLSID\{01234567-89AB-CDEF-0123-456789ABCDEF}" /reg:64
reg query "HKCR\CLSID\{01234567-89AB-CDEF-0123-456789ABCDEF}" /reg:32
reg query "HKCR\Vendor.CameraControl.1"

32 位和 64 位的注册表视图是两回事,所以 务必查看与应用位数相符的一侧。/reg:64 和 /reg:32 两边都看一遍更保险。

想在开发机上试的时候,先用 regsvr32 /u Vendor.CameraControl.dll 解除注册,再做确认。不过在其他产品也使用同一个 COM 的环境里会产生影响,因此准备干净环境更稳妥。

在干净环境中确认的流程说明把分发物整套集中到一个文件夹,准备目标 COM 从未注册过的验证环境,先确认未注册后再复制文件夹直接启动,并操作到会执行 CoCreateInstance 的功能来确认的流程。失败时把分发物整套集中起来准备干净的验证环境先确认未被注册复制过去直接启动操作到会创建COM的功能采集日志来排查

图15:插入“确认未注册”这一步,就能把恰好能跑排除出验证。

11. 容易踩坑的地方

11.1 开发机上能跑,到分发目标却跑不起来

首先要怀疑的是 其实一直被注册表里的注册信息救着场 这种情况。 验证 Reg-Free COM 时,尽量在 干净环境 中做才稳妥。

11.2 出现“side-by-side configuration is incorrect”而无法启动

这一类问题会由 manifest 不一致、依赖 DLL 缺失、VC++ 运行库缺失、架构不符等原因引起。 只看表面的错误文字相当不友好,因此常规做法是用 事件日志 和 sxstrace 追查。

事件日志方面,打开事件查看器 > Windows 日志 > 应用程序,找来源为 SideBySide 的错误。是在解析哪个 assembly 时失败的,会显示在这里。

sxstrace 要一边复现失败一边采集。把命令提示符以管理员身份打开会更可靠。

rem 1. 开始跟踪。这个窗口保持打开
sxstrace trace -logfile:sxstrace.etl

rem 2. 在另一个窗口启动应用,复现失败

rem 3. 停止跟踪。在窗口 1 中按 Enter,或者从另一个窗口执行下面这条
sxstrace stoptrace

rem 4. 把原始的 .etl 转换成人能读的格式
sxstrace parse -logfile:sxstrace.etl -outfile:sxstrace.txt

如果不想出现停止提示,就在步骤 1 加上 -nostop。输出太长时,在步骤 4 加上 -filter:MyApp.exe,可以只筛出目标应用的部分。

转换后的 sxstrace.txt 会按顺序列出去找了哪些清单、在哪里没有匹配上。如果 10.4 的命名方式弄错了,看这里“去找的文件名”也能判断出来。

side-by-side 错误的排查方法说明出现 side-by-side configuration is incorrect 无法启动时,先在事件查看器中确认来源为 SideBySide 的错误,再用 sxstrace 开始跟踪并复现失败,停止后用 parse 转换成人能读的格式来追查不匹配之处的流程。在事件日志中确认SideBySide用sxstrace开始跟踪启动应用复现失败停止后用parse转换读出查找与不匹配之处

图16:不要在表面的错误文字上纠结,用日志和跟踪去追解析的路径。

11.3 component manifest 与 application manifest 的对应关系错位

  • name 不一样
  • version 不一样
  • processorArchitecture 不一样
  • 以为复制过去的 manifest 其实是旧的

这些差别看上去非常小,但在启动时的影响相当大。

11.4 忘记放依赖 DLL

如果只盯着 Vendor.CameraControl.dll 就心满意足,接下来会被加载的 Vendor.Helper.dll、VC++ 运行库、proxy / stub DLL 就会漏掉。 Reg-Free COM 减少的是 COM 注册方面的问题,并不会连 原生依赖解析方面的问题 也一并消除。

11.5 把类型库和引用设置的运维往后拖

即使只把运行时的 activation 打通了,一旦出现

  • 想从 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 时的基本姿态是这样。

  1. 认定这是 activation 层面的话题
  2. 把 runtime 和 design-time 的议题分开
  3. 在干净环境中确认
  4. 先把位数和依赖 DLL 理顺

按这个顺序来看,就能大幅减少踩坑。

引入时的基本姿态说明引入 Reg-Free COM 时,认定这是激活层面的话题、把运行时与设计时的议题分开、在干净环境中确认、先把位数与依赖 DLL 理顺,按这个顺序来看就不容易踩坑。认定是activation的话题分开runtime与design-time在干净环境中确认先理顺位数与依赖DLL

图17:按顺序守住这 4 条姿态,引入时的问题会少很多。

13. 相关文章

14. 参考资料

共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。

与本文相近的主题页面。以本文为起点,可以进一步了解相关服务和其他文章。

本文与以下服务页面相关联,欢迎从最接近的入口查看。

常见问题

汇总了咨询这一主题时常见的问题。

什么是 Reg-Free COM?
Reg-Free COM 是 Registration-Free COM 的简称,指把 COM 的注册信息保存在清单而不是注册表里的机制。运行时,CoCreateInstance 或 CLSIDFromProgID 在解析时会先查看激活上下文,再根据其中记录的清单信息解析出 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 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表