Как использовать DLL на .NET 8 из VBA с типизацией — публикация COM и TLB через dscom

· Обновлено: · · C#, .NET 8, VBA, COM, Office, dscom

История изменений (1 обновлений, последнее 30 Aug 2026)

Журнал изменений этой статьи. Там, где версия до правки была заархивирована, она остаётся доступной для чтения по постоянной ссылке с DOI.

Русский текст переписан как полноценный технический перевод, а не калька с японского. Утверждения статьи не менялись.
Первая публикация
Цитирование статьи(DOI: 10.5281/zenodo.21619716)

Статья заархивирована на Zenodo. Ниже приведены DOI, который всегда ведёт к последней версии, и DOI, закреплённый за версией, которую вы читаете.

Го Комура (2026). Как использовать DLL на .NET 8 из VBA с типизацией — публикация COM и TLB через dscom. KomuraSoft LLC. https://doi.org/10.5281/zenodo.21619716 https://comcomponent.com/ru/blog/2026/03/16/007-dotnet8-dll-typed-vba-com-dscom-tlb/

DOI (последняя версия)
10.5281/zenodo.21619716
DOI (эта версия)
10.5281/zenodo.21619717

Ситуации, когда из VBA нужно вызвать код на .NET 8, до сих пор встречаются вполне обычно. Особенно когда существующие наработки в Excel или Access хочется оставить как есть, а в C# унести только тяжёлые вычисления, обработку строк, HTTP, криптографию или бизнес-логику.

Но если опереться на позднее связывание через CreateObject, на стороне VBA всё становится Object. IntelliSense слабеет, опечатки в именах методов находятся только во время выполнения, и со временем вы оказываетесь в хрупком коде, который живёт строковыми именами.

Хрупкость позднего связыванияЕсли опереться на позднее связывание через CreateObject, на стороне VBA всё становится Object, IntelliSense слабеет, и опечатки в именах методов находятся только во время выполнения.Позднее связывание через CreateObjectНа стороне VBA сплошной ObjectIntelliSense слабеетОпечатка всплывает только в runtime

Рис. 1: Чем сильнее опираетесь на позднее связывание, тем хрупче становится код из-за строковых имён.

Поэтому в этот раз сузим тему до одного пути: опубликовать DLL на .NET 8 как COM, сгенерировать библиотеку типов (TLB) через dscom и вызывать её из VBA типизированно, ранним связыванием.

Старую историю про .NET Framework + RegAsm, ручное написание IDL и сборку через MIDL, а также Reg-Free COM в этот раз оставляем в стороне. Здесь только одна дорога: .NET 8 / COM host / dscom / VBA early binding.

Код из этой статьи опубликован на GitHub как полный набор примеров, которые можно собрать и проверить (библиотека, публикуемая как COM, скрипты генерации и регистрации TLB, модули VBA, модульные тесты).

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. Сначала выясните, 32-разрядный это выпуск или 64-разрядный (глава 3)
Средство генерации TLB dscom. Для 64-бит и 32-бит способы получения разные (глава 6)
Клиентский ПК Среда выполнения .NET 8 той же разрядности, что и Office (глава 9)

Перед тем как идти по шагам, настоятельно советую записать версии своей среды. Когда позже окажется «те же шаги, а не работает», сравнивать будет не с чем, кроме этих записей.

# Список .NET SDK и runtime (видно, стоят ли x64 и x86)
dotnet --info

# Номер сборки Windows
winver

Разрядность и выпуск Office можно посмотреть в Excel: Файл > Учётная запись > Сведения о Excel. В конце строки заголовка диалога будет 32-разрядная или 64-разрядная.

1. Сначала вывод

Если сразу выложить только вывод, последовательность такая.

  • Собрать библиотеку классов .NET 8 с EnableComHosting=true
  • Сделать явный интерфейс и класс, которые будут видны COM
  • Для класса поставить ClassInterfaceType.None и не уходить в AutoDual
  • Для интерфейса, которым пользуется VBA, поставить InterfaceIsDual
  • Из *.dll, полученного после сборки, сделать *.tlb командой dscom tlbexport
  • Зарегистрировать *.comhost.dll через regsvr32
  • Зарегистрировать *.tlb через dscom tlbregister
  • Добавить ссылку в VBA и вызывать типизированно, например Dim x As ИмяБиблиотеки.IYourInterface

