什麼是 Reg-Free COM - 免註冊使用 COM 的機制
· 更新日期: · Go Komura · COM, Reg-Free COM, Registration-Free COM, Windows 開發, Legacy 技術
更新紀錄(2 筆,最後更新 2026年09月04日)
本文的修改紀錄。已保存的更新前版本,可透過附有 DOI 的永久連結閱讀。
引用本文(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、型別程式庫、執行緒模型的難處並不會跟著消失。
flowchart TB
accTitle: Reg-Free COM 會消掉什麼、又留下什麼
accDescr: 說明 Reg-Free COM 消掉的主要是被全域註冊拖著走的麻煩,而位元數、相依 DLL、型別程式庫與執行緒模型的難處並不會消失的圖。
rf1["Reg-Free COM"] --> rf2["全域註冊的麻煩會減少"]
rf1 --> rf3["也有留下來的難處"]
rf3 -.-> rf4["位元數 / 相依 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 的啟用拉回以應用程式為單位 的機制。
flowchart TB
accTitle: 註冊資訊的存放位置改變了
accDescr: 說明 Reg-Free COM 把 COM 的註冊資訊放在資訊清單而不是登錄檔,解析 CoCreateInstance 等呼叫時會先查看啟用內容,因此 COM 元件可以依應用程式各自以 private 的方式持有的圖。
mg1["註冊資訊放在資訊清單裡"] --> mg2["解析時先看啟用內容"]
mg2 --> mg3["COM 元件可依應用程式各自以 private 持有"]
mg3 -.-> mg4["容易做 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 進行公開
反過來說,本文想強調的有兩點。
- Reg-Free COM 講的是「啟用」這件事
- 型別資訊的散發與設計階段的參考設定,可能會以另一個議題留下來
把這兩件事混在一起,討論就會變得相當混濁。
flowchart TB
accTitle: 不可混在一起的兩個議題
accDescr: 說明 Reg-Free COM 講的是啟用這件事,而型別資訊的散發與設計階段的參考設定會以另一個議題留下來,把兩者混在一起討論就會變得混濁的圖。
pt1["Reg-Free COM"] --> pt2["講的是啟用這件事"]
pt3["型別資訊的散發、設計階段的參考設定"] --> pt4["以另一個議題留下來"]
pt2 -.-> pt5["混在一起討論就會變混濁"]
pt4 -.-> pt5
圖 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,所以呼叫端不需要另外做什麼特別安排。
在這個基礎上,用一張圖看全貌會比較快。
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
圖 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 就是用來減輕這種散發模型痛苦的機制。
flowchart TB
accTitle: 全域共用反咬一口的流程
accDescr: 說明 COM 的註冊資訊放進登錄檔後整台機器容易共用,但實務上容易因其他產品覆寫、解除安裝波及與開發機和正式機的環境差異而反咬一口,讓人困擾的是散發模型而不是 COM 本體的圖。
gs1["註冊資訊在整台機器共用"] --> gs2["多個應用程式都能用,很方便"]
gs1 --> gs3["實務上共用會反咬一口"]
gs3 -.-> gs4["覆寫 / 波及 / 環境差異"]
gs3 --> gs5["散發模型讓人困擾"]
圖 5: 痛苦的真正來源不是 COM 本身,而是全域註冊這個前提。
5. Reg-Free COM 的運作機制
5.1 在應用程式資訊清單裡寫下相依關係
首先,應用程式那一側要在應用程式資訊清單裡寫下 自己相依於哪些 side-by-side assembly。
這份資訊清單可以:
- 像
MyApp.exe.manifest那樣放在 EXE 旁邊 - 以資源的形式嵌入 EXE
兩種方式都能處理。 實務上,想讓散發與替換一目了然就用外部檔案,想優先取得不易損壞與散發單純就用嵌入,這樣的取捨很常見。
另外,當外部檔案版與嵌入版同時存在時,以檔案系統上的資訊清單優先。
5.2 在元件資訊清單裡寫下 COM 資訊
接著,COM 那一側要把 原本放在登錄檔裡的資訊 移到元件資訊清單。
放進去的,例如是下面這些資訊。
comClassclsidprogidthreadingModeltypelib- 必要時還有 Proxy / Stub 或 window class 等
也就是說,概念上是改用 XML 描述 COM 的長相 來取代登錄檔。
這份資訊清單可以:
- 和 DLL 分開成不同檔案放置
- 以資源的形式嵌入 DLL
兩種方式都能設定。
實務上,以 private assembly 的形式嵌入 DLL 通常比較不會出事。分開成獨立檔案雖然直接了當,但很容易在檔名與 assemblyIdentity 的對應、放置位置、漏拷貝這些地方絆倒。
flowchart TB
accTitle: 元件資訊清單的存放方式
accDescr: 說明原本放在登錄檔裡的 comClass、clsid、typelib 等資訊改由元件資訊清單以 XML 保存,可以選擇和 DLL 分開放置或以資源嵌入 DLL,而實務上嵌入比較不會出事的圖。
cm1["原本在登錄檔的資訊改用 XML 保存"] --> cm2["分開成獨立檔案放置"]
cm1 --> cm3["嵌入 DLL"]
cm2 -.-> cm4["容易因漏拷貝等絆倒"]
cm3 -.-> cm5["實務上通常比較不會出事"]
圖 6: 資訊的內容一樣,但放置方式的選擇會左右出事的機率。
5.3 執行階段會先查看啟用內容
Reg-Free COM 的關鍵就在這裡。
當應用程式呼叫 CLSIDFromProgID 或 CoCreateInstance 時,COM 執行階段會查看 目前作用中的啟用內容。
只要那裡有必要的 ProgID → CLSID、CLSID → DLL 資訊,就能不經過登錄檔完成解析。
反過來說,如果資訊清單裡缺少必要資訊,就會退回一般以註冊為基礎的解析。 正因為這個行為,才會出現 開發機上碰巧跑得起來 的陷阱:以為已經改成 Reg-Free,實際上卻是被本機的註冊救了一命。
這是 Reg-Free COM 最惱人的陷阱。
flowchart TB
accTitle: 退回註冊解析的陷阱
accDescr: 說明解析 CoCreateInstance 時會先查看啟用內容,但資訊清單缺少必要資訊時會退回一般以註冊為基礎的解析,因而出現開發機上被本機註冊救著、碰巧跑得起來的陷阱的圖。
ac1["解析時先看啟用內容"] --> ac2{"資訊清單的資訊夠不夠"}
ac2 -->|"夠"| ac3["不經過登錄檔完成解析"]
ac2 -->|"不夠"| ac4["退回以註冊為基礎的解析"]
ac4 -.-> ac5["開發機上碰巧跑得起來的陷阱"]
圖 7: 安靜退回的 fallback,是這個機制最大的陷阱。
6. 好處在哪裡
Reg-Free COM 的優點,在實務上相當明顯。
6.1 容易做 XCOPY 散發
必要的檔案可以全部放進應用程式資料夾,安裝程式與註冊處理都會變輕。
當然,如果要寫進 Program Files 底下,權限是另一回事,但至少 為了 COM 註冊而做的系統管理員作業 比較容易減少。
6.2 容易減少版本衝突
即使同一台機器上有多個版本的 COM 元件,也比較容易讓每個應用程式各用各的版本。
因為別的產品安裝而行為突然改變 這類出包,會變得相當容易避開。
6.3 通常不必大幅修改既有程式碼
Reg-Free COM 改的是 解析的方式,而不是從根本改變既有程式碼的呼叫寫法。
因此只要條件合適,幾乎不用動 CoCreateInstance 那一側的程式碼就能導入。
6.4 移除與復原變得輕鬆
因為東西都關在以應用程式為單位的範圍裡,更新或復原(rollback)都會變得很直接了當。 講得極端一點,整個資料夾換掉 的思路變得容易採用。
flowchart TB
accTitle: 關在應用程式單位裡的好處
accDescr: 說明把 COM 元件關在以應用程式為單位的範圍裡,就能得到容易做 XCOPY 散發、容易避開版本衝突、幾乎不動既有程式碼即可導入,以及移除與復原都很單純這些好處的圖。
cl1["關在以應用程式為單位的範圍"] --> cl2["容易做 XCOPY 散發"]
cl1 --> cl3["可以減少版本衝突"]
cl2 -.-> cl5["整個資料夾換掉也行"]
cl3 -.-> cl4["呼叫端的程式碼幾乎不用動"]
圖 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 以什麼為前提,並不會因此一次就跟著改變。
flowchart TB
accTitle: 執行階段有解,設計階段仍在
accDescr: 說明 Reg-Free COM 能協助執行階段的啟用,但設計階段的工具或 IDE 參考設定若以登錄檔為前提就不會跟著改變,必須另外做一套維運設計的圖。
rt1["執行階段的啟用"] --> rt2["Reg-Free 幫得上忙"]
dt1["設計階段的參考設定 UI"] --> dt2["有時以登錄檔為前提"]
dt2 -.-> dt3["需要另一套維運設計"]
圖 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 先處理的是 讓程式能啟動。 要怎麼帶著型別開發,是下一個議題。
flowchart TB
accTitle: 啟動的議題與型別的議題的先後
accDescr: 說明資訊清單裡雖然也能寫 typelib 資訊,但 VBA 的引用項目設定、C++ 的 import、.NET 設計階段產生參考等型別資訊的處理需要另外設計,Reg-Free COM 先處理的是讓程式能啟動,帶型別開發是下一個議題的圖。
st1["設定 Reg-Free COM"] --> st2["先讓程式能啟動"]
st2 --> st3["接著才是怎麼帶型別開發"]
st3 -.-> st4["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」。
flowchart TB
accTitle: .NET 5 之後的 Reg-Free COM
accDescr: 說明 .NET 5 之後用 EnableComHosting 產生作為 COM 公開入口的 comhost.dll,啟用 EnableRegFreeCom 後會輸出 Reg-Free COM 用的 side-by-side 資訊清單,但 TLB 的產生與註冊必須另外組起來的圖。
dn1["以 EnableComHosting 建置"] --> dn2["comhost.dll 成為入口"]
dn2 --> dn3["啟用 EnableRegFreeCom"]
dn3 --> dn4["輸出 Reg-Free 用的資訊清單"]
dn4 -.-> dn5["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 正確填寫。
flowchart TB
accTitle: 兩份資訊清單的一致要求
accDescr: 說明應用程式端的 dependentAssembly 與元件端的 assemblyIdentity 必須在名稱與版本上一致,一旦錯開就會變成光看錯誤訊息無法判斷原因的啟動失敗的圖。
mm1["應用程式端的 dependentAssembly"] --> mm3{"名稱與版本是否一致"}
mm2["元件端的 assemblyIdentity"] --> mm3
mm3 -->|"一致"| mm4["解析成立"]
mm3 -->|"錯開"| mm5["看不出原因的啟動失敗"]
圖 12: 比起 XML 的細節,兩側的名牌是否一致才是生死關鍵。
10.4 資訊清單要放在哪裡、又要怎麼嵌入
這是步驟上最容易卡住的地方。放成獨立檔案,還是嵌入二進位檔,可以取的名字並不一樣。
side-by-side 會針對應用程式資料夾,依下列順序尋找 private assembly。
- WinSxS 資料夾
<appdir>\<assemblyname>.DLL<appdir>\<assemblyname>.manifest<appdir>\<assemblyname>\<assemblyname>.DLL<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 那一側也要改成相同名稱。
flowchart TB
accTitle: 搜尋會停下來的機制
accDescr: 說明 side-by-side 會從 WinSxS 依序在應用程式資料夾尋找 private assembly,一旦先找到和 assembly 同名的 DLL 就會停止搜尋,因此獨立檔案的做法必須把 assembly 名稱取得和 DLL 不同的圖。
se1["開始尋找 private assembly"] --> se2["尋找與 assembly 同名的 DLL"]
se2 -->|"找到"| se3["搜尋停在這裡"]
se2 -->|"沒找到"| se4["尋找同名的 manifest 檔"]
se3 -.-> se5["獨立檔案的做法要把名稱取得不一樣"]
圖 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 對照,就能排除「以為嵌進去了、其實沒有」這種情況。
flowchart TB
accTitle: 用 mt.exe 嵌入的步驟
accDescr: 說明使用 mt.exe 時先以 validate_manifest 通過語法檢查,再把應用程式資訊清單嵌入 EXE、把元件資訊清單以資源 ID 1 嵌入 DLL,最後取出來和原本的 XML 對照確認的流程的圖。
mt1["讓語法檢查通過"] --> mt2["把應用程式端嵌入 EXE"]
mt2 --> mt3["把元件端嵌入 DLL〔ID 1〕"]
mt3 --> mt4["取出來與原本的 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 來說,「在開發機上跑起來了」幾乎沒有意義,因為永遠留著「其實只是被本機的登錄檔註冊救了一命」的可能。請依下列順序確認。
- 建置之後,把要散發的檔案全部收進一個資料夾。 包含 EXE、資訊清單、COM DLL、相依 DLL、VC++ 執行階段,以及 Proxy / Stub DLL
- 準備驗證環境。 最理想的是目標 COM 從來沒有被註冊過的環境。使用 Windows 沙箱,就能每次都從全新的狀態開始試
- 在那個環境裡,先確認目標 COM 尚未註冊。 用下面的指令執行後,如果出現找不到機碼的錯誤,就代表尚未註冊
- 把資料夾複製過去,直接啟動。 不執行安裝程式,也不執行
regsvr32 - 確認能一路走到建立 COM 物件為止。 只是啟動的話,延遲建立的元件無法驗證。要實際操作到會呼叫
CoCreateInstance的畫面或功能 - 如果失敗,就依 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 的環境裡會造成影響,因此準備乾淨環境比較安全。
flowchart TB
accTitle: 在乾淨環境確認的流程
accDescr: 說明把要散發的檔案收進一個資料夾,準備目標 COM 從未註冊過的驗證環境,先確認尚未註冊之後再把資料夾複製過去直接啟動,並操作到會呼叫 CoCreateInstance 的功能來確認的流程的圖。
cv1["把要散發的檔案收成一份"] --> cv2["準備乾淨的驗證環境"]
cv2 --> cv3["先確認尚未註冊"]
cv3 --> cv4["複製過去直接啟動"]
cv4 --> cv5["操作到會建立 COM 的功能"]
cv5 -.->|"失敗時"| cv6["採集記錄來調查"]
圖 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 的命名方式弄錯,只要看這裡「去尋找的檔案名稱」也能發現。
flowchart TB
accTitle: side-by-side 錯誤的調查方式
accDescr: 說明出現 side-by-side configuration is incorrect 而無法啟動時,先在事件檢視器確認來源為 SideBySide 的錯誤,再以 sxstrace 開始追蹤並重現失敗,停止後用 parse 轉成人看得懂的格式來追查不一致之處的流程的圖。
sx1["在事件記錄檔確認 SideBySide"] --> sx2["用 sxstrace 開始追蹤"]
sx2 --> sx3["啟動應用程式重現失敗"]
sx3 --> sx4["停止後以 parse 轉換"]
sx4 --> sx5["讀出搜尋與不一致的位置"]
圖 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 時的基本態度是這樣。
- 想清楚這是 activation 的議題,劃清界線
- 把 runtime 與 design-time 的議題分開
- 在乾淨環境確認
- 先把位元數與相依 DLL 準備齊
照這個順序看,就會相當不容易出事。
flowchart TB
accTitle: 導入時的基本態度
accDescr: 說明導入 Reg-Free COM 時要想清楚這是啟用的議題,把執行階段與設計階段的議題分開,在乾淨環境確認,並先把位元數與相依 DLL 準備齊,照這個順序就比較不容易出事的圖。
bs1["想清楚這是 activation 的議題"] --> bs2["分開 runtime 與 design-time"]
bs2 --> bs3["在乾淨環境確認"]
bs3 --> bs4["先把位元數與相依 DLL 準備齊"]
圖 17: 依序守住這四個態度,導入時的出包就能大幅減少。
13. 相關文章
- COM / ActiveX / OCX 是什麼 - 差異與關係一次整理
- ActiveX / OCX 現在該怎麼處理 - 保留、包裝、取代的判斷表
- 從 VBA 帶型別使用 .NET 8 的 DLL - 以 COM 發布 + dscom 產生 TLB
14. 參考資料
- Microsoft Learn - 建立免註冊的 COM 物件
- Microsoft Learn - 應用程式資訊清單
- Microsoft Learn - Assembly Manifests(嵌入時的資源 ID 與名稱限制)
- Microsoft Learn - Assembly Searching Sequence(private assembly 的搜尋順序)
- Microsoft Learn - Manifest File Schema
- Microsoft Learn - Mt.exe
- Microsoft Learn - 免註冊的 COM 互通功能 (.NET Framework)
- Microsoft Learn - 將 .NET Core 元件公開給 COM
- Microsoft Learn - sxstrace
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
COM / ActiveX / OCX 是什麼 - 差異與關係一次整理
從實務角度梳理 COM 是什麼、ActiveX 是什麼、OCX 是什麼,涵蓋三者的差異與關係、與 OLE 的關聯、實際用在哪些地方,以及現在該怎麼看待它們。
WinRT 就是 COM —— IInspectable、.winmd、語言投影,以及 WinUI 至今仍立在二進位契約之上的原因
WinRT 不是受管理的執行階段,而是在 COM 之上加了中繼資料(.winmd)與語言投影的 ABI。本文從 IUnknown 與 IInspectable 的關係,一路談到桌面應用程式裡 HWND 初始化與 package identity 的卡關之處。
什麼是 OLE 物件 —— 內嵌與連結的機制以及業務文件中的陷阱
在 Word 中內嵌 Excel 表格的功能,本質就是 OLE 物件。本文從內嵌與連結的差異、複合檔案與結構化儲存體、In-Place Activation 的機制,一路談到連結中斷、檔案膨脹與資安對策,全部立足於實務視角。
剪貼簿與拖放的運作方式 ── 在業務應用程式中正確處理 OLE 資料傳輸
貼上 Excel 表格就散掉、關掉複製來源就貼不上──真正的原因,是剪貼簿把同一份內容以多種格式同時放上。本文從標準格式、延遲轉譯、OLE 拖放,一路談到剪貼簿歷程與雲端同步的原則。
今日的 Windows 殼層整合 ── 右鍵選單、檔案關聯,以及 Windows 11 的變化
說明 Windows 11 的右鍵選單為何會藏進「顯示更多選項」,並整理副檔名→ProgID→verb 這套關聯基礎、傳統殼層擴充功能的注意事項,以及 IExplorerCommand 與 MSIX/sparse package 的新做法。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
ActiveX 遷移
整理保留、包裝或替換 COM / ActiveX / OCX 資產的階段性判斷的主題頁面。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
Windows 應用程式開發
從 COM DLL / OCX 的散發、資訊清單設定、位元數到相依 DLL,都與 Windows 桌面應用程式的實作直接相關。
技術諮詢 & 設計審查
也適合用來梳理該不該採用 Reg-Free COM、要不要保留以註冊為主的維運方式,以及型別程式庫與設計階段的參考設定該如何切分。
常見問題
整理諮詢這個主題時常見的問題。
- 什麼是 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 的產生、嵌入與註冊另外梳理清楚會比較安全。