從 VBA 帶型別呼叫 .NET 8 DLL 的方法 - COM 公開與 dscom TLB

· 更新日期: · · C#, .NET 8, VBA, COM, Office, dscom

更新紀錄(2 筆,最後更新 2026年09月04日)

本文的修改紀錄。已保存的更新前版本,可透過附有 DOI 的永久連結閱讀。

已將繁體中文版改寫為日文原文的完整翻譯。先前的繁體中文版只譯出日文原文的一部分,遺漏了章節、表格、Mermaid 圖、圖說與 FAQ。本次依日文原文將這些內容全部補回,並新增本文的知識地圖章節。技術主張與日文版一致。 查看更新前的版本 (DOI: 10.5281/zenodo.22279418)
補上了日文原文中已有的諮詢引導(consultation_services)。內文沒有改動。 查看更新前的版本 (DOI: 10.5281/zenodo.21616305)
初次發布
引用本文(DOI: 10.5281/zenodo.21616304)

本文保存於 Zenodo。以下同時提供一律指向最新版本的 DOI,以及固定於您正在閱讀版本的 DOI。

Go Komura(2026)。〈從 VBA 帶型別呼叫 .NET 8 DLL 的方法 - COM 公開與 dscom TLB〉。小村軟體有限公司。https://doi.org/10.5281/zenodo.21616304 https://comcomponent.com/zh-TW/blog/2026/03/16/007-dotnet8-dll-typed-vba-com-dscom-tlb/

DOI(最新版本)
10.5281/zenodo.21616304
DOI(此版本)
10.5281/zenodo.22297142

想從 VBA 呼叫 .NET 8 的處理,這種場面現在仍然很常見。尤其是想把 Excel 或 Access 的既有資產原樣留著,只把繁重的運算、字串處理、HTTP、加密、業務邏輯這類部分抽離到 C# 的時候。

不過,只要用 CreateObject 往晚期繫結靠,VBA 這邊就會滿是 Object。IntelliSense 會變弱,方法名稱打錯要到執行階段才發現,最後慢慢沉進靠字串硬撐的泥濘裡。

晚期繫結的泥濘說明用 CreateObject 往晚期繫結靠之後 VBA 這邊會滿是 Object,IntelliSense 變弱,方法名稱打錯要到執行階段才發現的圖。用 CreateObject 做晚期繫結VBA 這邊滿是 ObjectIntelliSense 變弱typo 到執行階段才發現

圖 1: 越往晚期繫結靠,就越會沉進靠字串硬撐的泥濘裡。

所以這次把範圍收斂到:把 .NET 8 的 DLL 以 COM 公開,用 dscom 產生型別程式庫(TLB),再從 VBA 以早期繫結帶型別使用。

.NET Framework + RegAsm 的舊時代、手寫 IDL 再用 MIDL 編起來的做法、Reg-Free COM,這次都先放到一邊。這裡只走 .NET 8 / COM host / dscom / VBA early binding 這一條直線。

另外,本文出現的程式碼,已經整理成可以建置、可以驗證的完整範例(COM 公開的函式庫、TLB 產生與註冊指令碼、VBA 模組、單元測試),公開在 GitHub 上。

dotnet8-dll-typed-vba-com-dscom-tlb - komurasoft-blog-samples (GitHub)

需要的環境

項目 需要什麼
OS Windows。因為要做 COM 註冊,必須能以系統管理員權限執行 regsvr32
.NET SDK .NET 8 SDK。EnableComHosting 是 .NET 5 以後才有的功能
Office Excel 或 Access。先查看是 32 位元版還是 64 位元版(第 3 章)
TLB 產生工具 dscom。64 位元用與 32 位元用的取得方式不同(第 6 章)
用戶端電腦 與 Office 相同位元數的 .NET 8 執行階段(第 9 章)

進到步驟之前,強烈建議先把自己環境的版本記下來。之後如果變成「明明照同樣的步驟卻跑不起來」,能拿來比對的資訊就只有這些。