Иными словами, схема такая: точка входа COM — это *.comhost.dll, который делает .NET SDK; информация о типах — это *.tlb, который делает dscom; VBA смотрит на этот TLB и идёт в раннее связывание.

Одна дорога до типизированного вызоваСобираем с EnableComHosting, делаем TLB через dscom tlbexport, регистрируем comhost через regsvr32 и TLB через dscom tlbregister, затем добавляем ссылку в VBA и вызываем типизированно.Сборка с EnableComHostingTLB через dscom tlbexportРегистрация comhost через regsvr32Регистрация TLB через dscom tlbregisterСсылка в VBA и типизированный вызов

Рис. 2: Если идти в порядке сборка → генерация TLB → две регистрации → ссылка, вызов получается типизированным.

Карта знаний этой статьи

Чтобы типизированно использовать библиотеку классов .NET 8 из VBA, необходима библиотека типов COM; её генерирует и регистрирует инструмент dscom — преемник tlbexp.exe и RegAsm.exe, которые в .NET Framework уже недоступны. На стороне .NET точкой входа COM служит COM host, собранный с EnableComHosting; предпосылки — регистрация через regsvr32 и совпадение разрядности с Office. Сторона VBA загружает библиотеку типов в ссылках проекта и получает раннее связывание — это типобезопаснее, чем позднее связывание через CreateObject. Совместимость после публикации зависит от обращения с IID и CLSID и от выбора ClassInterfaceType; сочетание ClassInterfaceType.None, InterfaceIsDual и DispId — установившаяся практика, которая не даёт сломать ссылки VBA.

Карта знаний: типизированное использование DLL .NET 8 из VBAСхема, которая показывает, что для типизированного раннего связывания COM из VBA нужна библиотека типов; что dscom отвечает за её генерацию и регистрацию; что сторона .NET 8 публикуется как COM host; и как ClassInterfaceType и обращение с IID и CLSID влияют на совместимость ссылок VBAтребуеттребуетиспользуетиспользуетне рекомендуетсяреализуетпреемникнастраиваетсянастраиваетсятребуетреализуетиспользуеттребуеттребуетможет вызватьможет вызватьможет вызватьне рекомендуетсярекомендуется дляснижаетрекомендуется дляиспользуетпроверяетсянастраиваетсятребуеттребуеттребуетVBA (Visual Basic for Applications)dscomбиблиотека типов (TLB)раннее связывание (VBA)позднее связывание (CreateObject)tlbexp.exe / RegAsm.exeCOM host (*.comhost.dll)regsvr32.NET (начиная с Core)COM (Component Object Model)IID (идентификатор интерфейса)CLSID (Class ID)повреждение ссылок и регистрации VBAClassInterfaceType.AutoDualClassInterfaceType.NoneDispIdAttributeInterfaceIsDual (dual interface)HRESULTисключение .NETComVisibleAttributeтребование совпадения разрядности

На схеме сплошная линия обозначает отношение, которое выполняется всегда, а пунктирная — условное отношение (условия указаны в пояснении к каждому отношению на странице сведений). Полный список отношений (всего 27, с доказательствами и степенью уверенности) и определения основных понятий собраны на странице сведений карты знаний (на японском). Данные: JSON-LD / Turtle

2. Общая картина этой схемы

Сначала на одной схеме — кто за что отвечает.

Типы из подключённого TLBВызовы COMVBA / Excel / AccessVbaTypedComSample.tlbVbaTypedComSample.comhost.dllVbaTypedComSample.dll (.NET 8).NET 8 Runtime

Рис. 3: VBA берёт информацию о типах из TLB и вызывает реализацию на .NET 8 через comhost.

Роли распределяются так.

Файл Роль
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-сервера должна совпадать.

