Usare una DLL .NET 8 da VBA con tipizzazione completa - Esposizione COM e TLB con dscom

· Aggiornato il: · · C#, .NET 8, VBA, COM, Office, dscom

Ci sono ancora molte situazioni in cui vuoi chiamare codice .NET 8 da VBA. In particolare, quando vuoi mantenere i tuoi asset Excel o Access esistenti così come sono, mentre scarichi solo le parti pesanti — elaborazione stringhe, HTTP, crittografia, logica di business — su C#.

Tuttavia, se fai affidamento sul late binding tramite CreateObject, il lato VBA finisce per essere pieno di Object. IntelliSense diventa debole, gli errori di battitura nei nomi dei metodi passano inosservati fino al runtime, e gradualmente si affonda in una palude di plumbing basato su stringhe.

Quindi questa volta ci concentriamo su esporre una DLL .NET 8 a COM, generare una type library (TLB) con dscom e consumarla da VBA con tipizzazione completa early binding.

Lasciamo da parte la vecchia storia .NET Framework + RegAsm, la scrittura a mano di IDL e la compilazione con MIDL, e Reg-Free COM. Qui copriamo solo il singolo percorso .NET 8 / COM host / dscom / VBA early binding.

Tutto il codice di questo articolo è pubblicato su GitHub come un set di esempi completo, compilabile e verificabile (la libreria COM-exposed, script di generazione e registrazione TLB, moduli VBA e test unitari).

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

1. La conclusione prima di tutto

Mettendo la conclusione in evidenza, il flusso è il seguente.

  • Compilare una class library .NET 8 con EnableComHosting=true
  • Creare un’interfaccia esplicita e una classe da esporre a COM
  • Impostare la classe a ClassInterfaceType.None; non ricadere su AutoDual
  • Rendere l’interfaccia consumata da VBA InterfaceIsDual
  • Dalla *.dll compilata, generare una *.tlb con dscom tlbexport
  • Registrare la *.comhost.dll con regsvr32
  • Registrare la *.tlb con dscom tlbregister
  • In VBA, aggiungere il riferimento e usarla con tipizzazione completa, ad esempio Dim x As LibraryName.IYourInterface

In sintesi, la configurazione è: l’entry point COM è la *.comhost.dll prodotta dall’SDK .NET, le informazioni sui tipi sono la *.tlb prodotta da dscom, e VBA early-binds contro quella TLB.

2. Il quadro generale

Prima, vediamo chi fa cosa in un singolo diagramma.

informazioni sui tipi dalla TLB referenziatachiamate COMVBA / Excel / AccessVbaTypedComSample.tlbVbaTypedComSample.comhost.dllVbaTypedComSample.dll (.NET 8)Runtime .NET 8

I ruoli sono i seguenti.

File Ruolo
VbaTypedComSample.dll L’implementazione .NET 8 stessa
VbaTypedComSample.comhost.dll L’entry point chiamato da COM
VbaTypedComSample.tlb Le informazioni sui tipi che VBA vede
VbaTypedComSample.deps.json Informazioni di risoluzione delle dipendenze
VbaTypedComSample.runtimeconfig.json Informazioni di avvio del runtime .NET

Ciò che conta qui è che la TLB è ciò che VBA ha bisogno per conoscere i tipi, e la comhost è ciò che serve come entry point di attivazione COM.

Il fatto che non si possa semplicemente consegnare un singolo .dll e considerare il lavoro finito è uno degli aspetti meno immediati del mondo COM.

3. Decidi questo per primo - abbina 32 bit / 64 bit

Se sbagli qui, molto probabilmente scivolerai verso ActiveX component can't create object.

Assicurati che la bitness di Office / VBA e del COM server coincidano.

Consumatore Lato .NET Generazione TLB Comando di registrazione
Office 64 bit x64 / win-x64 dscom C:\Windows\System32\regsvr32.exe
Office 32 bit (su Windows 64 bit) x86 / win-x86 dscom32.exe C:\Windows\SysWOW64\regsvr32.exe

Con il COM host in .NET 5+, lasciare il progetto come AnyCPU tende a spingere la *.comhost.dll verso il lato 64 bit, che può non quadrare con Office a 32 bit. Quindi è più sicuro specificare esplicitamente x86 / x64 per abbinarsi alla tua installazione di Office.

Il codice di questo articolo usa Office a 64 bit come esempio. Per Office a 32 bit, leggi x64 che appare più avanti come x86, e win-x64 come win-x86.

4. Costruire il lato .NET 8

Qui costruiamo un esempio minimale in cui VBA può chiamare Add, Divide e 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>

Il punto chiave è EnableComHosting. Con questo impostato, VbaTypedComSample.comhost.dll viene generata al momento della build.

4.2 Mantenere l’intero assembly COM-invisible di default

Poiché vogliamo che solo i tipi che esponiamo a COM siano marcati ComVisible(true), l’approccio facile è impostare l’intero assembly a false.

