什麼是 Reg-Free COM - 免註冊使用 COM 的機制

· 更新日期: · · COM, Reg-Free COM, Registration-Free COM, Windows 開發, Legacy 技術

更新紀錄(2 筆,最後更新 2026年09月04日)

本文的修改紀錄。已保存的更新前版本,可透過附有 DOI 的永久連結閱讀。

已將繁體中文版改寫為日文原文的完整翻譯。先前的繁體中文版只譯出日文原文的一部分,遺漏了章節、表格、Mermaid 圖、圖說與 FAQ。本次依日文原文將這些內容全部補回,並新增本文的知識地圖章節。技術主張與日文版一致。 查看更新前的版本 (DOI: 10.5281/zenodo.22279435)
補上了日文原文中已有的諮詢引導(consultation_services)。內文沒有改動。 查看更新前的版本 (DOI: 10.5281/zenodo.21616313)
初次發布
引用本文(DOI: 10.5281/zenodo.21616312)

本文保存於 Zenodo。以下同時提供一律指向最新版本的 DOI,以及固定於您正在閱讀版本的 DOI。

Go Komura(2026)。〈什麼是 Reg-Free COM - 免註冊使用 COM 的機制〉。小村軟體有限公司。https://doi.org/10.5281/zenodo.21616312 https://comcomponent.com/zh-TW/blog/2026/03/16/011-what-is-reg-free-com/

DOI(最新版本)
10.5281/zenodo.21616312
DOI(此版本)
10.5281/zenodo.22297152

在 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 的註冊資訊放在資訊清單(manifest)而不是登錄檔裡的做法
  • 執行階段解析 CoCreateInstance 或 CLSIDFromProgID 時,會先查看 啟用內容(activation context)
  • 因此 COM DLL / OCX 可以依應用程式各自以 private 的方式持有
  • 主要優點是 容易做 XCOPY 散發、容易避開版本衝突、解除安裝比較不會出事
  • 不過 32bit / 64bit 的問題不會消失,這一點無法靠資訊清單的寫法繞過
  • 另外 相依 DLL、型別程式庫、設計階段的參考設定、對非標準註冊資訊的依賴 都必須另外考慮
  • 實務上,想把應用程式專用的 COM 元件放在自己旁邊 時特別合用

簡單說,Reg-Free COM 是 把 COM 的啟用拉回以應用程式為單位 的機制。

註冊資訊的存放位置改變了說明 Reg-Free COM 把 COM 的註冊資訊放在資訊清單而不是登錄檔,解析 CoCreateInstance 等呼叫時會先查看啟用內容,因此 COM 元件可以依應用程式各自以 private 的方式持有的圖。註冊資訊放在資訊清單裡解析時先看啟用內容COM 元件可依應用程式各自以 private 持有容易做 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. 先用一張圖收攏

進入圖之前,先把本文反覆出現的四個詞定義好。