Сторона использования Ориентир для .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

В COM host начиная с .NET 5+, если оставить проект как AnyCPU, *.comhost.dll склонен собираться в 64-разрядную сторону и может не совпасть с 32-разрядным Office. Поэтому безопаснее явно указать x86 / x64 под установленный Office.

Несовпадение, к которому ведёт AnyCPUЕсли оставить AnyCPU, comhost склонен уезжать в 64-бит и может не совпасть с 32-разрядным Office, поэтому безопаснее явно указать x86 или x64 под Office.Оставить AnyCPUcomhost склонен уехать в 64-битНе совпадает с 32-разрядным OfficeЯвно указать битность под Office

Рис. 4: Расхождение bitness — короткий путь к «невозможно создать объект».

Код в этой статье разбираем на примере 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. Генерируем TLB через dscom

6.1 Что такое dscom

dscom — это открытый инструмент командной строки, который генерирует и регистрирует COM-библиотеку типов (TLB) из сборки .NET. Его публикует компания dSPACE, лицензия — Apache-2.0.

Зачем он нужен: начиная с .NET 5 упразднены tlbexp.exe и RegAsm.exe. В эпоху .NET Framework этими двумя средствами генерировали TLB и регистрировали сборку, а в .NET 5+ преемника в поставке нет. dscom как раз закрывает эту дыру.

Какую дыру закрывает dscomВ эпоху .NET Framework TLB генерировали и сборку регистрировали tlbexp.exe и RegAsm.exe; начиная с .NET 5 оба упразднены, преемника в поставке нет, и эту дыру закрывает dscom.Эпоха .NET Frameworktlbexp.exe и RegAsm.exe.NET 5 и новееОба упразднены, преемника нетДыру закрывает dscom

Рис. 6: Начиная с .NET 5 инструмент генерации TLB сменился на 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 чаще всего стопорится.

dscom, который ставится через dotnet tool install, умеет работать только со сборками AnyCPU или 64-бит и генерирует только 64-битную TLB. Чтобы сделать 32-битную TLB, нужен отдельный исполняемый файл dscom32.exe, и его берут не из NuGet, а скачивают со страницы релизов на GitHub.

Скачанный dscom32.exe в примере этой статьи лежит в папке tools в корне проекта. Куда класть — на ваше усмотрение, но не распространяйте его вместе с выходными файлами сборки. Это инструмент разработки, в runtime он не нужен.

Есть ещё одна предпосылка, которую легко пропустить. Чтобы запустить dscom32.exe, нужна x86-сборка среды выполнения .NET. Это из-за того, как dscom загружает hostfxr.dll: среда, где стоит только x64, его не поднимет. В выводе dotnet --info проверьте, есть ли x86 runtime.

Подготовка к 32-битной TLBdscom из dotnet tool делает только 64-битную TLB, поэтому 32-битную TLB делают dscom32.exe со страницы релизов на GitHub, а для запуска нужна x86-сборка среды выполнения .NET.Нужна 32-битная TLBВзять dscom32.exe со страницы релизовПроверить, есть ли x86 runtimetlbexport через dscom32.exeВариант из dotnet 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 регистрирует comhosttlbregister регистрирует TLBРегистрация точки входа COMРегистрация типов, которые видит VBA

Рис. 8: Точка входа и информация о типах — разные вещи, поэтому и регистрации две, комплектом.

8. Добавляем ссылку в VBA и вызываем типизированно

  1. Открыть Excel или Access
  2. Открыть редактор VBA (VBE) сочетанием Alt + F11. Из ленты это вкладка Разработчик > Visual Basic. Если вкладки Разработчик нет, включите её: Файл > Параметры > Настройка ленты, флажок Разработчик
  3. В меню VBE: Сервис > Ссылки
  4. Список Доступные ссылки идёт по алфавиту. Если регистрация прошла, в нём будет имя библиотеки (по умолчанию то же, что у сборки: VbaTypedComSample) — поставьте флажок слева и ОК
  5. Если в списке её нет, кнопкой Обзор... выберите VbaTypedComSample.tlb напрямую