using System.Runtime.InteropServices;

[assembly: ComVisible(false)]

4.3 Scrivere l’interfaccia e la classe da esporre

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), "Cannot divide by zero.");
        }

        return x / y;
    }

    public string Hello(string name)
    {
        if (string.IsNullOrWhiteSpace(name))
        {
            return "Hello";
        }

        return $"Hello, {name}";
    }
}

I punti degni di nota in questo codice sono i seguenti.

  • Assegnare Guid separati all’interfaccia e alla classe
  • Usare ClassInterfaceType.None così non dipendi da un’interfaccia di classe auto-generata
  • Usare InterfaceIsDual così è facile da usare da VBA
  • Assegnare DispId aiuta a ridurre gli incidenti quando l’ordine dei metodi viene cambiato dopo la pubblicazione
  • COM eseguirà New sulla classe, quindi fornire un costruttore pubblico senza parametri

5. Build

Fai una build Release.

dotnet build -c Release

Dopo la build, la cartella di output contiene almeno i seguenti file.

bin/
  Release/
    net8.0-windows/
      VbaTypedComSample.dll
      VbaTypedComSample.comhost.dll
      VbaTypedComSample.deps.json
      VbaTypedComSample.runtimeconfig.json

Questa cartella è ciò che usi per deploy e registrazione. Se poi sposti i file in una posizione diversa, devi rifare la registrazione.

6. Generare la TLB con dscom

6.1 Per 64 bit

Prima, installa dscom.

dotnet tool install --global dscom

Poi genera la TLB dall’assembly compilato.

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

6.2 Per Office a 32 bit

Questa parte è una piccola trappola. Se stai generando una TLB per Office a 32 bit, è più sicuro usare dscom32.exe.

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

7. Registrare il COM host e la TLB

Esegui questo da un Command Prompt / PowerShell elevato (amministratore).

7.1 Per Office a 64 bit / COM a 64 bit

$out = Resolve-Path .\bin\Release\net8.0-windows

C:\Windows\System32\regsvr32.exe "$out\VbaTypedComSample.comhost.dll"
dscom tlbregister "$out\VbaTypedComSample.tlb"

7.2 Per Office a 32 bit (su Windows 64 bit)

$out = Resolve-Path .\bin\Release\net8.0-windows

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

Qui succedono due cose.

  • regsvr32 registra la *.comhost.dll come COM server
  • tlbregister registra la *.tlb come type library

8. Aggiungere il riferimento in VBA e usarlo con tipizzazione

  1. Apri Excel o Access
  2. Apri l’editor VBA
  3. Strumenti -> Riferimenti
  4. Se la libreria appare nell’elenco, selezionala
  5. Se non appare, scegli VbaTypedComSample.tlb tramite Sfoglia...
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

Con questo, il lato VBA ottiene i seguenti benefici.

  • IntelliSense funziona
  • Gli errori di battitura nei nomi dei metodi sono più facili da catturare prima dell’esecuzione
  • L’API pubblica può essere ispezionata nell’Object Browser
  • È più leggibile che scrivere Object dappertutto

8.1 Le eccezioni emergono come errori COM sul lato VBA

Ad esempio, quando viene lanciata un’eccezione sul lato .NET, come con Divide(10, 0), appare come errore COM sul lato VBA.

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. Come pensare al deploy

La cosa importante al momento del deploy è consegnare l’intero set di output, non solo la DLL da sola.

VbaTypedComSample.dll
VbaTypedComSample.comhost.dll
VbaTypedComSample.deps.json
VbaTypedComSample.runtimeconfig.json
VbaTypedComSample.tlb
(eventuali DLL di dipendenza se necessarie)

Inoltre, il PC client ha bisogno del runtime .NET 8 corrispondente. Il COM host non funziona come self-contained deployment; in pratica deve essere operato framework-dependent.

10. Pitfall

10.1 Non lasciarlo su AnyCPU

Se la bitness di VBA / Office e quella del COM host perdono sincronia, le cose falliscono in modi piuttosto sgradevoli.

  • Per Office a 64 bit: x64 / win-x64
  • Per Office a 32 bit: x86 / win-x86

10.2 Non usare ClassInterfaceType.AutoDual

Sembra comodo a prima vista, ma si rompe facilmente una volta che tocchi ordine o composizione dei membri dopo la pubblicazione.

Se vuoi un uso stabile e tipizzato da VBA, la pratica consolidata è definire interfacce esplicite e impostare la classe a ClassInterfaceType.None.

10.3 Non rigenerare i GUID alla leggera

In COM, il GUID è il contratto stesso. Sostituire casualmente un IID o CLSID dopo la pubblicazione rompe i riferimenti e le registrazioni VBA esistenti.

10.4 Non rompere un’interfaccia pubblicata

