從 VBA 帶型別使用 .NET 8 的 DLL - 以 COM 發布 + dscom 產生 TLB
想從 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
相關文章
共用相同標籤的最新文章。能以相近的主題延伸理解。
什麼是 VBA - 限制、未來走向、該被取代的情境與務實的遷移模式
本文從實務角度釐清 VBA 的本質、桌面前提的限制、Excel for the web 與巨集封鎖、64bit 與伺服器自動化的注意事項,並依職責分派 Office Scripts、Office Add-ins、.NET 外移等替代目標,提示階段性遷移的具體步驟。
Excel 報表輸出該怎麼做 - COM 自動化 / Open XML / 範本方式的判斷表
從 Windows 應用與業務系統的角度,把 Excel 報表輸出拆成驅動 Excel 與組裝 Excel 檔案兩條路。整理 COM 自動化、Open XML 直接生成、範本套版、既有 VBA 併用的取捨,並針對使用者編輯、夜間批次、大量輸出等情境,給出不易壞且容易維運的選...
開發 COM 元件、OCX/ActiveX 時常見的坑 - 整理 Visual Studio 的 32bit/64bit、註冊、管理員權限
整理開發 COM、OCX、ActiveX 元件時最容易卡關的四個面向:宿主行程的 32bit/64bit、Visual Studio 2022 變成 64bit 後的設計時整合、regsvr32 與 Regasm 的註冊位置、以及管理員權限與 HKCU/HKLM 的關係,協...
Windows 應用安全處理子行程的 checklist - Job Object、結束傳播、標準輸入輸出、watchdog 的最佳實務
在 Windows 應用上安全處理子行程,關鍵不在挑啟動 API,而是設計行程樹的擁有者與結束流程。本文整理 Job Object 的 KILL_ON_JOB_CLOSE、GUI 與 console 的 graceful shutdown、stdio 平行抽乾與 EOF、w...
序列通訊應用的陷阱 - 先釐清 1 byte 單位、逾時、流控、重連、USB 轉換、UI 凍結
從設備整合與儀器控制的實作現場出發,整理序列通訊應用最容易踩到的陷阱。把訊息邊界、逾時語意、流控線設定、single writer、session 重連與 hex dump 日誌一一拆開,幫助讀者把「偶爾才壞」的 byte 序列處理改造成可預測且容易調查的結構。
相關主題
與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。
Windows 技術主題
彙整 KomuraSoft LLC 關於 Windows 開發、故障調查與既有資產活用文章的主題中心。
ActiveX 遷移
整理保留、包裝或替換 COM / ActiveX / OCX 資產的階段性判斷的主題頁面。
與本主題相關的服務
本文連結到以下服務頁面,歡迎從最接近的入口查看。
Windows 應用程式開發
支援包含常駐處理、設備連動、運作日誌與可維護結構的 Windows 桌面應用程式。
既有資產活用 & 遷移支援
在持續活用 COM / ActiveX / OCX 資產、原生程式碼與 32 位元相依的同時,協助規劃階段性的遷移。