Если библиотека в списке не появляется, причина почти всегда одна из двух: несовпадение bitness из главы 3 или не прошедший tlbregister из главы 7. Из 32-разрядного Office 64-битная зарегистрированная TLB не видна.

Как разобрать отсутствие в списке ссылокЕсли библиотеки нет в списке ссылок, причина почти всегда либо несовпадение bitness, либо не прошедший tlbregister; из 32-разрядного Office 64-битная TLB не видна.Библиотеки нет в спискеПодозревать bitness из главы 3Подозревать сбой tlbregister из главы 7Из 32-битного Office 64-битная TLB не видна

Рис. 9: Если библиотеки нет в списке, почти всегда это либо bitness, либо регистрация.

Проверить, что ссылка встала, можно так: Вид > Просмотр объектов (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 и откройте окно Immediate сочетанием Ctrl + G: появятся три строки в соответствии с реализацией главы 4.

30
2.5
Hello, VBA

Если здесь всё как ожидалось, пройден весь путь: ссылка, регистрация COM, запуск runtime, маршалинг аргументов и возвращаемых значений. Если значения не те — смотрите реализацию на стороне .NET; если выполнить вообще нельзя — подозревайте главы 3 и 7.

Разбор по результату выполненияЕсли образец даёт ожидаемый результат, путь от ссылки до маршалинга цел; если значения не те — смотреть реализацию .NET; если выполнить нельзя — bitness из главы 3 и регистрацию из главы 7.как ожидалосьзначения не тевыполнить нельзяВыполнить образец VBAКакой результат?Весь путь целСмотреть реализацию .NETСмотреть bitness и регистрацию

Рис. 10: По трём строкам вывода уже понятно, какой слой подозревать.

После этого на стороне VBA появляются такие преимущества.

  • работает IntelliSense
  • опечатки в именах методов легче поймать ещё до выполнения
  • публичный API можно посмотреть в Object Browser
  • читать легче, чем сплошной 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 Hex$(Err.Number)
    Debug.Print Err.Description
End Sub

Если сразу понять, как читать эти значения, разбор идёт быстрее.

Поле Что туда попадает
Err.Number HRESULT, соответствующий исключению .NET, в виде знакового Long. В десятичном виде читать неудобно, поэтому переводите в шестнадцатеричный через Hex$(Err.Number)
Err.Description Через COM IErrorInfo приходит сообщение исключения .NET как есть. Для кода выше это строка, содержащая Деление на 0 невозможно.

Значение HRESULT зафиксировано для каждого типа исключения. Для ArgumentOutOfRangeException это COR_E_ARGUMENTOUTOFRANGE, значение 0x80131502. То есть если Hex$(Err.Number) даёт 80131502, дошла именно ожидаемая ArgumentOutOfRangeException со стороны .NET.

Основные соответствия такие.

Исключение .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: в Err.Number оно попадает как знаковый Long, в Err.Description — сообщение через IErrorInfo.На стороне .NET throw исключенияНа границе COM — в HRESULTВ Err.Number как знаковоеВ Err.Description — сообщениеЧитать в hex через Hex$

Рис. 11: На границе COM исключение меняет вид на HRESULT и доходит до Err в VBA.

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 downloads
  • Нужен не SDK, а runtime. Образец в этой статье — библиотека классов без UI, поэтому хватает .NET Runtime. Если используются типы WPF или Windows Forms, нужен .NET Desktop Runtime
  • Разрядность должна совпадать с Office. Для 64-разрядного Office — x64, для 32-разрядного — x86. По той же причине, по которой в главе 3 явно указали x64 / x86: если здесь не совпадёт, запуск не пойдёт
  • Проверить, стоит ли уже, можно на клиентском ПК командой dotnet --list-runtimes: должна быть строка Microsoft.NETCore.App 8.x

В сопроводительных материалах к поставке обязательно напишите «какой runtime, какая версия, какая разрядность». Если это выпадет из инструкции, на месте увидят Компонент ActiveX не может создать объект. и начнут подозревать bitness, сжигая время.

Что нужно собрать при распространенииРаспространение — это не одна DLL, а комплект выходных файлов; на клиентский ПК ставят среду выполнения .NET 8 той же разрядности, что у Office, и в материалах поставки указывают тип, версию и разрядность runtime.Разместить комплект выходных файловРаботает на клиенте.NET 8 runtime той же разрядностиВ материалах поставки указать runtime

Рис. 12: Одна DLL не работает; всё встаёт на место, только когда комплект и runtime собраны вместе.

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 Не обновляйте DLL, пока открыт 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-битным, regsvr32 из SysWOW64 не снимет). Если папку снести или перенести, не сняв регистрацию, в реестре останется запись с несуществующим путём.