用語 意義
啟用內容(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 的對應、放置位置、漏拷貝這些地方絆倒。

元件資訊清單的存放方式說明原本放在登錄檔裡的 comClass、clsid、typelib 等資訊改由元件資訊清單以 XML 保存,可以選擇和 DLL 分開放置或以資源嵌入 DLL,而實務上嵌入比較不會出事的圖。原本在登錄檔的資訊改用 XML 保存分開成獨立檔案放置嵌入 DLL容易因漏拷貝等絆倒實務上通常比較不會出事

圖 6: 資訊的內容一樣,但放置方式的選擇會左右出事的機率。

5.3 執行階段會先查看啟用內容

Reg-Free COM 的關鍵就在這裡。

當應用程式呼叫 CLSIDFromProgID 或 CoCreateInstance 時,COM 執行階段會查看 目前作用中的啟用內容。 只要那裡有必要的 ProgID → CLSID、CLSID → DLL 資訊,就能不經過登錄檔完成解析。

反過來說,如果資訊清單裡缺少必要資訊,就會退回一般以註冊為基礎的解析。 正因為這個行為,才會出現 開發機上碰巧跑得起來 的陷阱:以為已經改成 Reg-Free,實際上卻是被本機的註冊救了一命。

這是 Reg-Free COM 最惱人的陷阱。

退回註冊解析的陷阱說明解析 CoCreateInstance 時會先查看啟用內容,但資訊清單缺少必要資訊時會退回一般以註冊為基礎的解析,因而出現開發機上被本機註冊救著、碰巧跑得起來的陷阱的圖。夠不夠解析時先看啟用內容資訊清單的資訊夠不夠不經過登錄檔完成解析退回以註冊為基礎的解析開發機上碰巧跑得起來的陷阱

圖 7: 安靜退回的 fallback,是這個機制最大的陷阱。

6. 好處在哪裡

Reg-Free COM 的優點,在實務上相當明顯。

6.1 容易做 XCOPY 散發

必要的檔案可以全部放進應用程式資料夾,安裝程式與註冊處理都會變輕。 當然,如果要寫進 Program Files 底下,權限是另一回事,但至少 為了 COM 註冊而做的系統管理員作業 比較容易減少。

6.2 容易減少版本衝突

即使同一台機器上有多個版本的 COM 元件,也比較容易讓每個應用程式各用各的版本。 因為別的產品安裝而行為突然改變 這類出包,會變得相當容易避開。

6.3 通常不必大幅修改既有程式碼

Reg-Free COM 改的是 解析的方式,而不是從根本改變既有程式碼的呼叫寫法。 因此只要條件合適,幾乎不用動 CoCreateInstance 那一側的程式碼就能導入。

6.4 移除與復原變得輕鬆

因為東西都關在以應用程式為單位的範圍裡,更新或復原(rollback)都會變得很直接了當。 講得極端一點,整個資料夾換掉 的思路變得容易採用。

關在應用程式單位裡的好處說明把 COM 元件關在以應用程式為單位的範圍裡,就能得到容易做 XCOPY 散發、容易避開版本衝突、幾乎不動既有程式碼即可導入,以及移除與復原都很單純這些好處的圖。關在以應用程式為單位的範圍容易做 XCOPY 散發可以減少版本衝突整個資料夾換掉也行呼叫端的程式碼幾乎不用動

圖 8: 每一項優點,都是從「關起來」這個單一性質長出來的。

7. 合用與不合用的情境

7.1 合用的情境

在下列情況裡,Reg-Free COM 相當有力。

情況 契合度
想把應用程式專用的 COM DLL / OCX 一起包進去 非常好
想讓同一台 PC 上並存多個版本 非常好
想避開廠商元件的註冊出包 好
想在既有桌面應用程式裡以 private 的方式使用 ActiveX / OCX 好
想讓散發變輕,又不想大改既有的呼叫端 好

典型上,它和 業務用桌面應用程式、設備介接工具、VB6 / MFC / WinForms 的既有資產 特別合。

7.2 不合用,或需要謹慎評估的情境

另一方面,也有一些情況最好謹慎評估。

情況 說明
想在整台機器層級共用 COM Reg-Free 的好處就變薄
位元數本來就對不上 Reg-Free 解決不了
強烈依賴非標準的註冊資訊或自訂安裝流程 不容易改寫成資訊清單
相依 DLL 或 VC++ 執行階段的散發還沒整理好 最後還是會在別的地方跌倒
設計階段的工具或 IDE 的參考設定以登錄檔為前提 需要另一套維運設計

最後一點特別重要。 Reg-Free COM 幫得上 執行階段的啟用,但 設計階段的參考設定 UI 以什麼為前提,並不會因此一次就跟著改變。

執行階段有解,設計階段仍在說明 Reg-Free COM 能協助執行階段的啟用,但設計階段的工具或 IDE 參考設定若以登錄檔為前提就不會跟著改變,必須另外做一套維運設計的圖。執行階段的啟用Reg-Free 幫得上忙設計階段的參考設定 UI有時以登錄檔為前提需要另一套維運設計

圖 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 關在應用程式本機
  • 容易減少版本衝突
  • 容易讓散發與復原變單純

這些優點。

另一方面,

  • 32bit / 64bit
  • 相依 DLL
  • TLB / 參考設定
  • 對非標準註冊資訊的依賴
  • 在乾淨環境的驗證

依然重要。

所以,導入 Reg-Free COM 時的基本態度是這樣。

  1. 想清楚這是 activation 的議題,劃清界線
  2. 把 runtime 與 design-time 的議題分開
  3. 在乾淨環境確認
  4. 先把位元數與相依 DLL 準備齊

照這個順序看,就會相當不容易出事。

導入時的基本態度說明導入 Reg-Free COM 時要想清楚這是啟用的議題,把執行階段與設計階段的議題分開,在乾淨環境確認,並先把位元數與相依 DLL 準備齊,照這個順序就比較不容易出事的圖。想清楚這是 activation 的議題分開 runtime 與 design-time在乾淨環境確認先把位元數與相依 DLL 準備齊

圖 17: 依序守住這四個態度,導入時的出包就能大幅減少。

13. 相關文章

14. 參考資料

共用相同標籤的最新文章。能以相近的主題延伸理解。

與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。

本文連結到以下服務頁面,歡迎從最接近的入口查看。

常見問題

整理諮詢這個主題時常見的問題。

什麼是 Reg-Free COM?
它是把 COM 的註冊資訊放在資訊清單而不是登錄檔裡的機制,名稱是 Registration-Free COM 的縮寫。執行階段解析 CoCreateInstance 或 CLSIDFromProgID 時會先查看啟用內容,再依照其中寫的資訊清單資料解析出 DLL。這讓 COM DLL/OCX 可以依應用程式各自以 private 的方式持有,好處是容易做 XCOPY 散發、容易避開版本衝突,解除安裝也比較不會出事。
改用 Reg-Free COM 之後,32bit/64bit 的問題也會一起解決嗎?
不會解決。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 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