让 VBA 以带类型的方式调用 .NET 8 DLL 的方法 - COM 公开与 dscom TLB

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

更新记录(2 条,最后更新 2026年09月03日)

本文的修改记录。已保存的更新前版本,可通过带有 DOI 的永久链接阅读。

本文此前是日文原文的节译,缺少大量章节、表格、Mermaid 图、图题、脚注与 FAQ。现已改写为日文原文的完整译文,技术主张与日文版一致,并补上了此前缺失的图与表,同时统一了全篇的术语译法。 查看更新前的版本 (DOI: 10.5281/zenodo.22276832)
补充了日文原文中已有的咨询引导(consultation_services)。正文内容没有改动。 查看更新前的版本 (DOI: 10.5281/zenodo.21615485)
首次发布
引用本文(DOI: 10.5281/zenodo.21615484)

本文保存于 Zenodo。以下同时提供始终指向最新版本的 DOI,以及固定于您正在阅读版本的 DOI。

小村 豪(2026)。《让 VBA 以带类型的方式调用 .NET 8 DLL 的方法 - COM 公开与 dscom TLB》。小村软件有限公司。https://doi.org/10.5281/zenodo.21615484 https://comcomponent.com/zh-CN/blog/2026/03/16/007-dotnet8-dll-typed-vba-com-dscom-tlb/

DOI(最新版本)
10.5281/zenodo.21615484
DOI(此版本)
10.5281/zenodo.22282002

希望从 VBA 调用 .NET 8 处理逻辑的场景依然很常见。尤其是在想保留 Excel 或 Access 的既有资产的同时,只把繁重处理、字符串处理、HTTP、加密、业务逻辑这类部分转移到 C# 中的情况下。

不过,如果依赖 CreateObject 采用延迟绑定,VBA 一侧就会到处都是 Object。IntelliSense 的效果会变弱,方法名的拼写错误要到运行时才会暴露,逐渐陷入依赖字符串硬凑的困境。

延迟绑定的困境展示依赖 CreateObject 采用延迟绑定后,VBA 一侧到处都是 Object,IntelliSense 效果变弱,方法名拼写错误要到运行时才会暴露的示意图。用 CreateObject 做延迟绑定VBA 一侧到处都是 ObjectIntelliSense 效果变弱拼写错误要到运行时才暴露

图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)

所需环境

项目 需要准备的内容
操作系统 Windows。由于要进行 COM 注册,需要能以管理员权限运行 regsvr32
.NET SDK .NET 8 SDK。EnableComHosting 是 .NET 5 之后才提供的功能
Office Excel 或 Access。先确认使用的是 32bit 版还是 64bit 版(第 3 章)
TLB 生成工具 dscom。64bit 用和 32bit 用的获取方式不同(第 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. 首先要确定的事 - 统一 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 会更安全。

放任 AnyCPU 导致的不匹配展示保持 AnyCPU 时 comhost 往往偏向 64bit,可能与 32bit Office 不匹配,因此按照 Office 明确指定 x86 或 x64 更安全的示意图。保持 AnyCPUcomhost 往往偏向 64bit与 32bit Office 不匹配按照 Office 明确指定位数

图4:位数不一致是通往“不能创建对象”的捷径。

本文的代码以 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,不依赖自动生成的类接口
  • 为了方便 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 64bit 的情况

如果只需要生成 64bit 的 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 面向 32bit Office 的情况 - 从哪里获取 dscom32.exe

这里是适配 32bit Office 时最容易卡住的地方。

通过 dotnet tool install 安装的 dscom 只能处理 AnyCPU 或 64bit 的程序集,能生成的也只有 64bit 的 TLB。 要生成 32bit 的 TLB,需要另一个可执行文件 dscom32.exe,而它不在 NuGet 上,需要从 GitHub 的发布页面下载。

下载得到的 dscom32.exe,在本文的示例中放在项目根目录下的 tools 文件夹里。放在哪里都可以,但请不要和构建输出一起分发。它是开发阶段的工具,运行时并不需要。

还有一个容易被忽略的前提。要运行 dscom32.exe,必须安装 x86 版的 .NET 运行时。 这是因为 dscom 需要加载 hostfxr.dll,在只安装了 x64 版的环境中无法运行。请在 dotnet --info 的输出列表中确认是否存在 x86 的运行时。

生成 32bit TLB 的准备展示用 dotnet tool 安装的 dscom 只能生成 64bit TLB,因此 32bit TLB 要用从 GitHub 发布页面获取的 dscom32.exe 生成,而运行它需要 x86 版 .NET 运行时的示意图。需要 32bit 的 TLB从发布页面获取 dscom32.exe确认是否有 x86 版运行时用 dscom32.exe 执行 tlbexportdotnet tool 版只能生成 64bit TLB

图7:适配 32bit 时连工具的获取途径都不一样,所以这里最容易卡住。

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

另外,dscom 自己的文档中也推荐:如果保持 AnyCPU,*.comhost.dll 会被生成为 64bit,因此要在 32bit 环境下使用时,应该把程序集本身按 32bit 编译。这和第 3 章的结论是一致的。

如果觉得每次编译都手动敲命令很麻烦,可以引入 dSPACE.Runtime.InteropServices.BuildTasks 包,在编译时自动生成 TLB。

7. 注册 COM host 和 TLB

请在具有管理员权限的命令提示符 / 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"

这里进行的操作有 2 个:

  • 用 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 章的 bitness 不一致,或者第 7 章的 tlbregister 没有执行成功。从 32bit Office 中看不到以 64bit 注册的 TLB。

引用列表中不显示时的排查展示引用列表中看不到该库时,原因大多是 bitness 不一致或 tlbregister 未成功,并且从 32bit Office 看不到以 64bit 注册的 TLB 的示意图。列表中不显示该库怀疑第 3 章的 bitness 不一致怀疑第 7 章的 tlbregister 失败32bit Office 看不到 64bit 的 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 注册、运行时启动、参数与返回值的封送处理全都跑通了。反过来,如果这里的值不对,就要怀疑 .NET 一侧的实现;如果根本无法执行,则要怀疑第 3 章和第 7 章。

用执行结果做排查展示示例的执行结果符合预期时说明从引用设置到封送处理全部跑通,值不对时怀疑 .NET 一侧的实现,根本无法执行时怀疑第 3 章的 bitness 和第 7 章注册的排查思路示意图。符合预期值不对无法执行执行 VBA 示例结果如何整条路径都已跑通怀疑 .NET 一侧的实现怀疑位数与注册

图10:仅凭这 3 行输出,就能确定应该怀疑哪一层。

这样一来,VBA 一侧就能获得以下好处:

  • IntelliSense 可以正常发挥作用
  • 方法名的拼写错误在执行前就容易被发现
  • 可以在 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 存放该 HRESULT 的有符号 Long 值,Err.Description 存放经由 IErrorInfo 传来的异常消息的示意图。.NET 一侧抛出异常在 COM 边界转换为 HRESULT以有符号形式存入 Err.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)