Как свернуть регистрацию до и после обновленияПри обновлении закрывают Office, при необходимости снимают регистрацию в обратном порядке той же разрядности командой, затем пересобирают и регистрируют снова.Закрыть OfficeПри необходимости снять в обратном порядкеПересобратьЗарегистрировать сноваКомандой той же разрядности, что при регистрации

Рис. 14: Чтобы не остаться с занятой DLL и хвостом в реестре, снятие и повторную регистрацию делают парой.

11. Итог

Тема «использовать DLL на .NET 8 из VBA с типизацией» перестаёт быть страшной процедурой, если свести её к публикации COM + генерации TLB через dscom. На стороне .NET 8 ставите EnableComHosting=true и готовите явные интерфейсы (класс — с ClassInterfaceType.None, для VBA — InterfaceIsDual), делаете TLB командой dscom tlbexport, регистрируете *.comhost.dll через regsvr32, а *.tlb — через dscom tlbregister. Дальше остаётся добавить ссылку в VBA и идти в раннее связывание.

Если появляются сомнения, помогает приём — думать о COM host и TLB раздельно.

  • Точка входа — *.comhost.dll
  • Информация о типах — *.tlb
  • Сама реализация — *.dll

12. Справочные материалы

Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.

Эти страницы показывают тему статьи в более широком контексте услуг и решений.

Статья напрямую связана со следующими услугами.

Технические консультации и ревью дизайна

Если нужно разобрать границу между существующим VBA-кодом и .NET 8 — включая разрядность (bitness), регистрацию, генерацию TLB и способ распространения, — это удобно вести как техническую консультацию и ревью архитектуры.

Частые вопросы

Вопросы, которые часто возникают при консультациях по теме статьи.

Что нужно, чтобы использовать DLL на .NET 8 из VBA с типизацией (ранним связыванием)?
Соберите библиотеку классов .NET 8 с EnableComHosting=true, чтобы получить *.comhost.dll, затем сделайте *.tlb командой dscom tlbexport. Дальше зарегистрируйте *.comhost.dll через regsvr32, а *.tlb — через dscom tlbregister; после добавления этого TLB в ссылки VBA библиотеку можно вызывать типизированно, в виде Dim x As ИмяБиблиотеки.IYourInterface. Роли такие: точка входа COM — *.comhost.dll, информация о типах, которую видит VBA, — *.tlb, сама реализация — *.dll.
В чём причина ошибки «Компонент ActiveX не может создать объект»?
Типичная причина — несовпадение разрядности (bitness) Office/VBA и COM-сервера. Для 64-разрядного Office собирайте как x64/win-x64 и регистрируйте через regsvr32 из System32; для 32-разрядного Office (на 64-разрядной Windows) — как x86/win-x86, регистрируйте через regsvr32 из SysWOW64, а TLB генерируйте через dscom32.exe. В COM host на .NET 5+ проект, оставленный как AnyCPU, склонен собирать *.comhost.dll в 64-разрядную сторону и может не совпасть с 32-разрядным Office, поэтому безопаснее явно указать x86 или x64 под установленный Office.
Разве нельзя использовать 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 развёртывание. Если позже смените каталог размещения, регистрацию придётся делать заново.

Об авторе

Страница с профилем автора статьи.

Го Комура

Представитель KomuraSoft LLC

Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.

Публичные ссылки

Вернуться в блог