從 32bit 應用呼叫 64bit DLL 的 COM 橋接案例
· 更新日期: · Go Komura · COM, Windows 開發, 32bit, 64bit
更新紀錄(3 筆,最後更新 2026年09月04日)
本文的修改紀錄。已保存的更新前版本,可透過附有 DOI 的永久連結閱讀。
- 已將繁體中文版改寫為日文原文的完整翻譯。先前的繁體中文版只譯出日文原文的一部分,遺漏了章節、表格、Mermaid 圖、圖說與 FAQ。本次依日文原文將這些內容全部補回,並新增本文的知識地圖章節。技術主張與日文版一致。 查看更新前的版本 (DOI: 10.5281/zenodo.22278989)
- 補上了日文原文中已有的諮詢引導(consultation_services),並刪除了內文開頭與標題重複的一級標題。版面本來就會顯示標題,讀者原先會看到兩次。 查看更新前的版本 (DOI: 10.5281/zenodo.22170267)
- 在第 3 節之前補充了一段。如果你想從 64 位元這一側呼叫的東西本身就是 in-proc 的 COM 伺服器(透過 InprocServer32 註冊的 DLL),那麼只要給它的 CLSID 加上 AppID,並在該 AppID 機碼下寫入空字串的 DllSurrogate,它就能被 Windows 內建的 dllhost.exe 承載,有時根本不必自己寫 EXE 伺服器。這一段還指出,啟動的 surrogate 的位元數由 DLL 決定而非用戶端,而且只要註冊了 LocalServer32,surrogate 就不會被使用。不過,如果要呼叫的只是一個普通的原生 DLL(本文正是這種情況),那就根本沒有可供承載的 COM 伺服器,後文介紹的 EXE 伺服器方案依然是必要的。註冊步驟,以及 surrogate 是否夠用、何時該自己寫 EXE 的界線,分別交給了指向另外兩篇文章的連結。 查看更新前的版本 (DOI: 10.5281/zenodo.21616232)
- 初次發布
引用本文(DOI: 10.5281/zenodo.21616231)
本文保存於 Zenodo。以下同時提供一律指向最新版本的 DOI,以及固定於您正在閱讀版本的 DOI。
Go Komura(2026)。〈從 32bit 應用呼叫 64bit DLL 的 COM 橋接案例〉。小村軟體有限公司。https://doi.org/10.5281/zenodo.21616231 https://comcomponent.com/zh-TW/blog/2026/01/25/002-com-case-study-32bit-to-64bit/
- DOI(最新版本)
- 10.5281/zenodo.21616231
- DOI(此版本)
- 10.5281/zenodo.22297058
想從 32bit 應用呼叫 64bit DLL,這種需求在 Windows 上相當典型。 尤其是想保留既有資產,只使用 64 位元側的功能時,COM 橋接的架構很容易成為實際可行的解法。
目標讀者: 正在維護既有的 32bit Windows 應用,並且想使用 64 位元側的 DLL 或函式庫的人。本文以「聽過 COM,但沒有自己動手組過」為前提來寫。
前提環境: 64 位元版的 Windows(x64),以及可以撰寫 C# 的開發環境(例如 Visual Studio)。把 COM 伺服器註冊到整台電腦(HKEY_LOCAL_MACHINE 底下)的作業需要系統管理員權限。COM 的基本觀念整理在「什麼是 COM - Windows COM 的設計為何至今依然優美」。
目錄
圖中實線表示始終成立的關係,虛線表示附帶條件的關係(成立條件寫在詳細頁面中各關係的說明中)。關係的完整清單(共 19 條,附依據與可信度)以及主要概念的定義,彙整在知識地圖詳細頁面(日文)。資料:JSON-LD / Turtle
1. 假設情境
這是在 32bit 既有應用維持原樣的前提下,想使用 64bit DLL 的處理的情況。 然而,32bit 處理程序無法載入 64bit DLL。這是 OS 層級的限制,不是想點辦法就能解決的事情。
常見的狀況像這樣。
- 既有的 32bit 應用作為資產規模很大,無法立刻遷移
- 64bit DLL 那一側有新功能,或是相依函式庫只有 64 位元版
- 想從 32 位元側「以帶型別的方式」呼叫
在這種組合下,在同一個處理程序內呼叫的路一開始就被封死了。
flowchart TB
accTitle: 假設情境的構圖
accDescr: 32bit 的既有應用想使用 64bit DLL 的處理,但 32bit 處理程序無法載入 64bit DLL,這個 OS 層級的限制把在同一個處理程序內呼叫的路封死的圖。
app["32bit 的既有應用"] --> want["想使用 64bit DLL 的處理"]
want --> deny["同一個處理程序內無法載入"]
deny -.-> os["OS 層級的限制,無法繞過"]
圖 1: 32bit 處理程序無法載入 64bit DLL,因此在同一個處理程序內呼叫的路一開始就不存在。
2. 解決方式
這一章開始專有名詞會接連出現,先把最低限度的詞彙列出來。
| 用語 | 意思 |
|---|---|
| In-proc COM(DLL 伺服器) | 把 COM 元件載入到與呼叫端同一個處理程序中使用的形式。速度快,但位元數不一致就無法載入 |
| Out-of-proc COM(EXE 伺服器) | 把 COM 元件當成另一個處理程序啟動來使用的形式。位元數不同也能串接 |
| LocalServer | 指在同一台電腦上以另一個處理程序執行的 COM 伺服器。要把 EXE 的路徑註冊到登錄檔的 LocalServer32 機碼 |
| IDL / TypeLib | 寫下介面形狀(方法名稱、引數型別)的定義(IDL),以及把它二進位化的產物(TypeLib)。兩側靠它們看到同一份「契約」 |
| 封送處理(marshalling) | 為了跨越處理程序邊界,把引數與傳回值改裝成可以傳送的形式。反向則是解除封送處理 |
| Proxy / Stub | 實際執行封送處理的代理程式碼。呼叫端這一側會產生 Proxy,伺服器端則會產生 Stub |
| WOW6432Node | 在 64 位元 Windows 上,給 32 位元應用看的登錄檔內容實際存放的位置。即使機碼名稱相同,32 位元側與 64 位元側的內容也是分開的 |
解決的基本做法,是用 Out-of-proc COM(EXE 伺服器)分離。 64bit DLL 由 64 位元的 COM 伺服器(EXE)呼叫,32bit 應用則透過 COM 使用。
flowchart TB
accTitle: COM 橋接的基本架構
accDescr: 32bit 應用透過 COM 呼叫 64 位元 COM 伺服器的 EXE,而該伺服器在內部呼叫 64bit DLL,說明這種另起處理程序分離架構的圖。
a32["32bit 應用"] -->|"透過 COM 呼叫"| srv["64bit COM 伺服器(EXE)"]
srv -->|"在內部呼叫"| dll["64bit DLL"]
圖 2: 64bit DLL 交給 64 位元的 EXE 伺服器持有,32bit 應用則透過 COM 使用那個伺服器。
流程如下。
- 準備 64 位元的 COM LocalServer(EXE),在其內部呼叫 64bit DLL
- 共用 COM 介面(IDL/TypeLib),公開型別
- 32bit 應用以「帶型別」的方式呼叫 COM(透過 Proxy / Marshal 交換)
不過也有需要注意的地方。
- 32bit/64bit 的註冊是分開的(包含 WOW6432Node)
- 自訂結構需要設計封送處理
- 因為有 IPC 的額外負擔,高頻率呼叫要留意
也就是說,「把 64 位元的處理移到另一個處理程序,再用 COM 搭橋」才是最正統的做法。
flowchart TB
accTitle: 搭建橋接的三個步驟
accDescr: 準備 64 位元的 COM LocalServer 並在內部呼叫 64bit DLL、用 IDL 與 TypeLib 公開介面的型別、由 32bit 應用以帶型別的方式呼叫,說明這三個步驟流程的圖。
s1["準備 64 位元的 COM LocalServer"] --> s2["用 IDL / TypeLib 公開型別"]
s2 --> s3["32bit 應用以帶型別的方式呼叫"]
s3 -.-> ps["透過 Proxy / Marshal 交換"]
圖 3: 準備 LocalServer、公開型別、以帶型別的方式呼叫,橋接由這三個階段構成。
另外,如果 64 位元側想使用的東西原本就是 in-proc 的 COM 伺服器(以 InprocServer32 註冊的 DLL),有時候可以不必自己寫 EXE 伺服器。只要在 CLSID 上加上 AppID,再往那個 AppID 機碼寫入空字串的 DllSurrogate,該 DLL 就會被載到 Windows 內建的 surrogate 處理程序(64 位元的 DLL 就是 System32\dllhost.exe)上,從 32bit 用戶端看來,它就是一個在另一個處理程序中執行的 out-of-proc COM 伺服器(並不是把 EXE 的路徑註冊到 LocalServer32。相反地,只要有 LocalServer32,surrogate 就不會被使用)。反方向(從 64bit 應用使用 32 位元的 COM DLL)機制也一樣,被啟動起來的 surrogate 的位元數不是由用戶端,而是由 DLL 那一側決定。不過,如果像本文這樣想呼叫的只是一個普通的原生 DLL,那就根本沒有可以載到 surrogate 上的 COM 伺服器,因此後面要說明的 EXE 伺服器方式仍然必要。註冊步驟整理在 開發 COM 元件、OCX/ActiveX 時常見的坑 - 整理 Visual Studio 的 32bit/64bit、註冊、管理員權限 的 3.5(那邊是以 32bit DLL 為前提,所以只要把寫入 AppID 值的檢視讀成另一邊即可),surrogate 夠不夠用,還是要自己寫 EXE 的界線則整理在 ActiveX / OCX 現在該怎麼處理 - 保留、包裝、取代的判斷表 的 5.2。
3. 處理流程(序列圖)
以下是 32bit 應用要呼叫 64bit DLL 的處理時,實際走過的流程。
sequenceDiagram
participant App as 32bit 用戶端應用
box rgba(100,100,255,0.1) 由已註冊的 COM 封送處理基礎架構處理
participant Proxy as COM Proxy<br/>(32 位元側)
participant RPC as RPC/IPC<br/>(處理程序間通訊)
participant Stub as COM Stub<br/>(64 位元側)
end
participant Server as 64bit COM Server<br/>(EXE)
participant DLL as 64bit DLL
App->>Proxy: ICalcService.Add(1, 2)
rect rgba(100,100,255,0.1)
Note over Proxy: 對參數做封送處理
Proxy->>RPC: 序列化後的資料
RPC->>Stub: 跨越處理程序邊界傳送
Note over Stub: 對參數做解除封送處理
end
Stub->>Server: Add(1, 2)
Server->>DLL: 原生函式呼叫
DLL-->>Server: 結果: 3
Server-->>Stub: 結果: 3
rect rgba(100,100,255,0.1)
Note over Stub: 對傳回值做封送處理
Stub-->>RPC: 序列化後的結果
RPC-->>Proxy: 跨越處理程序邊界傳送
Note over Proxy: 對傳回值做解除封送處理
end
Proxy-->>App: 結果: 3
圖 4: 32bit 應用的呼叫經過 Proxy、處理程序間通訊與 Stub 抵達 64 位元伺服器與 64bit DLL,結果再沿同一條路徑返回。
重點:
- 32bit 應用可以透過
ICalcService介面以型別安全的方式呼叫 - COM 執行階段會使用已註冊的 Proxy/Stub DLL、TypeLib 封送器、標準封送器等跨越處理程序邊界
- 因為有處理程序間通訊的額外負擔,比起細碎的呼叫,收攏成批次處理更為理想
flowchart TB
accTitle: 呼叫粒度的思考方式
accDescr: 由於處理程序間通訊有額外負擔,反覆進行細碎呼叫時成本會不斷累積,因此收攏成批次處理較為理想,說明這個思考方式的圖。
ipc["處理程序間通訊的額外負擔"] --> fine["反覆進行細碎呼叫時會不斷累積"]
ipc --> batch["收攏成批次處理後影響較小"]
batch -.-> rec["比較理想的是這一邊"]
圖 5: 每次跨越處理程序邊界都要付出成本,所以呼叫最好整併成較粗的粒度。
4. 範例程式碼(示意)
4.1. 共用介面與伺服器、用戶端
以下是概念上的示意。要讓它動起來,還需要後面 4.2 的註冊。
// 共用介面(相當於 IDL)
[ComVisible(true)]
[Guid("7A4B5B23-0A2F-4D2B-9D4D-8A2A92B8B001")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ICalcService
{
int Add(int a, int b);
}
// 64bit COM LocalServer(EXE 側)
[ComVisible(true)]
[Guid("1C9B6F4D-1E9A-4E61-9A4F-6A0F1D2D9A11")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("KomuraSoft.CalcService")]
public class CalcService : ICalcService
{
public int Add(int a, int b)
{
// 在這裡呼叫 64bit DLL
return a + b;
}
}
// 32bit 應用側(用戶端)
Type t = Type.GetTypeFromProgID("KomuraSoft.CalcService");
var calc = (ICalcService)Activator.CreateInstance(t);
int result = calc.Add(1, 2);
做成這個形式之後,32 位元側就能「以帶型別的方式」處理。 COM 會在內部使用 Proxy/Stub,透過 IPC 替你完成呼叫。
之所以加上 [ProgId("KomuraSoft.CalcService")],是為了讓用戶端可以用 Type.GetTypeFromProgID("KomuraSoft.CalcService") 找到它。ProgID 只不過是「人看得懂的別名」,真正找到伺服器的是接下來要說明的 CLSID 註冊。
flowchart LR
accTitle: 從 ProgID 抵達伺服器的過程
accDescr: 用戶端指定的 ProgID 只是人看得懂的別名,系統會由它查出 CLSID,再靠 CLSID 的註冊找到實際的伺服器,說明這個解析順序的圖。
progid["ProgID(人看得懂的別名)"] --> clsid["CLSID 的註冊"]
clsid --> srv["找到伺服器"]
圖 6: ProgID 是入口的別名,真正指向伺服器的是 CLSID 的註冊。
4.2. 最低限度的註冊步驟
COM 的機制是「COM 執行階段查出登錄檔中已註冊的 CLSID 再把它啟動起來」,所以沒有註冊的程式碼絕對不會動(Type.GetTypeFromProgID 會傳回 null,或是在 CreateInstance 時得到 REGDB_E_CLASSNOTREG)。EXE 伺服器(LocalServer)需要的註冊,追根究柢只有下面這三個機碼。
| 要註冊的東西 | 機碼 | 值 |
|---|---|---|
| ProgID → CLSID 的對照關係 | HKEY_CLASSES_ROOT\KomuraSoft.CalcService\CLSID |
{1C9B6F4D-1E9A-4E61-9A4F-6A0F1D2D9A11} |
| CLSID → EXE 的路徑 | HKEY_CLASSES_ROOT\CLSID\{1C9B6F4D-…}\LocalServer32 |
64bit COM 伺服器 EXE 的完整路徑 |
| CLSID → ProgID 的反查 | HKEY_CLASSES_ROOT\CLSID\{1C9B6F4D-…}\ProgID |
KomuraSoft.CalcService |
而本文主題本身的陷阱就在這裡。Microsoft 的文件明確寫著:HKEY_LOCAL_MACHINE\SOFTWARE\Classes 由 32bit 應用與 64bit 應用共用,但它底下的 CLSID 子機碼(以及 Interface 等)在 32 位元側與 64 位元側是分開的(32 位元側的實體就是 WOW6432Node)。也就是說,ProgID 的機碼只要寫一次兩邊都看得到,但CLSID 的註冊如果沒有同時寫進 32 位元檢視與 64 位元檢視,32bit 用戶端就找不到伺服器。
flowchart TB
accTitle: 登錄檔中共用的部分與分開的部分
accDescr: Classes 底下的 ProgID 機碼只要寫一次 32 位元與 64 位元都看得到,而 CLSID 底下在 32 位元檢視與 64 位元檢視是分開的,必須兩邊都寫,缺了 32 位元側時 32bit 用戶端就找不到伺服器,說明這件事的圖。
progk["ProgID 的機碼(Classes 底下)"] --> shared["兩邊都看得到"]
clsk["CLSID 底下的註冊"] --> v64["寫進 64 位元檢視"]
clsk --> v32["寫進 32 位元檢視"]
v32 -.-> wow["實體是 WOW6432Node"]
v32 -.-> warn["缺了就從 32 位元看不到"]
圖 7: ProgID 的機碼是共用的,但 CLSID 底下依 32 位元/64 位元的檢視而分開,所以兩邊都要註冊。
在系統管理員權限的命令提示字元中,使用 reg 命令的 /reg:32 與 /reg:64 最為可靠(Microsoft 不建議自己把 Wow6432Node 寫進路徑裡)。
:: 以系統管理員權限的命令提示字元執行
set CLSID={1C9B6F4D-1E9A-4E61-9A4F-6A0F1D2D9A11}
set PROGID=KomuraSoft.CalcService
set SERVER=C:\Program Files\KomuraSoft\CalcServer.exe
:: 1) ProgID → CLSID(HKLM\SOFTWARE\Classes 底下由 32/64 共用)
reg add "HKLM\SOFTWARE\Classes\%PROGID%\CLSID" /ve /d "%CLSID%" /f
:: 2) CLSID → LocalServer32 與 ProgID(CLSID 底下 32/64 分開,所以兩邊都要寫)
:: LocalServer32 的值要連引號一起把執行檔的路徑放進去。
:: 寫成 /d "%SERVER%" 時,引號會在 reg.exe 的引數解析中消失,值會以
:: C:\Program Files\... 的形式存進去。COM 會把它當成命令列來解讀,
:: 於是會先去找在空白之前被截斷的 C:\Program.exe
reg add "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\LocalServer32" /ve /d "\"%SERVER%\"" /f /reg:64
reg add "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\ProgID" /ve /d "%PROGID%" /f /reg:64
reg add "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\LocalServer32" /ve /d "\"%SERVER%\"" /f /reg:32
reg add "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\ProgID" /ve /d "%PROGID%" /f /reg:32
:: 查看存進去的值。如果像 "C:\Program Files\..." 這樣帶著引號出現就是正確的
reg query "HKLM\SOFTWARE\Classes\CLSID\%CLSID%\LocalServer32" /ve /reg:64
LocalServer32 的引號不只是規矩問題。沒有引號時,C:\Program Files\... 就可能被解讀成「把 Files\... 當成引數傳給 C:\Program」。結果,在有人握有建立 C:\Program.exe 權限的環境中,那一個可能會先被啟動。要放進含有空白的路徑時,一定要加上引號。
flowchart TB
accTitle: LocalServer32 有無引號造成的解讀差異
accDescr: LocalServer32 的值若不加引號就放入路徑,就可能被解讀成在空白之前截斷,在可以放置 C:\Program.exe 的環境中那個執行檔可能先被啟動,而連引號一起存入則會啟動預期中的 EXE,說明這個差異的圖。
noq["不加引號存入"] --> cut["可能被解讀成在空白之前截斷"]
cut --> evil["C:\Program.exe 可能先被啟動"]
q["連引號一起存入"] --> safe["會啟動預期中的 EXE"]
圖 8: 把含有空白的路徑不加引號註冊,就留下了讓別的執行檔被啟動的空隙。
取消註冊只要刪掉同樣的機碼。
set CLSID={1C9B6F4D-1E9A-4E61-9A4F-6A0F1D2D9A11}
set PROGID=KomuraSoft.CalcService
reg delete "HKLM\SOFTWARE\Classes\CLSID\%CLSID%" /f /reg:64
reg delete "HKLM\SOFTWARE\Classes\CLSID\%CLSID%" /f /reg:32
reg delete "HKLM\SOFTWARE\Classes\%PROGID%" /f
此外,EXE 這一側在啟動時必須向 COM 表明「這個 CLSID 由我負責」。用 C/C++ 的話是 CoRegisterClassObject,.NET Framework 的話則是 RegistrationServices.RegisterTypeForComClients。登錄檔的註冊只照顧到 COM 把 EXE 啟動起來為止,所以少了這一步表明身分的動作,就會變成 EXE 有啟動,物件卻建不出來這種很難看出原因的失敗。
flowchart TB
accTitle: 登錄檔註冊與 EXE 側的表明身分
accDescr: 登錄檔註冊照顧到的只到 COM 把 EXE 啟動起來為止,啟動後的 EXE 表明自己負責這個 CLSID,物件才建得出來,缺了這個表明就會變成 EXE 有啟動卻建不出物件的失敗,說明這件事的圖。
regd["登錄檔註冊"] --> boot["COM 啟動 EXE"]
boot --> ann["EXE 表明自己負責這個 CLSID"]
ann --> ok["可以建立物件"]
boot -.->|"缺了表明身分時"| ng["有啟動卻建不出來的失敗"]
圖 9: 登錄檔註冊只到啟動 EXE 為止,再往後如果沒有 EXE 自己表明身分,物件就建不出來。
另外,如果只是在開發途中試跑,把同樣的結構寫到 HKCU\SOFTWARE\Classes 而不是 HKLM,不需要系統管理員權限也能註冊(HKEY_CLASSES_ROOT 是 HKLM 與 HKCU 的合成檢視)。不過 HKCU\SOFTWARE\Classes\CLSID 同樣是 32bit/64bit 分開處理的,所以兩個檢視都要寫這一點並沒有改變。
4.3. .NET Framework 與 .NET(5 以後)的做法不同
上面的程式碼是 C#,但要用哪一種 .NET,步驟會有很大的差別。把兩者混在一起就會卡住。
| .NET Framework | .NET(Core 3.0 / 5 以後) | |
|---|---|---|
| 註冊工具 | 有 RegAsm.exe(不過它產生的是 in-proc 用的 InprocServer32 註冊,所以 LocalServer32 終究還是要自己寫) |
沒有相當於 RegAsm 的工具 |
| 標準的 COM 公開方式 | 在組件上加屬性再跑 RegAsm |
用 <EnableComHosting>true</EnableComHosting> 產生 *.comhost.dll,再用 regsvr32 註冊(僅限 in-proc) |
| TypeLib(.tlb)的產生 | 可以用 TlbExp 或 RegAsm /tlb 產生 |
不支援。要手寫 IDL 並用 MIDL 編譯(.NET 6 以後可以把做好的 .tlb 內嵌到 comhost 裡) |
| CLSID 的指定 | 可省略 | 要讓 COM 建立的類別必須明確指定 CLSID |
| AnyCPU 的處理 | 32bit/64bit 兩種用戶端都能使用 | 隨附的 *.comhost.dll 預設是 64 位元,所以只能從 64bit 用戶端使用 |
本文的架構(EXE 伺服器)在 .NET(5 以後)已經超出標準 EnableComHosting 的範圍,因此註冊處理要自己寫。Microsoft 官方備有 OutOfProcCOM 這個範例,要在 .NET 這一側組建時,那裡就是出發點。
flowchart TB
accTitle: 在 .NET(5 以後)搭建 EXE 伺服器的路徑
accDescr: 本文的 EXE 伺服器架構在 .NET 5 以後超出標準 EnableComHosting 的範圍,因此註冊處理要自己寫,而官方範例 OutOfProcCOM 就是出發點,說明這條路徑的圖。
exe[".NET(5 以後)的 EXE 伺服器"] --> range["超出標準 EnableComHosting 的範圍"]
range --> self["自己撰寫註冊處理"]
self -.-> smp["官方範例 OutOfProcCOM 是出發點"]
圖 10: .NET(5 以後)的 EXE 伺服器超出標準功能的範圍,所以註冊處理要以自行撰寫為前提。
5. 完整的範例程式碼
把上述概念實作成可實際執行的形式,範例公開在 GitHub 上。
Call64bitDLLFrom32bitProc - GitHub
這個儲存庫包含以下內容:
- Call64bitDLLFrom32bitProc/ - 64bit COM LocalServer (EXE)
- X64DLL/ - 64bit DLL(實際處理)
- X86App/ - 32bit 用戶端 (WinForms)
- scripts/ - COM 伺服器註冊、取消註冊指令碼
依照 README 記載的步驟建置並註冊之後,就能實際確認從 32bit 處理程序呼叫 64bit DLL 的運作。
6. 總結
COM 橋接並非萬能,而是適合的工作與不適合的工作分得很清楚的架構。決定採用之前,請先用下面這張表對照自己的情況。
| 適合的情況 | 不適合的情況 |
|---|---|
| 32bit 應用本體無法重做(改寫的成本划不來) | 本來就能把 32 位元側重新建置成 64 位元(那才是最短路徑) |
| 呼叫是粗粒度的(一次一張影像、一次一個檔案等) | 一次一個元素、跑上數萬次這種高頻率的細碎呼叫(IPC 的額外負擔會佔主導) |
| 交換的是數值、字串、陣列等容易封送處理的型別 | 大量來回傳遞原始指標或複雜的自訂結構 |
| 即使 64 位元側的處理當掉,也想讓應用本體繼續活著(處理程序分離成為優點) | 不想撰寫處理伺服器端當掉或重新啟動的復原邏輯 |
| 想保留帶型別的呼叫(IntelliSense 與編譯時檢查) | 一次性的批次處理就夠,用標準輸入輸出或檔案交換就足夠 |
反過來看最後一列,「把 64 位元側的處理做成單純的主控台 EXE,用引數與檔案來交換」這種單純的替代方案,也一直值得評估。COM 橋接派得上用場的場合,是想維持帶型別的呼叫時,以及想反覆呼叫一個持有狀態的伺服器時。
flowchart TB
accTitle: COM 橋接與單純替代方案的分界
accDescr: 若需要維持帶型別的呼叫或反覆呼叫持有狀態的伺服器,COM 橋接就派得上用場,若一次性的批次處理就夠,則把它做成主控台 EXE 用引數與檔案交換的單純替代方案也值得評估,說明這個分界的圖。
q{"需要帶型別的呼叫或保持狀態嗎?"} -->|"是"| br["COM 橋接"]
q -->|"否"| alt["用主控台 EXE 以引數與檔案交換"]
圖 11: 是否需要帶型別的呼叫與保持狀態,就是橋接與單純替代方案的分界。
接下來的行動,建議照這個順序進行。
- 先 clone 第 5 章的範例儲存庫,依照 README 建置並註冊,在手邊做出一個會動的狀態。
- 從自己的 64bit DLL 中只挑一個函式,在相當於範例
ICalcService的介面上加一個方法讓它跑通。 - 跑通之後,量測呼叫次數與每次的資料量。先在這裡做完「收攏成粗粒度」的設計判斷(把多次呼叫整併成一次),之後的返工就會變少。
7. 參考資料
- Component Object Model (COM) 概觀 https://learn.microsoft.com/en-us/windows/win32/com/component-object-model–com–portal
- COM LocalServer32 的註冊 https://learn.microsoft.com/en-us/windows/win32/com/localserver32
- COM 介面的基礎 https://learn.microsoft.com/en-us/windows/win32/com/the-component-object-model
- COM Interop(從 .NET 使用) https://learn.microsoft.com/en-us/dotnet/standard/native-interop/cominterop
- WOW64 的登錄檔重新導向器(
HKLM\SOFTWARE\Classes為共用,CLSID底下 32/64 分開) https://learn.microsoft.com/en-us/windows/win32/winprog64/shared-registry-keys - 把 .NET(Core / 5 以後)的元件公開給 COM https://learn.microsoft.com/en-us/dotnet/core/native-interop/expose-components-to-com
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
開發 COM 元件、OCX/ActiveX 時常見的坑 - 整理 Visual Studio 的 32bit/64bit、註冊、管理員權限
整理開發 COM、OCX、ActiveX 元件時最容易卡關的四個面向:宿主行程的 32bit/64bit、Visual Studio 2022 變成 64bit 後的設計時整合、regsvr32 與 Regasm 的註冊位置、以及管理員權限與 HKCU/HKLM 的關係,協...
Office 2024/Microsoft 365 中 ActiveX 無法運作的原因與檢查步驟
說明在 Office 2024/Microsoft 365 中 ActiveX 無法運作時,依序排查預設停用、32bit/64bit、COM 註冊、相依 DLL、IE 模式、Click-to-Run 記錄的順序。
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 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
ActiveX 遷移
整理保留、包裝或替換 COM / ActiveX / OCX 資產的階段性判斷的主題頁面。
32 位元 / 64 位元互通
整理 32 位元 / 64 位元互通、原生邊界與相關 Windows 設計判斷的主題頁面。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
既有資產活用 & 遷移支援
本文談的是在保留 32bit 資產的同時往 64 位元那一側架橋,因此和既有資產活用、遷移支援直接相關。
技術諮詢 & 設計審查
如果現在還停在想先釐清 COM 橋接與處理程序邊界該怎麼劃分的階段,可以用技術諮詢、設計審查的形式一起比較評估。
常見問題
整理諮詢這個主題時常見的問題。
- 可以從 32bit 應用直接呼叫 64bit DLL 嗎?
- 不行。32bit 處理程序無法載入 64bit DLL,這是 OS 層級的限制,不是想辦法就能繞過去的事情。在同一個處理程序內呼叫的路一開始就被封死了,因此需要把 64 位元側的處理分離到另一個處理程序的架構。
- 要從 32bit 應用使用 64bit DLL 的功能該怎麼做?
- 最正統的做法是用 Out-of-proc COM(EXE 伺服器)分離。64bit DLL 由 64 位元的 COM LocalServer(EXE)呼叫,32bit 應用則透過 COM 介面以帶型別的方式使用。共用 COM 介面(IDL/TypeLib)來公開型別,COM 執行階段會用 Proxy/Stub 與處理程序間通訊替你跨越邊界。
- COM 橋接架構有哪些注意事項?
- 主要有三點。32bit 與 64bit 的註冊是分開的(包含 WOW6432Node),自訂結構需要設計封送處理,以及處理程序間通訊有額外負擔,所以高頻率的細碎呼叫要留意。與其把大量細碎的呼叫灌進橋接,不如收攏成批次處理。
- 有實際會動的範例程式碼嗎?
- 有。GitHub 的 Call64bitDLLFrom32bitProc 儲存庫公開了一整套範例,包含 64bit COM LocalServer(EXE)、64bit DLL、32bit 用戶端(WinForms),以及 COM 伺服器的註冊與取消註冊指令碼。依照 README 的步驟建置並註冊之後,就能確認從 32bit 處理程序呼叫 64bit DLL 的運作。