更新紀錄(1 筆,最後更新 2026年08月30日)
本文的修改紀錄。已保存的更新前版本,可透過附有 DOI 的永久連結閱讀。
- 補充了 DllSurrogate 的說明:為什麼 regsvr32 成功之後 64 位元行程仍然無法載入 32 位元的 InprocServer32;如何在 CLSID 上加 AppID、並在該 AppID 機碼下寫入空字串的 DllSurrogate,就能不寫新的 EXE 把 DLL 放進 dllhost.exe;為什麼啟動的 dllhost.exe 位元數取決於 DLL 而不是用戶端;為什麼已註冊的 LocalServer32 會優先於代理行程;以及如何驗證結果。文中也明確指出代理行程並不會消除位元數差異:消失的只有同一行程的限制,呼叫會變成跨行程,並帶來封送處理與 IPC 的成本。 查看更新前的版本 (DOI: 10.5281/zenodo.21616408)
- 初次發布
引用本文(DOI: 10.5281/zenodo.21616407)
本文保存於 Zenodo。以下同時提供一律指向最新版本的 DOI,以及固定於您正在閱讀版本的 DOI。
小村 豪(2026)。〈開發 COM 元件、OCX/ActiveX 時常見的坑 - 整理 Visual Studio 的 32bit/64bit、註冊、管理員權限〉。小村軟體有限公司。https://doi.org/10.5281/zenodo.21616407 https://comcomponent.com/zh-TW/blog/2026/04/15/001-com-ocx-activex-pitfalls-visual-studio-bitness-admin-rights/
- DOI(最新版本)
- 10.5281/zenodo.21616407
- DOI(此版本)
- 10.5281/zenodo.22170296
開發 COM 元件或 OCX/ActiveX 的案子,容易卡住的地方往往不在程式碼本身,而是在 執行環境、註冊、宿主、權限 的邊界。
常見的症狀像這些:
- 編譯可過,執行起來就
0x80040154。 - 自己機器跑得動,別台 PC 不行。
- 執行階段可以跑,只有 Visual Studio 的 Designer 掛。
- 用管理員起動才行,一般權限就壞。
regsvr32叫了半天,怎樣都治不好。
這些通常不是單一 bug,而是 COM 的前提在某個地方對不上 的狀態。
想先釐清 COM/ActiveX/OCX 這幾個名詞,可以先看 COM/ActiveX/OCX 是什麼 - 差異與關係一次整理,整體感會比較清楚。 本文接著往下一階段:實際在哪裡最容易卡,連 Visual Studio 的位元數與管理員權限議題也一起整理。
1. 先下結論
用實務可用的講法:
- COM/OCX/ActiveX 的問題,多半不是邏輯錯,而是 bitness(32bit/64bit)、註冊位置、宿主、權限 對不上。
- Visual Studio 2022 是 64bit 行程,所以以前「將就能跑」的 32bit 設計時整合常常會壞。12
regsvr32不是 什麼都能註冊的萬靈丹,它針對原生的 in-proc COM server(DLL/OCX)。要把 .NET Framework 公開給 COM 用,要走Regasm.exe;.NET 5+/6+/8+ 則是註冊產生的.comhost.dll。3456- 「管理員可以跑就好」很危險。常常其實是 per-user 註冊剛好看得到,或是 原本該由安裝器做的註冊,只在開發機靠手動完成。378
所以處理 COM/OCX/ActiveX 時,請先照下面 4 軸對齊:
- 哪個行程在當宿主
- 那個行程是 32bit 還是 64bit
- 註冊到哪裡(HKCU/HKLM,32bit view/64bit view)
- 那個操作或執行是否需要管理員權限
2. 從症狀反推,通常長這樣
| 症狀 | 先懷疑 | 真正原因常是 |
|---|---|---|
0x80040154 Class not registered |
沒註冊 | 其實「只有另一個位元數那邊註冊了」或「只對那個使用者註冊」 |
DllRegisterServer failed: 0x80070005 |
權限不足 | 用標準使用者註冊、post-build 沒有提升權限就註冊 |
| VS2022 裡只有 Designer 掛 | Designer 的限制 | 32bit COM/ActiveX 沒辦法被 64bit 的 Visual Studio 直接載入 |
| 只有管理員啟動才跑得動 | 權限 | 本質常常不是權限,而是註冊範圍或安裝設計偏了 |
| 64bit 應用叫不到 32bit OCX | COM 架構限制 | in-proc server 必須和宿主同位元數才能載入 |
| UI 執行緒可以,背景執行緒就卡 | 執行緒模型 | STA/MTA、CoInitializeEx、訊息迴圈的前提違反 |
0x80040154 的正式定義是 REGDB_E_CLASSNOTREG,字面上就是 Class not registered。9
regsvr32 的 0x80070005 在官方說明中也描述為 沒有管理員權限,無法寫入登錄檔或 System32。10
這裡的重點是 別完全照錯誤訊息的字面去判斷。
例如 Class not registered 並不等於「完全沒註冊」;註冊到別的登錄 view、只對某使用者註冊 也會造成同樣訊息。117
3. 在 Visual Studio 的位元數上卡住
3.1. Visual Studio 2022 變成 64bit
這是目前 COM/ActiveX 開發最容易踩的點。
Visual Studio 2022 的 devenv.exe 是 64bit only。1
因此 WinForms 的設計時體驗中,Visual Studio 那一側 無法直接載入 32bit 元件。Microsoft 也明確指出,Visual Studio 2022 是 64bit 行程,無法載入 32bit 的 .NET/COM/ActiveX 元件。2
以往:
- 專案 x86
- 參照的 ActiveX 也 x86
- Visual Studio 本身也 32bit
剛好湊得起來。VS2022 之後:
- 執行階段的應用可以 x86 跑
- 但 Designer 跑在 64bit 的 Visual Studio 這一側
就形成扭曲。 結果就是 執行階段活著,但只有 Designer 掛,非常煩。2
3.2. 改成 AnyCPU 也不一定解
這個誤會也很常見。
AnyCPU 不是 能讓相依端也自動中立的神奇開關。
Microsoft 指出,即使你的元件看起來是 AnyCPU,只要後面引用了固定 32bit 的 COM/ActiveX,Visual Studio 2022 的設計時就會出問題。2
所以改成 AnyCPU 還是錯時,請懷疑:
- 該組件後面有沒有 32bit 的原生相依
- ActiveX/OCX 是否只能 x86
- 有沒有只有 Designer 時才會載入的程式碼
這些比亂試更快找到原因。
3.3. System32 與 SysWOW64 的陷阱
x64 Windows 上的命名很容易令人誤會。
Microsoft Learn 指出:x64 Windows 上的 %windir%\System32 是給 64bit 應用程式用的;32bit 那側會被 WOW64 的檔案系統重導向機制引到別的位置。12
登錄檔同理,WOW64 的 registry redirector 會讓 32bit/64bit 看到 不同的邏輯 view。11
所以在本機排查時,明確寫出用了哪個位元數的 regsvr32 會比較安全:
# 要註冊 64bit DLL/OCX 時
C:\Windows\System32\regsvr32.exe vendor.ocx
# 在 x64 Windows 上要註冊 32bit DLL/OCX 時
C:\Windows\SysWOW64\regsvr32.exe vendor.ocx
這個陷阱最惡心的地方是 註冊到錯的一側,你「以為註冊了」但目標行程看不到。 最後變成:
regsvr32成功- 但 app 端就是
0x80040154 - 看登錄檔好像有
- 但你看的其實是另一個 view
3.4. regsvr32 不是什麼都能註冊
這點也很常被誤解。
原生的 in-proc COM server 一般會匯出 DllRegisterServer/DllUnregisterServer 支援自我註冊。3
regsvr32 就是對這類 DLL/OCX 使用的工具。4
若是要把 .NET Framework 的組件給 COM 使用,則該用 Regasm.exe。Microsoft Learn 也明確寫道,要給 COM 使用的組件,用 Regasm.exe 來註冊。514
.NET 5+/6+/8+ 的 COM 公開又有點不同:設定 <EnableComHosting>true</EnableComHosting> 編譯後會產生 *.comhost.dll,再用 regsvr32 註冊它。6
簡單歸納:
| 要公開的對象 | 常用的註冊方式 |
|---|---|
| 原生 C++ 的 DLL/OCX | regsvr32 |
| .NET Framework 組件 COM 公開 | Regasm.exe |
| .NET 5+/6+/8+ COM 公開 | 產出的 .comhost.dll 以 regsvr32 註冊 |
把這三種混在一起,常見的錯法是:
- 對 managed DLL 跑
regsvr32 - 當然找不到
DllRegisterServer - 結果誤判成「DLL 壞了」
需要型別函式庫(TLB)的世界(VBA/VB6/部分 early binding 對象),TLB 的產生與註冊 又是另一回事。.NET Framework 可以用 Regasm.exe /tlb 產生/註冊型別函式庫,Microsoft 也說明「型別註冊」與「型別函式庫註冊」是兩件獨立的事情。15
3.5. 想從 64bit 行程使用 32bit in-proc 時 ── DllSurrogate
3.3 與 3.4 講的是「註冊的位置與手段有沒有對上」。 這裡再補上一條出路,用來處理 註冊明明是對的,bitness 卻對不上 的情況。
先講結論。
- 就算
regsvr32執行成功,64bit 行程也讀不了 32bit 的InprocServer32。這不是註冊沒做好,而是 in-proc 這個前提本身。 - 如果不想另外寫一個新的 EXE,又想從 64bit 這側使用那個 DLL,最小的處置就是 給 CLSID 加上
AppID,並在對應的AppID機碼寫入空字串的DllSurrogate。16 - 這時候起來的,不是用戶端那側,而是
InprocServer32所指向的 DLL 那側 bitness 的dllhost.exe。
為什麼維持 in-proc 就是做不到
第 2 章症狀表裡列出的「64bit 應用叫不到 32bit OCX」,就算長得跟 0x80040154 一樣,內容卻是兩回事。
InprocServer32 字面上就是「在呼叫端的行程裡面執行的 server」這個指定。64bit 行程要載入寫在那裡的 32bit DLL,不管怎麼修登錄檔都不會發生。3.3 裡登錄檔的 view 之所以分開,追根究柢也是因為這個限制。
所以從這裡開始,話題不再是「把註冊修好」,而是 「挪到另一個行程去」。
代理行程(surrogate)做的事
DllSurrogate 是一個指定,用來 把 in-proc 的 DLL server 放進代理行程,以 Local Server 的形式對外提供。17
把值設成空字串,就會使用 Windows 內建的預設代理行程(dllhost.exe)。18
如果是 32bit 的 DLL,就會啟動 32bit 的代理行程,DLL 在其中 照舊以 in-proc 的方式 被載入。64bit 的用戶端則是從那個行程外面,透過 proxy 來操作物件。
這裡很容易被誤解,所以明確寫出來。
代理行程並不會消除 bitness 的差異。 消失的只是「必須放在同一個行程裡」這個限制。呼叫會變成 out-of-proc,還會多出 marshalling 與行程間通訊的成本。前提是 來往的型別要能夠 marshalling(IDispatch、已註冊的 proxy / stub、標準 marshaler 能處理的型別,三者之一)。
還有一點。用代理行程比較好處理的,是靠方法呼叫與事件就能收尾的自動化物件。貼在表單上負責繪製的視覺型 OCX,前提是視窗要在宿主的行程裡面,所以光靠這層處置是搞不定的。
flowchart TB
accTitle: 啟用最後會落在 in-proc 還是代理行程
accDescr: 本圖表示以包含 in-proc 的 CLSCTX 提出要求時,若與用戶端同一 view 的 CLSID 下有 InprocServer32,就先以 in-proc 載入;若沒有,而存在 EXE server 的註冊,則 EXE 會比代理行程先啟動;若沒有 EXE 註冊而有空的 DllSurrogate,就啟動 DLL 那側 bitness 的 dllhost;若以上都沒有,就無法啟用。
req["用戶端提出啟用要求"] --> ctx["以包含 in-proc 的 CLSCTX 提出要求"]
ctx --> q1{"同一 view 的 CLSID 下有 InprocServer32 嗎"}
q1 -->|"有"| inproc["照舊以 in-proc 載入"]
q1 -->|"沒有"| q2{"有 EXE server 的註冊嗎"}
q2 -->|"有"| exe["EXE 比代理行程先啟動"]
q2 -->|"沒有"| q3{"有空的 DllSurrogate 嗎"}
q3 -->|"有"| host["啟動 DLL 那側 bitness 的 dllhost"]
q3 -->|"沒有"| err["無法啟用,0x80040154"]
圖 1:啟動時先看是否能看到相同位元數的 InprocServer32,看不到時才依序落到 EXE 伺服器與空的 DllSurrogate。
註冊的最小集合
Microsoft Learn 把 DLL server 能放進代理行程的條件列成下面這樣。16
- CLSID 機碼下有
AppID這個值,而且對應的AppID機碼存在 - 啟用的呼叫中有設
CLSCTX_LOCAL_SERVER,而且 CLSID 機碼下 沒有LocalServer32/LocalServer/LocalService - CLSID 機碼下有
InprocServer32 InprocServer32所指向的 DLL 確實存在AppID機碼下面有DllSurrogate這個值
如果要讓那個 DLL 單獨跑在一個代理行程裡,Microsoft 建議的寫法是 把 AppID 設成與 CLSID 相同的 GUID。16
註冊只要 3 行就夠。下面是從 64bit 宿主使用 32bit COM DLL 的情形,GUID 是佔位符。前提是 已經先用 SysWOW64 那側的 regsvr32 把 InprocServer32 寫進去了(參見 3.3)。
:: 以管理員權限的命令提示字元執行
set CLSID={11111111-2222-3333-4444-555555555555}
set APPID={11111111-2222-3333-4444-555555555555}
:: 1) 給 CLSID 加上 AppID。CLSID 底下 32/64 是分開的,所以要明確指定 view
:: 如果反過來是要從 32bit 宿主使用 64bit 的 COM DLL,這裡就讀成 /reg:64
reg add "HKLM\SOFTWARE\Classes\CLSID\%CLSID%" /v AppID /t REG_SZ /d "%APPID%" /f /reg:32
:: 2) 建立對應的 AppID 機碼。Classes\AppID 在 32/64 之間是共用的,所以不需要指定 view
reg add "HKLM\SOFTWARE\Classes\AppID\%APPID%" /f
:: 3) 把 DllSurrogate 設成空的 REG_SZ。省略 /d 就會以空字串建立
reg add "HKLM\SOFTWARE\Classes\AppID\%APPID%" /v DllSurrogate /t REG_SZ /f
為什麼要設成空字串
DllSurrogate 是 REG_SZ,值本身就是代理行程的路徑。設成空字串(或 NULL)就會使用 系統預設的代理行程,寫上路徑則會嘗試啟動該路徑的自訂代理行程。1718
也就是說,出於好意把 dllhost.exe 的路徑寫進去反而會適得其反,留空這件事本身就是「請用預設的那個」的指定。
另外,HKLM\SOFTWARE\Classes\AppID 從 Windows 7/Windows Server 2008 R2 之後,是 32bit/64bit 共用 的機碼,不區分 view。19
而 CLSID 底下的 view 是分開的,所以如果是 32bit 的 server,AppID 值就得加在 32bit view 的 CLSID 那側。沒生效的時候,請務必把這兩個地方成對確認。
這裡看起來跟 3.3、6.3 的「只註冊在 32bit 那側的話,從 64bit 行程看就是 0x80040154」互相矛盾,但那邊講的是 in-proc 的情況。在 out-of-proc 的啟用裡,當用戶端與 server 兩邊都沒有提出 bitness 的要求時,COM 會找符合用戶端 bitness 的 server,找不到就啟動另一個 bitness 的 server。20
所以這套步驟,在 CLSID 那側仍然放在 32bit view 的狀態下就能成立。不需要把 InprocServer32 複製到 64bit view。
起來的 dllhost.exe 是哪個 bitness
這裡最常被誤解。
代理行程的 bitness,不是由用戶端那側,而是由 InprocServer32 所指向的 DLL 那側 決定的。DLL 既然是在代理行程裡面以 in-proc 被載入,當然就是這樣。
| 在裡面被載入的 COM DLL | 啟動的 dllhost.exe |
|---|---|
| 32bit | %SystemRoot%\SysWOW64\dllhost.exe |
| 64bit | %SystemRoot%\System32\dllhost.exe |
在工作管理員或 Process Explorer 看命令列,會看到它是以 dllhost.exe /Processid:{...} 的形式起來的。這裡的 GUID 是 AppID 而不是 CLSID。當成 CLSID 去找會找不到,請特別注意。
有 LocalServer32 時 EXE 會贏
Microsoft Learn 明確寫道,當存在 LocalServer / LocalServer32 / LocalService 時,啟動 EXE server 或服務永遠優先於把 DLL 放進代理行程。16
也就是說,代理行程是「沒有 EXE server 時的出路」。對已經註冊了 EXE server 的 CLSID 再加上 DllSurrogate,那邊也不會被使用。這裡被優先的,終究只是 相對於代理行程,而不是相對於 in-proc。
把對既有呼叫端的影響也一併掌握會比較安心。
就算加上 DllSurrogate,用包含 in-proc 的 CLSCTX 建立物件的用戶端,只要 bitness 對得上,就仍然照舊維持 in-proc。因為多個 CLSCTX 用 OR 傳進去時會 按列舉順序(in-proc → local → remote)依序嘗試,而在 in-proc 這一段,InprocServer32 機碼 只要存在就會被使用。20
像 CLSCTX_ALL、CLSCTX_SERVER 這種同時傳了 in-proc 與 local 的呼叫,就屬於這種情況。反過來,只用 CLSCTX_INPROC_SERVER 呼叫的地方,就算加上 DllSurrogate 也不會轉到代理行程。
而且從 64bit 的用戶端來看,放進 32bit view 的 InprocServer32 在 64bit view 裡並不存在。in-proc 這一段會以「沒有對應的機碼」直接跳過,於是就照原樣進入 local 那側(代理行程)的啟用。這並不是嘗試載入 bitness 不同的 DLL 然後失敗。2019
確認方法
有沒有寫進去,明確指定 view 讀回來最確實。
reg query "HKLM\SOFTWARE\Classes\CLSID\{11111111-2222-3333-4444-555555555555}\InprocServer32" /ve /reg:32
reg query "HKLM\SOFTWARE\Classes\CLSID\{11111111-2222-3333-4444-555555555555}" /v AppID /reg:32
reg query "HKLM\SOFTWARE\Classes\AppID\{11111111-2222-3333-4444-555555555555}" /v DllSurrogate
請不要省略第 1 行。reg add 在指定的機碼不存在時會建立它,所以 GUID 打錯,或者註冊到別的 view,就會做出一個只掛著 AppID 值的空 CLSID 機碼,而第 2 行照樣會通過。要看到 InprocServer32 也在同一個 view 裡,才算是真的確認過。
DllSurrogate 只要以 值保持空的 狀態出現就是對的。之後再從 64bit 的用戶端建立物件,看看 SysWOW64 那側的 dllhost.exe 會不會起來。
- 仍然是
0x80040154→ 重新檢查AppID值是不是加在 32bit view 的 CLSID 那側,以及InprocServer32指向的 DLL 是否確實存在 dllhost.exe有起來,但一呼叫就掛 → 確認 marshalling 的前提(IDispatch/ proxy-stub / 標準 marshaler)- 起來的是別的 EXE → 那個 CLSID 上還留著
LocalServer32之類的東西
到這裡為止都是「註冊」的話題
代理行程夠不夠用,還是應該在 32bit 那側自己寫一個輔助用的 EXE,這不是註冊,而是 構成怎麼選 的問題。 那部分整理在 ActiveX / OCX 現在如何處理 - 保留・包裝・取代的判斷表 的 5.2「想把 32bit OCX 帶到 64bit 側」。
4. 在管理員權限上卡住
4.1. post-build 步驟做註冊,只有管理員才會成功
Visual Studio 的 C++ 建置裡,build event 或 custom build step 本來就可以呼叫 regsvr32.exe。Microsoft Learn 的 post-build event 範例也有提到用 regsvr32.exe 做註冊。21
但 可以做 與 做了安全 是兩件事。
標準使用者執行 regsvr32 時,因為無法寫入登錄檔或 System32,會得到 0x80070005。Microsoft KB 把原因歸到 沒有管理員權限。10
常見事故是:
- 用普通權限開 Visual Studio,build 可過
- 只有 post-build 的註冊失敗
- 大家沒發現失敗
- 機器裡還留著之前舊的註冊,所以本機剛好還能跑
- 到乾淨環境自然就爆
這類情境,把建置與註冊分開 是基本原則:
- 建置只產出二進位
- 註冊改成明確的 install step/script/installer
- CI 上把「需要註冊的 step」拆成另一個 job
光這一層分離,事故就少很多。
4.2. per-user 與 per-machine 註冊混用會壞
只記得「COM 看 HKCR」的話,這裡會卡。
Microsoft Learn 指出:COM 會 先看 HKEY_CURRENT_USER\Software\Classes,再看整機資訊。3
HKEY_CLASSES_ROOT 本身其實是 HKLM\Software\Classes 與 HKCU\Software\Classes 的 merged view。722
所以會出現:
- 開發者 A 用他的帳號手動註冊
- 只有 A 的帳號跑得動
- 開發者 B 不行
- 服務帳號也不行
- 用管理員跑行為又變了
Microsoft 也建議:需要管理員權限的應用,應在安裝時把所依賴的 COM 物件註冊到 per-machine 的 COM 設定存放。87
所以開發現場要把下列差異講清楚:
- 只給自己帳號用的開發用註冊
- 整機共用的正式註冊
- 給服務或權限提升的應用用的註冊
不釐清,就會出現「為什麼只有管理員可以跑」「Explorer 擴充可以跑但服務不行」這類狀況。
4.3. Visual Studio 常駐「以管理員身分執行」不是解
有時候確實需要。 但長期把 Visual Studio 開在管理員模式,會 把原本該由 installer 或註冊 script 解決的問題,被 IDE 權限提升掩蓋掉。
Visual Studio 本身在提升權限時的行為也會變。Microsoft Learn 說明:Visual Studio 在 elevated 下執行時,有設定會 停用 per-user extensions。23
比較穩的運維是:
- 平常開發用標準權限
- 需要註冊的 step 才用明確提升的 Developer Command Prompt/PowerShell/installer
- 「只有管理員能重現」的情況,把那個前提本身寫進規格
日後會省很多麻煩。
5. ActiveX/OCX 特有的坑
5.1. 設計時授權與執行時授權是分開的
OCX/ActiveX 有個陰魂不散的問題就是 授權。
特別是老的 ActiveX 控制項,常會把 design-time license 與 run-time license 分開。MFC 的 ActiveX 文件也說明,可以用授權檔或授權金鑰區分 design-time/run-time。2425
這個世界會發生:
- 執行階段可以用
- 但在表單上貼就說「找不到授權」
- 機器 A 貼得了
- 機器 B 貼不了
「找不到這個元件的授權」這種錯誤,在老 ActiveX 上不稀奇。26
5.2. 貼進 WinForms 時,其實已經有一層包裝
在 WinForms 裡使用 ActiveX,其實 Windows Forms 並不是直接裝載 ActiveX。
Microsoft Learn 的 Aximp.exe 文件指出,ActiveX Control Importer 會 從 COM 型別函式庫產生 WinForms 用的包裝,以 AxHost 為基底的控制項呈現。2728
所以問題不只是一層,而是:
- 原本的 OCX/ActiveX 本體
- 型別函式庫
- 產生的 interop/wrapper
- WinForms Designer/runtime
多層都可能出事。 常見的情境:
- 更新了廠商的 OCX,事件簽名改了
- 重新設定引用後 wrapper 被重產,diff 超多
- 每台開發機產出的 interop 稍有差異
所以 Choose Toolbox Items 裡有看到並不代表沒事。
Designer 能貼、執行階段能拿到事件、部署端 wrapper 還能對上,這三件事要分開確認。
5.3. 輕看 STA/MTA 與訊息迴圈就會卡死
COM 要求每個使用它的執行緒都要呼叫 CoInitializeEx 做初始化。Microsoft Learn 寫明:每條使用 COM 的執行緒都要各自呼叫 CoInitializeEx。29
而且 STA(single-threaded apartment)需要訊息迴圈。2930
UI 類的 OCX/ActiveX 多半假設 STA,所以:
- UI 執行緒跑得動
Task.Run或 ThreadPool 一跑就卡- 事件回不來
- 偶爾才出事
這些很煩人。
STA 還有前提是:不能直接把介面指標複製到別的執行緒,要用就得做 marshalling。2930
這類 bug 不像 0x80040154 那麼親切,只會呈現「卡住/沒回應/偶爾崩」,排查起來跟登錄檔的麻煩一樣吃時間。
6. 現場管用的排查順序
實務上別一開始就深挖,照這順序切比較快。
6.1. 先固定「哪個行程是幾位元」
先看這個。
- 宿主是什麼(Visual Studio Designer/自家應用/Office/Access/Explorer/瀏覽器相容環境)
- 那個宿主是 32bit 還是 64bit
- 對象的 DLL/OCX 是 32bit 還是 64bit
- 它是 in-proc 還是 out-of-proc
這邊沒釐清就急著看登錄檔,多半迷路。
6.2. 接著確認「註冊的種類」
再來看 正確的註冊方式到底是什麼:
- 原生 DLL/OCX →
regsvr32 - .NET Framework COM 公開 →
Regasm.exe - .NET 5+/6+/8+ COM 公開 →
.comhost.dll - 本來就不支援 self-registration 的 DLL → 用
regsvr32是錯的
光這個分類就能擋掉一大堆誤判。
6.3. 之後才看「註冊在哪裡」
光看 HKCR 不夠。
HKCU\Software\ClassesHKLM\Software\Classes- 必要時區分 32bit/64bit 的 registry view
- 對象的 ProgID/CLSID/TypeLib
InprocServer32/LocalServer32- ThreadingModel
- 相依 DLL 的實體路徑
「HKCR 裡有」並不夠,還要看 是誰、以哪個 bitness、透過哪個 view 才有意義。711
6.4. 最後看「權限有沒有把問題蓋住」
最後確認問題真的是權限,或者只是 權限差異讓你看到不同的註冊。
- 標準使用者/管理員下行為是否不同
- Visual Studio 提升權限後有什麼變
- 服務帳號或其他使用者是否能重現
- 透過 installer 在乾淨環境是否成立
只在一台開發機上驗證,這裡最容易看錯。
7. 先定下來能減事故的運維
COM/ActiveX/OCX 的開發與維護,常常是 運維的決定方式 比實作技巧更重要。
7.1. 先定 bitness 策略
先決定:
- 固定走 x86 嗎
- 以 x64 為主嗎
- 兩者都支援嗎
- 這個元件非 in-proc 不可嗎
尤其廠商 OCX 只能 x86 時,硬把應用升到 x64 一定後面會卡。 相關主題也可參考 從 32bit 應用呼叫 64bit DLL 的方法 - COM 橋接有用的案例研究。
7.2. 定下註冊策略
別臨機應變處理註冊:
- 整機共用 → 以 installer 做 per-machine 註冊
- 只給該使用者用 → 刻意走 per-user
- 自家應用內部自用 → 考慮 registration-free COM
- 只在開發用 → 明確封裝在 dev setup script
registration-free COM 可以把啟用資訊放在 manifest,而不是登錄檔,對 減少註冊地獄 相當有用。Win32 與 .NET 皆有官方介紹。31632
7.3. 不要把產出物放在版控之外
OCX/ActiveX 常會把相依物散落:
- OCX 本體
- 相依 DLL
- TLB
.lic- interop DLL
- AxHost wrapper
- 註冊 script
- 範例宿主
這些只存在某人本機,幾個月後一定出事。
至少下列資訊要與程式碼放在同個地方:
- 以哪個版本為前提
- 以什麼順序放什麼
- 用哪個指令註冊
- 是 x86 還是 x64
8. 這類諮詢很契合
這個主題光是在全面改造前 做切分與方向的整理,就很有價值。
例如下列請求特別契合:
- 想把
0x80040154、0x80070005依 bitness/註冊/權限拆開看清 - 升到 Visual Studio 2022 後 Designer 壞了,想知道能救多少
- 想在留下廠商 OCX 的同時,把週邊搬到 .NET 或 C#
- 想決定 x86 固定資產要延命到哪、從哪裡開始 bridge/wrap/replace
- 想擺脫手動
regsvr32的依賴,重新設計 install/deploy
而「留、包、換」的整體判斷,也可參考 今天的 ActiveX/OCX 該怎麼處理 - 留、包、換的判斷表。
9. 總結
COM 元件或 OCX/ActiveX 開發會卡住的原因,大致是這 4 個:
- bitness 沒對齊
- 註冊方法選錯了
- 註冊範圍(HKCU/HKLM、32bit/64bit view)偏掉
- 誤把「只是被權限看見」的狀態當成正常
Visual Studio 2022 改成 64bit 後,以前「將就能動」的設計變得更容易暴露出問題。12 所以動 COM/OCX/ActiveX 時,先把環境前提對齊再寫程式 是最快的路。
與其 regsvr32 敲很多次,不如先釐清:
- 哪個行程在宿主
- 那個行程是幾位元
- 應該註冊到哪裡
- 那個註冊是不是真的需要管理員
- Designer 與 runtime 是不是分開看的
這些先整理,往往比硬湊快得多。
參考
-
Microsoft Learn, Visual Studio 2022 version 17.0 Release Notes —
devenv.exe is now 64-bit only. https://learn.microsoft.com/en-us/visualstudio/releases/2022/release-notes-v17.0 ↩ ↩2 ↩3 -
Microsoft Learn, Troubleshoot 32-bit problems - Windows Forms — Visual Studio 2022 是 64bit 行程,無法載入 32bit 的 .NET/COM/ActiveX,以及 out-of-process designer 的限制。 https://learn.microsoft.com/en-us/dotnet/desktop/winforms/visualstudio/troubleshoot-32bit ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Classes and Servers — COM 的註冊、HKCU/HKCR、self-registration 與
DllRegisterServer。 https://learn.microsoft.com/en-us/windows/win32/com/classes-and-servers ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, regsvr32 —
regsvr32的語法與角色。 https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/regsvr32 ↩ ↩2 -
Microsoft Learn, Registering assemblies with COM — .NET Framework 的 COM 註冊用
Regasm.exe。 https://learn.microsoft.com/en-us/dotnet/framework/interop/registering-assemblies-with-com ↩ ↩2 -
Microsoft Learn, Expose .NET Core components to COM —
EnableComHosting、產生的.comhost.dll、regsvr32、EnableRegFreeCom。 https://learn.microsoft.com/en-us/dotnet/core/native-interop/expose-components-to-com ↩ ↩2 ↩3 -
Microsoft Learn, Merged View of HKEY_CLASSES_ROOT — HKCR 是 HKLM 與 HKCU 的 merged view。 https://learn.microsoft.com/en-us/windows/win32/sysinfo/merged-view-of-hkey-classes-root ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, HKEY_CLASSES_ROOT Key — 需要管理員權限的應用應採 per-machine COM 設定註冊。 https://learn.microsoft.com/en-us/windows/win32/sysinfo/hkey-classes-root-key ↩ ↩2
-
Microsoft Learn, COM Error Codes (Generic) (Winerror.h) —
REGDB_E_CLASSNOTREG (0x80040154)等。 https://learn.microsoft.com/en-us/windows/win32/com/com-error-codes-1 ↩ -
Microsoft Learn, You receive 0x80070005 error when you try to register a DLL by using Regsvr32.exe — 權限不足導致 DLL 註冊失敗的典型例。 https://learn.microsoft.com/en-us/troubleshoot/windows-client/shell-experience/dllregisterserver-error ↩ ↩2
-
Microsoft Learn, Registry Redirector — WOW64 上 32bit/64bit 的登錄檔 view。 https://learn.microsoft.com/en-us/windows/win32/winprog64/registry-redirector ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, File System Redirector — x64 Windows 的
%windir%\System32與 WOW64 的檔案系統重導向。 https://learn.microsoft.com/en-us/windows/win32/winprog64/file-system-redirector ↩ -
Microsoft Learn, Compatibility considerations for 32-bit programs on 64-bit versions of Windows — WOW64 的檔案/登錄檔重導向。 https://learn.microsoft.com/en-us/troubleshoot/windows-server/performance/compatibility-limitations-32-bit-programs-64-bit-system ↩
-
Microsoft Learn, Regasm.exe (Assembly Registration Tool) —
Regasm.exe的角色與/tlb等選項。 https://learn.microsoft.com/en-us/dotnet/framework/tools/regasm-exe-assembly-registration-tool ↩ -
Microsoft Learn, Packaging a .NET Framework Assembly for COM — 型別函式庫與
Regasm.exe /tlb。 https://learn.microsoft.com/en-us/dotnet/framework/interop/packaging-an-assembly-for-com ↩ -
Microsoft Learn, Registering the DLL Server for Surrogate Activation — 能放進代理行程的條件、存在
LocalServer/LocalServer32/LocalService時優先啟動 EXE server 或服務、把AppID設成與 CLSID 相同 GUID 的構成。 https://learn.microsoft.com/en-us/windows/win32/com/registering-the-dll-server-for-surrogate-activation ↩ ↩2 ↩3 ↩4 -
Microsoft Learn, DllSurrogate —
AppID底下的DllSurrogate是REG_SZ,空字串時使用系統預設的代理行程,寫上路徑則使用該路徑的自訂代理行程。 https://learn.microsoft.com/en-us/windows/win32/com/dllsurrogate ↩ ↩2 -
Microsoft Learn, Using the system-supplied surrogate — 指定空字串或
NULL時會啟動系統預設的代理行程,以及代理行程內部的執行緒模型與行程生存期的處理。 https://learn.microsoft.com/en-us/windows/win32/com/using-the-system-supplied-surrogate ↩ ↩2 -
Microsoft Learn, CLSCTX enumeration — 多個
CLSCTX用 OR 傳入時會按列舉順序依序嘗試,在 in-proc 這一段只要InprocServer32機碼存在就會使用它,以及用戶端與 server 都沒有提出 bitness 要求時會選擇符合用戶端 bitness 的 server、沒有就啟動另一側。 https://learn.microsoft.com/en-us/windows/win32/api/wtypesbase/ne-wtypesbase-clsctx ↩ ↩2 ↩3 -
Microsoft Learn, Understanding Custom Build Steps and Build Events — post-build event 中呼叫
regsvr32.exe的範例。 https://learn.microsoft.com/en-us/cpp/build/understanding-custom-build-steps-and-build-events ↩ -
Microsoft Learn, Windows Registry for advanced users —
HKCU\Software\Classes與HKLM\Software\Classes、HKCR 的行為。 https://learn.microsoft.com/en-us/troubleshoot/windows-server/performance/windows-registry-advanced-users ↩ -
Microsoft Learn, Find, install, and manage extensions for Visual Studio — elevated 執行時 per-user extension 的行為。 https://learn.microsoft.com/en-us/visualstudio/ide/finding-and-using-visual-studio-extensions ↩
-
Microsoft Learn, MFC ActiveX Controls: Licensing an ActiveX Control — design-time/run-time license、
.LIC。 https://learn.microsoft.com/en-us/cpp/mfc/mfc-activex-controls-licensing-an-activex-control ↩ -
Microsoft Learn, Application Settings, MFC ActiveX Control Wizard — 執行時授權產生與
.lic檔。 https://learn.microsoft.com/en-us/cpp/mfc/reference/application-settings-mfc-activex-control-wizard ↩ -
Microsoft Learn, License information for this component not found. You don’t have an appropriate license to use this functionality in the design environment. https://learn.microsoft.com/en-us/office/vba/language/reference/user-interface-help/license-information-for-this-component-not-found-you-don-t-have-an-appropriate-l ↩
-
Microsoft Learn, Aximp.exe (Windows Forms ActiveX Control Importer) — 把 ActiveX 轉成 WinForms 用包裝。 https://learn.microsoft.com/en-us/dotnet/framework/tools/aximp-exe-windows-forms-activex-control-importer ↩
-
Microsoft Learn, AxHost Class — ActiveX Control Importer 產生的 AxHost 包裝。 https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.axhost ↩
-
Microsoft Learn, Initializing the COM library —
CoInitializeEx、每條執行緒的初始化、STA 訊息迴圈。 https://learn.microsoft.com/en-us/windows/win32/learnwin32/initializing-the-com-library ↩ ↩2 ↩3 -
Microsoft Learn, Single-Threaded Apartments — STA 的訊息迴圈、marshalling、
ThreadingModel。 https://learn.microsoft.com/en-us/windows/win32/com/single-threaded-apartments ↩ ↩2 -
Microsoft Learn, Creating Registration-Free COM Objects — 透過 activation context 的免註冊 COM。 https://learn.microsoft.com/en-us/windows/win32/sbscs/creating-registration-free-com-objects ↩
-
Microsoft Learn, Registration-Free COM Interop — .NET Framework 的 registration-free COM interop。 https://learn.microsoft.com/en-us/dotnet/framework/interop/registration-free-com-interop ↩
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
Office 2024/Microsoft 365 中 ActiveX 無法運作的原因與檢查步驟
說明在 Office 2024/Microsoft 365 中 ActiveX 無法運作時,依序排查預設停用、32bit/64bit、COM 註冊、相依 DLL、IE 模式、Click-to-Run 記錄的順序。
COM / ActiveX / OCX 是什麼 - 差異與關係一次整理
從實務角度梳理 COM 是什麼、ActiveX 是什麼、OCX 是什麼,涵蓋三者的差異與關係、與 OLE 的關聯、實際用在哪些地方,以及現在該怎麼看待它們。
ActiveX / OCX 現在該怎麼處理 - 保留、包裝、取代的判斷表
整理發現 ActiveX / OCX 時該選保留、包裝還是取代,把 32 位元與 64 位元、註冊、瀏覽器相依性以至於廠商維護都一起納入判斷。
從 32bit 應用呼叫 64bit DLL 的 COM 橋接案例
當 32bit 應用無法直接呼叫 64bit DLL 時,本文梳理以 COM 橋接串接兩者的思路,並一併說明 Windows 的限制、架構與處理流程。
委外・委託開發 Windows 應用程式前該整理的事項
在委外・委託開發 Windows 應用程式之前,整理既有軟體改版、設備整合、COM/ActiveX、發布與更新、維護等應留意的重點。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
ActiveX 遷移
整理保留、包裝或替換 COM / ActiveX / OCX 資產的階段性判斷的主題頁面。
32 位元 / 64 位元互通
整理 32 位元 / 64 位元互通、原生邊界與相關 Windows 設計判斷的主題頁面。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
Windows 應用程式開發
支援包含常駐處理、設備連動、運作日誌與可維護結構的 Windows 桌面應用程式。
既有資產活用 & 遷移支援
在持續活用 COM / ActiveX / OCX 資產、原生程式碼與 32 位元相依的同時,協助規劃階段性的遷移。
常見問題
整理諮詢這個主題時常見的問題。
- 出現 0x80040154 Class not registered 該怎麼查?
- 這個錯誤不等於「完全沒註冊」,常見的真正原因是只註冊到另一個位元數的登錄 view,或只對某個使用者註冊。先固定「哪個行程當宿主、它是 32bit 還是 64bit、目標 DLL/OCX 是哪個位元數」,再確認正確的註冊方式與註冊位置(HKCU/HKLM、32bit/64bit view),比急著看登錄檔更快找到原因。
- 為什麼 Visual Studio 2022 裡只有 Designer 掛掉?
- Visual Studio 2022 的 devenv.exe 是 64bit only,無法直接載入 32bit 的 .NET、COM、ActiveX 元件。所以執行階段的應用可以 x86 跑,但 Designer 跑在 64bit 的 Visual Studio 那一側就會壞。改成 AnyCPU 也不一定解,只要後面引用了固定 32bit 的 COM/ActiveX,設計時仍會出問題。
- regsvr32 什麼都能註冊嗎?
- 不能。regsvr32 針對的是匯出 DllRegisterServer 的原生 in-proc COM server(DLL/OCX)。.NET Framework 組件要用 Regasm.exe 註冊;.NET 5+/6+/8+ 則是設定 EnableComHosting 產生 .comhost.dll,再用 regsvr32 註冊它。對 managed DLL 直接跑 regsvr32 會找不到 DllRegisterServer,容易誤判成 DLL 壞了。
- 只有用管理員啟動才能跑,是權限問題嗎?
- 本質常常不是權限,而是註冊範圍或安裝設計偏了。HKEY_CLASSES_ROOT 是 HKLM 與 HKCU 的 merged view,COM 會先看 HKCU\Software\Classes,所以「只有某個帳號手動註冊過」也會造成這種症狀。整機共用的正式註冊應由 installer 做 per-machine 註冊,只給自己帳號用的開發用註冊要明確分開,長期把 Visual Studio 開在管理員模式只會把問題掩蓋掉。