# .NET SDK 與執行階段的清單(也看得出裝的是 x64 還是 x86)
dotnet --info

# Windows 的組建編號
winver

Office 的版本與位元數,可以在 Excel 的 檔案 > 帳戶 > 關於 Excel 查看。對話方塊標題列的結尾會顯示 32 位元 或 64 位元。

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 做早期繫結。

走到帶型別使用的一條直線說明以 EnableComHosting 建置,用 dscom tlbexport 做出 TLB,用 regsvr32 註冊 comhost、用 dscom tlbregister 註冊 TLB,最後在 VBA 加入引用項目帶型別使用這段流程的圖。以 EnableComHosting 建置用 dscom tlbexport 產生 TLB用 regsvr32 註冊 comhost用 dscom tlbregister 註冊 TLB在 VBA 加入引用項目並帶型別使用

圖 2: 依照建置、產生 TLB、兩次註冊、加入引用項目的順序走,就能帶型別呼叫。

圖中實線表示始終成立的關係,虛線表示附帶條件的關係(成立條件寫在詳細頁面中各關係的說明中)。關係的完整清單(共 27 條,附依據與可信度)以及主要概念的定義,彙整在知識地圖詳細頁面(日文)。資料:JSON-LD / Turtle

2. 這個結構的全貌

先用一張圖看清楚各個檔案分別負責什麼。

以加入引用項目的 TLB 取得型別資訊COM 呼叫VBA / Excel / AccessVbaTypedComSample.tlbVbaTypedComSample.comhost.dllVbaTypedComSample.dll (.NET 8).NET 8 Runtime

圖 3: VBA 從 TLB 取得型別資訊,再經由 comhost 呼叫 .NET 8 的實作本體。

各自的角色如下。

檔案 角色
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. 一開始就要決定的事 - 對齊 32 位元 / 64 位元

這裡弄錯,很高的機率會滾向 ActiveX 元件無法建立物件 那個方向。

請把 Office / VBA 與 COM 伺服器的位元數(bitness)對齊。

使用端 .NET 端的判斷基準 TLB 產生 註冊指令
64 位元 Office x64 / win-x64 dscom C:\Windows\System32\regsvr32.exe
32 位元 Office(在 64 位元 Windows 上) x86 / win-x86 dscom32.exe C:\Windows\SysWOW64\regsvr32.exe

.NET 5+ 之後的 COM host,如果放著 AnyCPU 不管,*.comhost.dll 容易偏到 64 位元那一側,有時候就跟 32 位元 Office 對不上。所以依照 Office 明確指定 x86 / x64 會比較安全。

放著 AnyCPU 不管招來的不一致說明放著 AnyCPU 不管時 comhost 容易偏到 64 位元那一側而與 32 位元 Office 對不上,因此依照 Office 明確指定 x86 或 x64 比較安全的圖。放著 AnyCPU 不管comhost 容易偏到 64 位元那一側與 32 位元 Office 對不上依照 Office 明確指定位元數

圖 4: 位元數的落差,是通往「無法建立物件」的捷徑。

本文的程式碼以 64 位元 Office 為例。如果是 32 位元 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,不依賴自動產生的類別介面
  • 為了讓 VBA 好用,設成 InterfaceIsDual
  • 先標上 DispId,公開之後調動方法順序時比較不容易出事
  • 因為會由 COM 端 New 出來,要準備公開且無引數的建構函式
要公開的型別怎麼組說明明確介面 ICalculator 加上 InterfaceIsDual 與 DispId,類別 Calculator 以 ClassInterfaceType.None 實作,Guid 對介面與類別分別指派的結構圖。實作ICalculator〔明確介面〕InterfaceIsDual 與 DispIdCalculator〔類別〕ClassInterfaceType.NoneGuid 各自分開指派