此外,客户端 PC 还需要安装对应版本的 .NET 8 运行时。COM host 并不是 self-contained 发布,基本上属于 framework-dependent 的运维方式。

具体要安装的内容如下。

  • 下载页面是 .NET 8 的下载页
  • 需要的不是 SDK,而是运行时。本文的示例是没有界面的类库,所以 .NET Runtime 就够了。如果要使用 WPF 或 Windows 窗体的类型,则需要 .NET Desktop Runtime
  • 位数要与 Office 保持一致。 64bit Office 用 x64,32bit Office 用 x86 的运行时。理由和第 3 章明确指定 x64 / x86 一样,这里不匹配同样启动不了
  • 是否已经安装,可以在客户端 PC 上执行 dotnet --list-runtimes,看有没有 Microsoft.NETCore.App 8.x 这一行

请务必在分发资料中写清楚“所需运行时的种类、版本和位数”。如果在部署步骤里漏写,到了现场就会看着“ActiveX 组件不能创建对象。”去怀疑位数,白白耗费时间。

分发时需要备齐的东西展示分发时不是只放单个 DLL 而要放整套输出文件,客户端 PC 要安装与 Office 位数相同的 .NET 8 运行时,并在分发资料中写明所需运行时的种类、版本和位数的示意图。部署整套输出文件能在客户端运行位数相同的 .NET 8 运行时分发资料中写明运行时信息

图12:只有单个 DLL 跑不起来,整套文件与运行时齐备了才能运行。

10. 容易踩的坑

10.1 不要一直保持 AnyCPU

如果 VBA / Office 的 bitness 与 COM host 的 bitness 不一致,会出现相当令人费解的失败方式。

  • 64bit Office 应使用 x64 / win-x64
  • 32bit 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
  • 如有必要,先取消注册
  • 重新编译
  • 再次注册

取消注册要按照与注册时相反的顺序,并使用与注册时位数相同的命令。和注册时一样,同样需要管理员权限。

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

dscom tlbunregister "$out\VbaTypedComSample.tlb"
C:\Windows\System32\regsvr32.exe /u "$out\VbaTypedComSample.comhost.dll"
# 32bit Office(运行在 64bit 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 是无法取消注册的(以 64bit 注册的内容,无法用 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 服务器的 bitness 不一致。如果是 64bit Office,需要用 x64/win-x64 编译并通过 System32 下的 regsvr32 注册;如果是 32bit Office(运行在 64bit Windows 上),则需要用 x86/win-x86 编译并通过 SysWOW64 下的 regsvr32 注册,生成 TLB 时也要用 dscom32.exe。在 .NET 5+ 的 COM host 中,如果保持 AnyCPU,*.comhost.dll 往往会偏向 64bit,可能与 32bit 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 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。

返回博客列表