想從 VBA 呼叫 .NET 8 的處理,情境其實還相當常見。尤其是想把 Excel 或 Access 的既有資產保留下來,只把重計算、字串處理、HTTP、加密、業務邏輯等搬到 C# 時。
但若用 CreateObject 往 late binding 那邊靠,VBA 裡就會滿是 Object:IntelliSense 會變弱、方法名的拼錯要等執行時才發現,整個開發就慢慢陷進靠字串硬湊的泥濘。
所以這次聚焦在:把 .NET 8 的 DLL 以 COM 發布,用 dscom 產出 type library(TLB),再從 VBA 用 early binding 帶型別使用。
.NET Framework + RegAsm 的舊時代、手寫 IDL 再用 MIDL 打包、Reg-Free COM 的議題,這次都先擺一邊。本文只走 .NET 8 / COM host / dscom / VBA early binding 這一條直線。
1. 先下結論
先把流程整段列出:
- 以
EnableComHosting=true編譯 .NET 8 類別庫 - 為 COM 公開 明確的介面 與 類別
- 類別用
ClassInterfaceType.None,不要靠AutoDual - 給 VBA 的介面用
InterfaceIsDual - 編譯後從
*.dll以dscom tlbexport產出*.tlb - 用
regsvr32註冊*.comhost.dll - 用
dscom tlbregister註冊*.tlb - VBA 加上「參考設定」,再用
Dim x As 函式庫名.IYourInterface帶型別使用
一句話:COM 的入口是 .NET SDK 產出的 *.comhost.dll,型別資訊由 dscom 產生為 *.tlb,VBA 看 TLB 做 early binding。
2. 架構總覽
先用一張圖看各自的角色。
flowchart LR
VBA["VBA / Excel / Access"] -->|以參考設定的 TLB 取得型別資訊| TLB["VbaTypedComSample.tlb"]
VBA -->|COM 呼叫| COMHOST["VbaTypedComSample.comhost.dll"]
COMHOST --> DOTNET["VbaTypedComSample.dll (.NET 8)"]
DOTNET --> RUNTIME[".NET 8 Runtime"]
各檔案的角色:
| 檔案 | 角色 |
|---|---|
VbaTypedComSample.dll |
.NET 8 的實作本體 |
VbaTypedComSample.comhost.dll |
來自 COM 的呼叫入口 |
VbaTypedComSample.tlb |
VBA 參考的型別資訊 |
VbaTypedComSample.deps.json |
相依關係的解析資訊 |
VbaTypedComSample.runtimeconfig.json |
.NET 執行時期啟動資訊 |
重點:VBA 要知道型別,需要的是 TLB;COM 的啟動入口則是 comhost。
把 .dll 一個丟過去並不會自動跑起來。COM 的世界在這邊不會那麼順。
3. 一開始就要決定 - 對齊 32bit / 64bit
這裡沒配好,十之八九會走向 無法建立 ActiveX 元件 這條路。
Office/VBA 與 COM 伺服器的 bitness 要對齊。
| 使用端 | .NET 端建議 | TLB 產生 | 註冊指令 |
|---|---|---|---|
| 64bit Office | x64 / win-x64 |
dscom |
C:\Windows\System32\regsvr32.exe |
| 32bit Office(在 64bit Windows 上) | x86 / win-x86 |
dscom32.exe |
C:\Windows\SysWOW64\regsvr32.exe |
.NET 5+ 之後的 COM host 在 AnyCPU 時,*.comhost.dll 會偏向 64bit 這一側,遇到 32bit Office 常常不對盤。所以 請依 Office 明確指定 x86 / x64。
本文以 64bit Office 為例。若是 32bit Office,請把 x64 讀成 x86、win-x64 讀成 win-x86。
4. .NET 8 端實作
以 VBA 可呼叫 Add、Divide、Hello 的最小示範為例。
4.1 .csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<EnableComHosting>true</EnableComHosting>
<PlatformTarget>x64</PlatformTarget>
<NETCoreSdkRuntimeIdentifier>win-x64</NETCoreSdkRuntimeIdentifier>
</PropertyGroup>
</Project>
重點是 EnableComHosting。加上之後,編譯時會產生 VbaTypedComSample.comhost.dll。
4.2 組件預設設為不對 COM 公開
只讓特定型別對 COM 公開 ComVisible(true),組件整體預設 false 比較輕鬆。
using System.Runtime.InteropServices;
[assembly: ComVisible(false)]
4.3 撰寫要公開的介面與類別
using System.Runtime.InteropServices;
namespace VbaTypedComSample;
[ComVisible(true)]
[Guid("2A1BBEDE-DE6E-4C34-AD60-2E9E0E33E999")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface ICalculator
{
[DispId(1)]
int Add(int x, int y);
[DispId(2)]
double Divide(double x, double y);
[DispId(3)]
string Hello(string name);
}
[ComVisible(true)]
[Guid("FAD1C752-0BB6-4DDD-889F-FE446350847A")]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(ICalculator))]
public class Calculator : ICalculator
{
public Calculator()
{
}
public int Add(int x, int y) => checked(x + y);
public double Divide(double x, double y)
{
if (y == 0)
{
throw new ArgumentOutOfRangeException(nameof(y), "不能除以 0。");
}
return x / y;
}
public string Hello(string name)
{
if (string.IsNullOrWhiteSpace(name))
{
return "Hello";
}
return $"Hello, {name}";
}
}
這段程式要掌握的重點:
Guid為 介面 與 類別 各自指派- 用
ClassInterfaceType.None,不靠自動產生的 class interface - 為了讓 VBA 好用,介面用
InterfaceIsDual - 加上
DispId,之後改方法順序時比較不會出事 - COM 會呼叫
New,所以要有 public 且無參數的建構子
5. 編譯
做 Release 編譯。
dotnet build -c Release
編譯後輸出資料夾至少會有:
bin/
Release/
net8.0-windows/
VbaTypedComSample.dll
VbaTypedComSample.comhost.dll
VbaTypedComSample.deps.json
VbaTypedComSample.runtimeconfig.json
發布與註冊都使用這個資料夾。之後若要改擺放位置,要重新註冊。
6. 用 dscom 產生 TLB
6.1 64bit 的情況
先安裝 dscom。
dotnet tool install --global dscom
再從編譯產物產生 TLB。
dscom tlbexport .\bin\Release\net8.0-windows\VbaTypedComSample.dll --out .\bin\Release\net8.0-windows\VbaTypedComSample.tlb
6.2 給 32bit Office 的情況
這裡是個陷阱。要給 32bit Office 產 TLB,用 dscom32.exe 比較保險。
.\tools\dscom32.exe tlbexport .\bin\Release\net8.0-windows\VbaTypedComSample.dll --out .\bin\Release\net8.0-windows\VbaTypedComSample.tlb
7. 註冊 COM host 與 TLB
這一步請用 系統管理員權限的 CMD/PowerShell 執行。
7.1 64bit Office / 64bit COM
$out = Resolve-Path .\bin\Release\net8.0-windows
C:\Windows\System32\regsvr32.exe "$out\VbaTypedComSample.comhost.dll"
dscom tlbregister "$out\VbaTypedComSample.tlb"
7.2 32bit Office(在 64bit Windows 上)
$out = Resolve-Path .\bin\Release\net8.0-windows
C:\Windows\SysWOW64\regsvr32.exe "$out\VbaTypedComSample.comhost.dll"
.\tools\dscom32.exe tlbregister "$out\VbaTypedComSample.tlb"
這一步做了兩件事:
- 用
regsvr32把*.comhost.dll註冊為 COM 伺服器 - 用
tlbregister把*.tlb註冊為 type library
8. 在 VBA 做參考設定並帶型別使用
- 打開 Excel 或 Access
- 打開 VBA 編輯器
工具->參考設定- 若清單裡看到函式庫,勾選
- 若看不到,透過
瀏覽...選取VbaTypedComSample.tlb
Option Explicit
Public Sub UseCalculator()
Dim calc As VbaTypedComSample.ICalculator
Set calc = New VbaTypedComSample.Calculator
Debug.Print calc.Add(10, 20)
Debug.Print calc.Divide(10, 4)
Debug.Print calc.Hello("VBA")
End Sub
這樣 VBA 就能獲得:
- IntelliSense 可用
- 方法名 typo 可在執行前被發現
- Object Browser 能檢視公開 API
- 比硬寫
Object更可讀
8.1 例外在 VBA 端會是 COM 錯誤
若 .NET 端丟例外(例如 Divide(10, 0)),VBA 端會以 COM 錯誤看到。
Option Explicit
Public Sub UseCalculatorWithErrorHandling()
On Error GoTo EH
Dim calc As VbaTypedComSample.ICalculator
Set calc = New VbaTypedComSample.Calculator
Debug.Print calc.Divide(10, 0)
Exit Sub
EH:
Debug.Print Err.Number
Debug.Print Err.Description
End Sub
9. 發布的思路
發布時重要的是 不要只發布 DLL,而是把輸出整包放上去。
VbaTypedComSample.dll
VbaTypedComSample.comhost.dll
VbaTypedComSample.deps.json
VbaTypedComSample.runtimeconfig.json
VbaTypedComSample.tlb
(必要時還有相依 DLL)
另外,用戶端要 對應版本的 .NET 8 執行時期。COM host 不走 self-contained,基本上是 framework-dependent 的運維。
10. 常見坑
10.1 不要維持 AnyCPU
VBA/Office 與 COM host 的 bitness 對不上,失敗方式會相當奇怪。
- 64bit Office 用
x64/win-x64 - 32bit Office 用
x86/win-x86
10.2 不要用 ClassInterfaceType.AutoDual
看起來很方便,但公開後動到成員順序或結構就容易弄壞。
要讓 VBA 穩定使用帶型別的 API,標準做法仍是:明確定義介面,類別用 ClassInterfaceType.None。
10.3 不要隨便重新產 GUID
在 COM 裡,GUID 就是契約:
- IID
- CLSID
公開之後隨便換,既有 VBA 參考與註冊就會壞。
10.4 不要破壞公開介面
COM 世界裡「只是加一個方法」有時候也會出事:
- 保留
ICalculator - 變動大時新增
ICalculator2 - 類別同時實作兩者也可以
10.5 型別選偏「樸素」一點
VBA 看得到的邊界,型別不要太花。
比較好相處的是:
intdoubleboolstringDateTimedecimalenum
10.6 Office 開著別做更新
Excel 或 Access 會把 DLL 抓住,造成編譯或重新註冊困難。
- 關掉 Office
- 需要時先解除註冊
- 重建
- 再註冊一次
11. 總結
把「.NET 8 DLL 在 VBA 裡帶型別使用」這件事收斂成 COM 發布 + dscom 產 TLB,要做的事其實有條理:
- .NET 8 端用
EnableComHosting=true - 為 COM 設計 明確介面
- 類別用
ClassInterfaceType.None - 給 VBA 的介面用
InterfaceIsDual - 用
dscom tlbexport產生 TLB - 用
regsvr32註冊*.comhost.dll - 用
dscom tlbregister註冊*.tlb - VBA 做參考設定後以 early binding 使用
簡單來說,.NET 8 時代的 VBA 整合,要把 COM host 與 TLB 分開想:
- 啟動入口是
*.comhost.dll - 型別資訊是
*.tlb - 實作本體是
*.dll
12. 參考資料
- Expose .NET components to COM - Microsoft Learn
- Qualify .NET types for interoperation - Microsoft Learn
- ComInterfaceType enumeration - Microsoft Learn
- ClassInterfaceType enumeration - Microsoft Learn
- COM callable wrapper - Microsoft Learn
- DispIdAttribute class - Microsoft Learn
- dscom - NuGet Gallery
- How to use the Regsvr32 tool and troubleshoot Regsvr32 error messages - Microsoft Support
- .NET 8 downloads
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
C# 操作 Excel 時 EXCEL.EXE 殘留的問題 ── COM 參照釋放模式與替換判斷
本文從 COM 參照計數與 RCW 的運作原理,整理 C# 透過 Microsoft.Office.Interop.Excel 操作 Excel 時 EXCEL.EXE 進程殘留的問題。內容涵蓋「兩個點規則」的陷阱、Marshal.ReleaseComObject 派與 G...
Windows 的行程間通訊該怎麼選 ── 具名管道 / TCP / gRPC / 共享記憶體 / COM 判斷表
整理 Windows 應用程式之間該如何選擇溝通方式。以判斷表整理具名管道、本機 TCP、gRPC、共享記憶體、檔案協作、COM 各自的強項與陷阱,並從實務角度說明 UI+服務分離・32bit/64bit 橋接・權限邊界等典型架構,以及具名管道的實作範例。
用 Power Automate 自動化業務 ── 雲端流程與桌面流程的分工,以及錯誤處理設計
本文整理 Power Automate 雲端流程與桌面流程的差異、與 PowerShell / VBA 的分工、授權、錯誤處理、UI 自動化的穩定化,以及認證資訊的安全處理,提供將業務自動化導入正式運作的實務設計指引。
委外・委託開發 Windows 應用程式前該整理的事項
在委外・委託開發 Windows 應用程式之前,整理既有軟體改版、設備整合、COM/ActiveX、發布與更新、維護等應留意的重點。
Office 2024/Microsoft 365 中 ActiveX 無法運作的原因與檢查步驟
說明在 Office 2024/Microsoft 365 中 ActiveX 無法運作時,依序排查預設停用、32bit/64bit、COM 註冊、相依 DLL、IE 模式、Click-to-Run 記錄的順序。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
ActiveX 遷移
整理保留、包裝或替換 COM / ActiveX / OCX 資產的階段性判斷的主題頁面。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
Windows 應用程式開發
支援包含常駐處理、設備連動、運作日誌與可維護結構的 Windows 桌面應用程式。
既有資產活用 & 遷移支援
在持續活用 COM / ActiveX / OCX 資產、原生程式碼與 32 位元相依的同時,協助規劃階段性的遷移。
常見問題
整理諮詢這個主題時常見的問題。
- 從 VBA 可以直接呼叫 .NET 8 的 DLL 嗎?
- 不能直接呼叫,需要把 .NET 8 類別庫以 COM 形式發布。做法是在專案設定 EnableComHosting=true 編譯,產生 *.comhost.dll 作為 COM 的啟動入口,再用 dscom 從 DLL 產生 type library(TLB)。VBA 透過參考設定載入 TLB 之後,就能以 early binding 帶型別使用,獲得 IntelliSense 與執行前的方法名檢查。
- dscom 是什麼?為什麼需要它?
- dscom 是產生 type library 的 dotnet 工具,可用 dotnet tool install --global dscom 安裝。VBA 要做 early binding 必須有型別資訊,而 .NET 8 的 COM host 不會自動產生 TLB。用 dscom tlbexport 從編譯後的 DLL 產出 .tlb,再用 dscom tlbregister 註冊之後,VBA 才能在參考設定中選用。若對象是 32bit Office,產 TLB 要改用 dscom32.exe 比較保險。
- 出現「無法建立 ActiveX 元件」錯誤時該檢查什麼?
- 最常見的原因是 Office/VBA 與 COM 伺服器的 bitness 沒有對齊。64bit Office 要以 x64/win-x64 建置並用 System32 的 regsvr32 註冊;32bit Office 則要以 x86/win-x86 建置並用 SysWOW64 的 regsvr32 註冊。.NET 5+ 的 COM host 在 AnyCPU 時會偏向 64bit,遇到 32bit Office 常常不對盤,因此請依 Office 明確指定 x86 或 x64。
- 發布給其他電腦時需要哪些檔案?
- 不要只發布 DLL,而是把輸出整包放上去:實作本體 DLL、comhost.dll、deps.json、runtimeconfig.json、TLB,必要時再加相依 DLL。用戶端還需要安裝對應版本的 .NET 8 執行時期,因為 COM host 不走 self-contained,基本上是 framework-dependent 的運維。另外若之後更改檔案擺放位置,必須重新註冊。
作者檔案
本文作者的個人檔案頁面。
Go Komura
小村軟體有限公司 代表
以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。