圖 5: 帶型別公開的關鍵,是明確介面與指定 None 的類別這個組合。

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 dscom 是什麼

dscom 是一套開放原始碼的命令列工具,用來從 .NET 組件產生並註冊 COM 的型別程式庫(TLB)。它由 dSPACE 公司公開,授權是 Apache-2.0。

之所以需要它,是因為.NET 5 以後 tlbexp.exe 與 RegAsm.exe 都已淘汰。.NET Framework 的年代,用這兩個就能產生 TLB 並註冊組件,但 .NET 5+ 並沒有內建後繼工具。dscom 就是為了補上這個缺口而做出來的工具。

dscom 補上的缺口說明 .NET Framework 年代可以用 tlbexp.exe 與 RegAsm.exe 產生 TLB 並註冊組件,但 .NET 5 以後兩者都已淘汰且沒有內建後繼,因此由 dscom 補上這個缺口的圖。.NET Framework 年代tlbexp.exe 與 RegAsm.exe.NET 5 以後兩者都淘汰且沒有後繼由 dscom 補上缺口

圖 6: 產生 TLB 的工具,在 .NET 5 以後換成了 dscom。

主要的子命令,記住這些就夠了。

子命令 作用
tlbexport 從組件輸出 TLB
tlbregister 把 TLB 註冊到系統
tlbunregister 取消 TLB 的註冊
tlbdump 輸出 TLB 的內容以供查看
tlbembed 把 TLB 內嵌到檔案裡

tlbdump 很方便,可以在打開 VBA 之前,先檢查產生出來的 TLB 裡有沒有預期的型別。

6.2 64 位元的情況

只是要做 64 位元的 TLB 的話,用 dotnet tool 就能裝。

dotnet tool install --global dscom

接著,從建置好的組件產生 TLB。

dscom tlbexport .\bin\Release\net8.0-windows\VbaTypedComSample.dll --out .\bin\Release\net8.0-windows\VbaTypedComSample.tlb

6.3 給 32 位元 Office 的情況 - dscom32.exe 要從哪裡取得

這裡是支援 32 位元 Office 時最容易卡住的地方。

用 dotnet tool install 裝到的 dscom,只能處理 AnyCPU 或 64 位元的組件,能產生的也只有 64 位元的 TLB。 要做 32 位元的 TLB,需要另一個執行檔 dscom32.exe,而且它不在 NuGet 上,要從 GitHub 的發行頁面下載。

下載回來的 dscom32.exe,在本文的例子裡放在專案根目錄下的 tools 資料夾。放哪裡都可以,但請不要跟建置輸出一起散發。它是開發時用的工具,執行階段並不需要。

還有一個容易漏掉的前提。要跑 dscom32.exe,機器上必須裝有 x86 版的 .NET 執行階段。 這是因為 dscom 需要載入 hostfxr.dll,只裝了 x64 版的環境跑不起來。請在 dotnet --info 輸出的清單裡,查看有沒有 x86 的執行階段。

產生 32 位元 TLB 的準備說明用 dotnet tool 裝到的 dscom 只能做出 64 位元的 TLB,因此 32 位元的 TLB 要用從 GitHub 發行頁面取得的 dscom32.exe 產生,而且要跑它必須裝有 x86 版的 .NET 執行階段的圖。需要 32 位元的 TLB從發行頁面取得 dscom32.exe查看有沒有 x86 版執行階段用 dscom32.exe 執行 tlbexportdotnet tool 版只做得出 64 位元 TLB

圖 7: 32 位元的支援連工具的取得管道都不一樣,所以這裡特別容易卡住。

.\tools\dscom32.exe tlbexport .\bin\Release\net8.0-windows\VbaTypedComSample.dll --out .\bin\Release\net8.0-windows\VbaTypedComSample.tlb

另外,dscom 自己的文件也建議:放著 AnyCPU 不管的話 *.comhost.dll 會以 64 位元產生,所以要在 32 位元下使用,組件本身就要以 32 位元編譯。這和第 3 章的結論一樣。