In COM, anche “aggiungere solo un metodo in seguito” non sempre finisce pacificamente.

  • Mantieni ICalculator così com’è
  • Se la modifica è sostanziale, introduci una nuova ICalculator2
  • La classe può implementare entrambe

10.5 Mantieni i tipi semplici

Al confine esposto a VBA, è più sicuro non fare acrobazie.

I tipi che funzionano bene sono, per iniziare, questi.

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

10.6 Non aggiornare mentre Office è aperto

Excel o Access possono mantenere un riferimento alla DLL, causando problemi durante build o re-registrazione.

  • Chiudi Office
  • Deregistra se necessario
  • Ricompila
  • Registra di nuovo

11. Riassunto

L’argomento “usare una DLL .NET 8 da VBA con tipizzazione completa” non è una procedura così spaventosa una volta ristretta a esposizione COM + generazione TLB con dscom. Sul lato .NET 8, imposta EnableComHosting=true e prepara interfacce esplicite (classe impostata a ClassInterfaceType.None, l’interfaccia rivolta a VBA impostata a InterfaceIsDual), genera la TLB con dscom tlbexport, registra la *.comhost.dll con regsvr32 e la *.tlb con dscom tlbregister. Dopodiché, basta aggiungere il riferimento in VBA e usare early binding.

Quando hai dubbi, il trucco è pensare separatamente al COM host e alla TLB.

  • Il punto di attivazione è la *.comhost.dll
  • Le informazioni sui tipi sono la *.tlb
  • L’implementazione stessa è la *.dll

12. Riferimenti

Articoli recenti con gli stessi tag per approfondire argomenti vicini.

Queste pagine collocano l’argomento in un contesto più ampio di servizi e decisioni.

L’articolo è direttamente collegato ai servizi seguenti.

Sviluppo di applicazioni Windows

Progettare la superficie di integrazione tra VBA, COM, Office, .NET 8 e generazione di type library è strettamente legata allo sviluppo di applicazioni Windows, quindi questo argomento si abbina bene al nostro servizio di sviluppo Windows.

Domande frequenti

Domande che ricorrono nelle consulenze sull’argomento dell’articolo.

Posso chiamare una DLL .NET 8 da VBA con early binding e IntelliSense?
Sì. Compila la class library con EnableComHosting=true, definisci un'interfaccia COM-visible esplicita contrassegnata InterfaceIsDual e una classe impostata a ClassInterfaceType.None, genera una type library con dscom tlbexport, poi registra la *.comhost.dll con regsvr32 e la *.tlb con dscom tlbregister. Dopo aver aggiunto la TLB come riferimento nell'editor VBA, puoi dichiarare variabili con tipizzazione completa, ottenere IntelliSense e ispezionare l'API nell'Object Browser.
Perché ricevo 'ActiveX component can't create object' chiamando .NET da VBA?
La causa più comune è un mismatch di bitness tra Office/VBA e il COM server. Office a 64 bit richiede una build x64/win-x64 registrata con regsvr32 di System32, mentre Office a 32 bit richiede x86/win-x86 registrata con regsvr32 di SysWOW64 e una TLB generata con dscom32.exe. Lasciare il progetto come AnyCPU tende a spingere la comhost DLL verso il 64 bit, quindi specifica esplicitamente x86 o x64 per abbinarlo alla tua installazione di Office.
Quali file devo distribuire oltre alla DLL .NET stessa?
Consegna l'intero set di output della build, non solo la DLL: la DLL di implementazione, la *.comhost.dll che funge da entry point di attivazione COM, i file deps.json e runtimeconfig.json, e la *.tlb che fornisce a VBA le informazioni sui tipi. Anche il PC client ha bisogno del corrispondente runtime .NET 8 installato, perché il COM host non funziona come self-contained deployment e deve essere eseguito framework-dependent. Se poi sposti i file in una posizione diversa, la registrazione deve essere rifatta.
Perché dovrei evitare ClassInterfaceType.AutoDual per classi .NET esposte a COM?
AutoDual sembra comodo, ma si rompe facilmente non appena si cambia l'ordine o la composizione dei membri dopo la pubblicazione. La pratica consolidata per un uso stabile e tipizzato da VBA è definire interfacce esplicite, impostare la classe a ClassInterfaceType.None e assegnare DispId espliciti. Tratta anche i GUID come il contratto stesso: non rigenerare IID o CLSID alla leggera, e se un'interfaccia necessita modifiche sostanziali dopo il rilascio, introduci una nuova interfaccia come ICalculator2 anziché modificare quella pubblicata.

Profilo dell’autore

Pagina di presentazione dell’autore dell’articolo.

Go Komura

Rappresentante di KomuraSoft LLC

Specializzato nello sviluppo di software Windows, nella consulenza tecnica e nell’analisi dei malfunzionamenti, soprattutto nei progetti con sistemi esistenti e guasti difficili da riprodurre.

Torna al blog