如果嫌每次建置都要手動敲一次麻煩,裝上 dSPACE.Runtime.InteropServices.BuildTasks 套件,就能在編譯時自動產生 TLB。

7. 註冊 COM host 與 TLB

這一段請在系統管理員權限的命令提示字元 / PowerShell 下執行。

7.1 64 位元 Office / 64 位元 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 32 位元 Office(在 64 位元 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 註冊成型別程式庫
註冊要分成兩路說明在系統管理員權限下,regsvr32 把 comhost 註冊成 COM 伺服器,dscom tlbregister 把 TLB 註冊成型別程式庫這兩個註冊動作的圖。以系統管理員權限執行用 regsvr32 註冊 comhost用 tlbregister 註冊 TLB註冊 COM 的啟動入口註冊 VBA 要看的型別資訊

圖 8: 啟動入口與型別資訊是兩回事,所以註冊也要兩個湊成一組。

8. 在 VBA 加入引用項目,帶型別使用

  1. 打開 Excel 或 Access
  2. 用 Alt + F11 打開 VBA 編輯器(VBE)。要從功能區開的話,是「開發人員」索引標籤 >「Visual Basic」。如果沒看到「開發人員」索引標籤,就到 檔案 > 選項 > 自訂功能區,把「開發人員」勾起來
  3. 在 VBE 的功能表選 工具 > 設定引用項目
  4. 「可使用的引用項目」清單會依英文字母順序排列。註冊成功的話,裡面會出現程式庫名稱(預設與組件名稱相同,也就是 VbaTypedComSample),把左邊的核取方塊勾起來後按 確定
  5. 如果清單裡看不到,就從 瀏覽... 按鈕直接選 VbaTypedComSample.tlb

清單裡沒出現的話,原因大多是第 3 章的位元數不一致,或第 7 章的 tlbregister 沒跑成功這兩者之一。從 32 位元 Office 看不到以 64 位元註冊的 TLB。

引用項目清單裡沒出現時怎麼縮小範圍說明設定引用項目的清單裡沒出現程式庫時,原因大多是位元數不一致或 tlbregister 沒跑成功,而且從 32 位元 Office 看不到以 64 位元註冊的 TLB 的圖。清單裡沒有出現程式庫先懷疑第 3 章的位元數不一致再懷疑第 7 章的 tlbregister 失敗從 32 位元 Office 看不到 64 位元的 TLB

圖 9: 沒出現在清單裡的原因,幾乎可以縮小到位元數或註冊這兩者。

引用項目有沒有加進去,可以打開 檢視 > 物件瀏覽器(F2),看左上角的程式庫選單裡能不能選到 VbaTypedComSample 來查看。如果在這裡看得到 ICalculator 與 Calculator,以及 Add / Divide / Hello,就表示 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

把游標放在這個程序上按 F5 執行,再用 Ctrl + G 打開即時運算視窗,就會照第 4 章的實作輸出 3 行。

30
2.5
Hello, VBA

這裡如果符合預期,就表示引用項目、COM 註冊、執行階段啟動、引數與傳回值的封送處理(marshalling)整條路都通了。反過來說,如果值對不上就懷疑 .NET 端的實作,如果根本跑不起來就懷疑第 3 章與第 7 章。

用執行結果縮小範圍說明範例的執行結果如果符合預期,就代表從引用項目到封送處理整條路都通了,值對不上要懷疑 .NET 端的實作,根本跑不起來則要懷疑第 3 章的位元數與第 7 章的註冊的圖。符合預期值對不上跑不起來執行 VBA 的範例結果如何整條路都通了懷疑 .NET 端的實作懷疑位元數與註冊

圖 10: 光靠這 3 行輸出,就能決定該懷疑哪一層。

這樣一來,VBA 這邊有這些好處。

  • IntelliSense 會生效
  • 方法名稱的 typo 比較容易在執行前發現
  • 可以用 Object Browser 查看公開的 API
  • 比整片硬寫 Object 好讀

8.1 例外在 VBA 端會變成 COM 錯誤

例如 Divide(10, 0) 這種在 .NET 端拋出例外的情況,在 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 Hex$(Err.Number)
    Debug.Print Err.Description
End Sub

先弄清楚這裡出現的值怎麼讀,縮小範圍會快很多。

項目 裡面會有什麼
Err.Number 會放入對應 .NET 例外的 HRESULT,形式是帶正負號的 Long。維持十進位不好讀,用 Hex$(Err.Number) 轉成十六進位
Err.Description 經由 COM 的 IErrorInfo,直接放入 .NET 的例外訊息。以上面的程式碼來說,是包含 不能除以 0。 的字串

HRESULT 的值依例外的型別而定。對應 ArgumentOutOfRangeException 的是 COR_E_ARGUMENTOUTOFRANGE,值為 0x80131502。也就是說,Hex$(Err.Number) 如果是 80131502,就表示如預期收到了 .NET 端的 ArgumentOutOfRangeException。

主要的幾個列出來如下。

.NET 的例外 HRESULT 常數 值
ArgumentException COR_E_ARGUMENT 0x80070057
ArgumentOutOfRangeException COR_E_ARGUMENTOUTOFRANGE 0x80131502
InvalidOperationException COR_E_INVALIDOPERATION 0x80131509
NotSupportedException COR_E_NOTSUPPORTED 0x80131515
上述以外的一般例外 COR_E_EXCEPTION 0x80131500

如果想在 VBA 端依例外種類分開處理,就得靠這個 HRESULT 做分支。不過,用 HRESULT 做分支的設計,禁不起 .NET 端例外型別的變動,所以業務上的失敗與其用例外,不如用傳回值或錯誤碼帶回去,作為邊界會穩定得多。

.NET 例外送到 VBA 的過程說明 .NET 端拋出的例外會在 COM 邊界轉換成 HRESULT,在 VBA 端 Err.Number 會放入帶正負號的 Long 形式的 HRESULT,Err.Description 則放入經由 IErrorInfo 傳來的例外訊息的圖。.NET 端拋出例外在 COM 邊界轉成 HRESULTErr.Number 以帶正負號的形式放入Err.Description 放入訊息用 Hex$ 轉成十六進位再讀

圖 11: 例外會在 COM 邊界換成 HRESULT 的樣子,送到 VBA 的 Err。

9. 散發時的思考方式

散發時重要的是不要只發一個 DLL,而是把輸出整套放上去。

VbaTypedComSample.dll
VbaTypedComSample.comhost.dll
VbaTypedComSample.deps.json
VbaTypedComSample.runtimeconfig.json
VbaTypedComSample.tlb
(必要時再加上整套相依 DLL)

而且,用戶端電腦上還需要對應的 .NET 8 執行階段。COM host 不是 self-contained 的散發方式,基本上是 framework-dependent 的維運。

要裝的東西具體來說如下。

  • 下載頁面是 .NET 8 的下載頁
  • 需要的不是 SDK,而是執行階段。本文的範例是沒有畫面的類別庫,所以 .NET Runtime 就夠了。如果要用到 WPF 或 Windows Forms 的型別,就需要 .NET Desktop Runtime
  • 位元數要跟 Office 對齊。 64 位元 Office 就是 x64、32 位元 Office 就是 x86 的執行階段。和第 3 章明確指定 x64 / x86 是同樣的理由,這裡對不上一樣起不來
  • 要知道裝好了沒有,可以在用戶端電腦執行 dotnet --list-runtimes,看有沒有 Microsoft.NETCore.App 8.x 這一行

散發說明文件裡,一定要寫上這個「需要的執行階段種類、版本、位元數」。安裝步驟裡漏寫的話,到了現場看到 ActiveX 元件無法建立物件 才回頭懷疑位元數,時間就這樣燒掉了。

散發時要湊齊的東西說明散發時不是只放一個 DLL 而是放上輸出整套,用戶端電腦要裝與 Office 相同位元數的 .NET 8 執行階段,並且要在散發說明文件裡寫明需要的執行階段種類、版本與位元數的圖。放上輸出整套在用戶端能跑起來相同位元數的 .NET 8 執行階段散發說明文件寫明執行階段資訊

圖 12: 只有 DLL 動不了,一整套加上執行階段湊齊才會動。

10. 常見陷阱

10.1 不要放著 AnyCPU 不管

VBA / Office 的位元數和 COM host 的位元數一錯開,失敗的方式會相當難受。

  • 64 位元 Office 就用 x64 / win-x64
  • 32 位元 Office 就用 x86 / win-x86

10.2 不要用 ClassInterfaceType.AutoDual

乍看很輕鬆,但公開之後只要動到成員順序或組成,就很容易弄壞。

想從 VBA 帶型別穩定使用,正統做法是定義明確介面,類別設成 ClassInterfaceType.None。

10.3 不要輕率地重新產生 GUID

在 COM 裡,GUID 本身就是契約。公開之後輕率地換掉 IID 或 CLSID,既有的 VBA 引用項目與註冊就會壞掉。

10.4 不要破壞已公開的介面

在 COM 裡,就算只是「事後多加一個方法」,也不一定能平安無事。

  • ICalculator 原樣保留
  • 變動很大的話就新增 ICalculator2
  • 類別兩個都實作也可以
已公開介面的守法說明已公開的 ICalculator 原樣保留,變動很大時新增 ICalculator2,類別兩個都實作也可以這種相容性守法的圖。已公開的 ICalculator原樣保留想做很大的變動新增 ICalculator2類別可以兩個都實作

圖 13: 既有契約原樣留著,新的契約橫向加上去,這是 COM 的作風。

10.5 型別要往樸素的方向收攏

在給 VBA 看的邊界上,不要太花俏比較安全。

比較合用的,先看這幾種。

  • int
  • double
  • bool
  • string
  • DateTime
  • decimal
  • enum

10.6 不要在 Office 開著的時候做更新

Excel 或 Access 會一直佔住 DLL,建置或重新註冊時就會出麻煩。

  • 關掉 Office
  • 需要的話先取消註冊
  • 重新建置
  • 再註冊一次

取消註冊要用與註冊時相反的順序,並且用與註冊時相同位元數的指令。同樣需要系統管理員權限,這點和註冊時一樣。

# 64 位元 Office / 64 位元 COM 的情況
$out = Resolve-Path .\bin\Release\net8.0-windows

dscom tlbunregister "$out\VbaTypedComSample.tlb"
C:\Windows\System32\regsvr32.exe /u "$out\VbaTypedComSample.comhost.dll"
# 32 位元 Office(在 64 位元 Windows 上)的情況
$out = Resolve-Path .\bin\Release\net8.0-windows

.\tools\dscom32.exe tlbunregister "$out\VbaTypedComSample.tlb"
C:\Windows\SysWOW64\regsvr32.exe /u "$out\VbaTypedComSample.comhost.dll"

regsvr32 的 /u 就是取消註冊的選項。用了和註冊時不同的 regsvr32 就解不掉(以 64 位元註冊的東西,沒辦法用 SysWOW64 的 regsvr32 移除)。整個資料夾搬走或刪掉之前沒先解除,登錄檔裡就會留下指向不存在路徑的註冊。

更新前後怎麼收拾註冊說明更新時要先關掉 Office,必要時以與註冊相反的順序、相同位元數的指令取消註冊,再重新建置,然後重新註冊一次這段流程的圖。關掉 Office必要時以相反順序取消註冊重新建置再註冊一次用與註冊時相同位元數的指令

圖 14: 為了避免 DLL 被佔住與殘留的註冊,取消與重新註冊要成對進行。

11. 總結

從 VBA 帶型別使用 .NET 8 的 DLL 這件事,只要收斂成以 COM 公開 + 用 dscom 產生 TLB,步驟其實沒那麼可怕。.NET 8 端設成 EnableComHosting=true,準備好明確介面(類別用 ClassInterfaceType.None,給 VBA 的用 InterfaceIsDual),用 dscom tlbexport 做出 TLB,再用 regsvr32 註冊 *.comhost.dll、用 dscom tlbregister 註冊 *.tlb。剩下就只是在 VBA 加入引用項目,做早期繫結而已。

拿不定主意的時候,訣竅是把 COM host 與 TLB 分開來想。

  • 啟動入口是 *.comhost.dll
  • 型別資訊是 *.tlb
  • 實作本體是 *.dll

12. 參考資料

共用相同標籤的最新文章。能以相近的主題延伸理解。

與本文相近的主題頁面。以本文為起點,可進一步連到相關服務與其他文章。

本文連結到以下服務頁面,歡迎從最接近的入口查看。

常見問題

整理諮詢這個主題時常見的問題。

要從 VBA 帶型別(早期繫結)使用 .NET 8 的 DLL,需要準備什麼?
先把 .NET 8 類別庫以 EnableComHosting=true 建置,產生 *.comhost.dll,再用 dscom tlbexport 做出 *.tlb。接著用 regsvr32 註冊 *.comhost.dll、用 dscom tlbregister 註冊 *.tlb,然後在 VBA 的設定引用項目裡加入那個 TLB,就能以 Dim x As 程式庫名稱.IYourInterface 的形式帶型別使用。以職責來分:COM 的啟動入口是 *.comhost.dll,VBA 看的型別資訊是 *.tlb,實作本體是 *.dll。
出現「ActiveX 元件無法建立物件」的原因是什麼?
典型原因是 Office/VBA 與 COM 伺服器的位元數不一致。64 位元 Office 就以 x64/win-x64 建置並用 System32 的 regsvr32 註冊;32 位元 Office(在 64 位元 Windows 上)則以 x86/win-x86 建置並用 SysWOW64 的 regsvr32 註冊,產生 TLB 也要改用 dscom32.exe。.NET 5+ 的 COM host 如果放著 AnyCPU 不管,*.comhost.dll 容易偏到 64 位元那一側,有時候就跟 32 位元 Office 對不上,所以依照 Office 明確指定 x86/x64 比較安全。
不可以使用 ClassInterfaceType.AutoDual 嗎?
乍看很輕鬆,但公開之後只要動到成員順序或組成就很容易弄壞,應該避免。想從 VBA 帶型別穩定使用,正統做法是定義明確的介面、類別設成 ClassInterfaceType.None,而給 VBA 用的介面設成 InterfaceIsDual。先標上 DispId,可以減少調動方法順序時出事的機會。另外在 COM 裡 GUID 本身就是契約,公開之後輕率地重新產生 IID 或 CLSID,既有的 VBA 引用項目與註冊就會壞掉。
散發的時候只給 DLL 就好了嗎?
只有 DLL 不會動。要把實作本體的 *.dll、*.comhost.dll、*.deps.json、*.runtimeconfig.json、*.tlb,必要時再加上整套相依 DLL 一起放上去。而且用戶端電腦上還需要對應的 .NET 8 執行階段,COM host 不是 self-contained 的散發方式,基本上是 framework-dependent 的維運。還要注意,之後如果改變擺放位置,註冊也要重做。

作者檔案

本文作者的個人檔案頁面。

Go Komura

小村軟體有限公司 代表

以 Windows 軟體開發、技術諮詢與故障調查為中心,在難以重現的故障調查與既有資產仍在運作的專案上具有優勢。

回到部落格一覽