Utiliser une DLL .NET 8 depuis VBA avec typage fort - Exposition COM et TLB dscom

· Mis à jour le: · · C#, .NET 8, VBA, COM, Office, dscom

Il existe encore couramment des situations où l’on souhaite appeler du code .NET 8 depuis VBA. C’est notamment le cas lorsqu’on veut conserver tels quels les actifs Excel ou Access existants, tout en déportant vers C# uniquement les parties lourdes - traitement de chaînes, HTTP, chiffrement, logique métier.

Cependant, si l’on s’appuie sur CreateObject pour une liaison tardive (late binding), le côté VBA finit criblé de Object. L’IntelliSense s’affaiblit, les fautes de frappe dans les noms de méthodes ne sont détectées qu’à l’exécution, et on s’enfonce peu à peu dans un bourbier reposant sur des chaînes de caractères.

Cette fois, nous nous concentrons donc sur l’exposition d’une DLL .NET 8 à COM, la génération d’une bibliothèque de types (TLB) avec dscom, et son utilisation typée depuis VBA par liaison anticipée (early binding).

Nous laissons de côté cette fois le récit ancien de .NET Framework + RegAsm, l’écriture manuelle d’IDL compilée avec MIDL, ainsi que le Reg-Free COM. Ici, nous ne traitons que le chemin unique .NET 8 / hôte COM / dscom / liaison anticipée VBA.

Par ailleurs, tout le code présenté dans cet article est publié sur GitHub sous forme d’un ensemble d’exemples complet, compilable et vérifiable (bibliothèque exposée à COM, scripts de génération et d’enregistrement du TLB, modules VBA, tests unitaires).

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

1. La conclusion d’abord

Pour poser d’abord la conclusion, voici le déroulé.

  • Compiler la bibliothèque de classes .NET 8 avec EnableComHosting=true
  • Créer une interface explicite et une classe à exposer à COM
  • Régler la classe sur ClassInterfaceType.None, sans se réfugier dans AutoDual
  • Mettre l’interface utilisée depuis VBA en InterfaceIsDual
  • À partir du *.dll produit par la compilation, générer un *.tlb avec dscom tlbexport
  • Enregistrer *.comhost.dll avec regsvr32
  • Enregistrer *.tlb avec dscom tlbregister
  • Ajouter la référence dans VBA et l’utiliser de façon typée, comme dans Dim x As NomDeLaBibliothèque.IVotreInterface

En somme, l’architecture est la suivante : le point d’entrée COM est le *.comhost.dll produit par le SDK .NET, l’information de type est le *.tlb produit par dscom, et VBA effectue une liaison anticipée en consultant ce TLB.

2. Vue d’ensemble de cette architecture

Voyons d’abord, en un seul schéma, qui joue quel rôle.

informations de type via le TLB référencéappels COMVBA / Excel / AccessVbaTypedComSample.tlbVbaTypedComSample.comhost.dllVbaTypedComSample.dll (.NET 8).NET 8 Runtime

Voici le rôle de chacun.

Fichier Rôle
VbaTypedComSample.dll L’implémentation .NET 8 elle-même
VbaTypedComSample.comhost.dll Le point d’entrée appelé depuis COM
VbaTypedComSample.tlb L’information de type que VBA consulte
VbaTypedComSample.deps.json Les informations de résolution des dépendances
VbaTypedComSample.runtimeconfig.json Les informations de démarrage du runtime .NET

Ce qui compte ici, c’est que le TLB est ce dont VBA a besoin pour connaître les types, et que le comhost est ce qui est nécessaire comme point d’entrée d’activation COM.

Le fait qu’on ne puisse pas simplement remettre le .dll seul et considérer l’affaire close illustre bien ce que le monde COM a de peu direct.

3. À décider en premier - Faire correspondre le 32 bits et le 64 bits

Se tromper ici fait basculer, avec une probabilité assez élevée, vers l’erreur Le composant ActiveX ne peut pas créer l'objet.

Veillez à faire correspondre la bitness d’Office / VBA et celle du serveur COM.

Côté consommateur Repère côté .NET Génération du TLB Commande d’enregistrement
Office 64 bits x64 / win-x64 dscom C:\Windows\System32\regsvr32.exe
Office 32 bits (sur Windows 64 bits) x86 / win-x86 dscom32.exe C:\Windows\SysWOW64\regsvr32.exe

Avec l’hôte COM de .NET 5+, laisser le projet en AnyCPU tend à faire pencher *.comhost.dll du côté 64 bits, ce qui peut ne pas correspondre à un Office 32 bits. Il est donc plus sûr d’expliciter x86 / x64 en fonction d’Office.

Le code de cet article prend Office 64 bits comme exemple. Pour un Office 32 bits, remplacez mentalement le x64 qui apparaît plus loin par x86, et win-x64 par win-x86.

4. Construire le côté .NET 8

Ici, nous construisons un exemple minimal où VBA peut appeler Add, Divide et 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>

Le point clé est EnableComHosting. Avec ce réglage, VbaTypedComSample.comhost.dll est généré au moment de la compilation.

4.2 Garder tout l’assembly invisible pour COM par défaut

Comme on ne veut marquer ComVisible(true) que sur les types exposés à COM, il est plus simple de régler l’ensemble de l’assembly sur false.

using System.Runtime.InteropServices;

[assembly: ComVisible(false)]

4.3 Écrire l’interface et la classe à exposer

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), "Impossible de diviser par zéro.");
        }

        return x / y;
    }

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

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

Les points à retenir dans ce code sont les suivants.

  • Attribuer un Guid séparé à l’interface et à la classe
  • Utiliser ClassInterfaceType.None pour ne pas dépendre d’une interface de classe générée automatiquement
  • Utiliser InterfaceIsDual pour faciliter l’utilisation depuis VBA
  • Attribuer des DispId permet de réduire les accidents lorsqu’on modifie l’ordre des méthodes après publication
  • Comme la classe est instanciée (New) par COM, prévoir un constructeur public sans argument

5. Compiler

Effectuez une compilation Release.

dotnet build -c Release

Après la compilation, le dossier de sortie contient au moins les fichiers suivants.

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

C’est ce dossier qui sert pour la distribution et l’enregistrement. Si vous changez l’emplacement plus tard, il faudra refaire l’enregistrement.

6. Générer le TLB avec dscom

6.1 Pour le 64 bits

Installez d’abord dscom.

dotnet tool install --global dscom

Ensuite, générez le TLB à partir de l’assembly compilé.

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

6.2 Pour Office 32 bits

C’est un petit piège ici. Pour générer un TLB destiné à un Office 32 bits, il est plus sûr d’utiliser dscom32.exe.

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

7. Enregistrer l’hôte COM et le TLB

Exécutez ceci depuis une invite de commandes / un PowerShell avec des droits administrateur.

7.1 Pour un Office 64 bits / COM 64 bits

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

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

7.2 Pour un Office 32 bits (sur Windows 64 bits)

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

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

Deux choses se produisent ici.

  • regsvr32 enregistre *.comhost.dll comme serveur COM
  • tlbregister enregistre *.tlb comme bibliothèque de types

8. Ajouter la référence dans VBA et l’utiliser de façon typée

  1. Ouvrir Excel ou Access
  2. Ouvrir l’éditeur VBA
  3. Outils -> Références
  4. Si la bibliothèque apparaît dans la liste, cocher la case
  5. Si elle n’apparaît pas dans la liste, choisir VbaTypedComSample.tlb via Parcourir...
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

Cela apporte les avantages suivants côté VBA.

  • L’IntelliSense fonctionne
  • Les fautes de frappe dans les noms de méthodes sont plus faciles à repérer avant l’exécution
  • L’API publique peut être consultée dans l’Object Browser
  • Plus lisible qu’un usage brut de Object

8.1 Les exceptions deviennent des erreurs COM côté VBA

Par exemple, lorsqu’une exception est levée côté .NET, comme avec Divide(10, 0), elle apparaît côté VBA comme une erreur 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. Comment penser la distribution

Ce qui compte lors de la distribution, c’est de déployer l’ensemble des fichiers produits, et non la DLL seule.

VbaTypedComSample.dll
VbaTypedComSample.comhost.dll
VbaTypedComSample.deps.json
VbaTypedComSample.runtimeconfig.json
VbaTypedComSample.tlb
(ainsi que l'ensemble des DLL dépendantes si nécessaire)

De plus, le PC client a besoin du runtime .NET 8 correspondant. L’hôte COM ne fonctionne pas en déploiement self-contained ; il doit en principe être exploité en mode framework-dependent.

10. Pièges courants

10.1 Ne pas laisser le projet en AnyCPU

Si la bitness de VBA / Office et celle de l’hôte COM divergent, les échecs prennent une forme assez déroutante.

  • Pour un Office 64 bits : x64 / win-x64
  • Pour un Office 32 bits : x86 / win-x86

10.2 Ne pas utiliser ClassInterfaceType.AutoDual

Cela paraît pratique à première vue, mais c’est facile à casser dès qu’on touche à l’ordre des membres ou à la composition après publication.

Pour un usage typé stable depuis VBA, la pratique établie consiste à définir une interface explicite et à régler la classe sur ClassInterfaceType.None.

10.3 Ne pas régénérer les GUID à la légère

En COM, le GUID constitue le contrat lui-même. Remplacer l’IID ou le CLSID à la légère après publication casse les références et enregistrements VBA existants.

10.4 Ne pas casser une interface déjà publiée

En COM, même « ajouter simplement une méthode après coup » ne se passe pas toujours sans heurts.

  • Conserver ICalculator tel quel
  • Si le changement est important, créer un nouveau ICalculator2
  • La classe peut implémenter les deux

10.5 Garder des types sobres

À la frontière exposée à VBA, il est plus sûr de rester simple.

Pour commencer, les types suivants s’accordent bien.

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

10.6 Ne pas mettre à jour pendant qu’Office est ouvert

Excel ou Access peut garder la main sur la DLL, ce qui provoque des complications lors de la compilation ou du réenregistrement.

  • Fermer Office
  • Désenregistrer si nécessaire
  • Recompiler
  • Enregistrer à nouveau

11. Résumé

Le sujet « utiliser une DLL .NET 8 depuis VBA avec typage fort » n’est pas une procédure si effrayante une fois qu’on le ramène à l’exposition COM + la génération du TLB avec dscom. Côté .NET 8, on règle EnableComHosting=true et on prépare une interface explicite (classe en ClassInterfaceType.None, interface côté VBA en InterfaceIsDual), on génère le TLB avec dscom tlbexport, on enregistre *.comhost.dll avec regsvr32 et *.tlb avec dscom tlbregister. Il ne reste plus ensuite qu’à ajouter la référence dans VBA et à utiliser la liaison anticipée.

En cas de doute, l’astuce consiste à penser séparément l’hôte COM et le TLB.

  • Le point d’entrée d’activation est *.comhost.dll
  • L’information de type est *.tlb
  • L’implémentation elle-même est *.dll

12. Références

Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.

Ces pages replacent le sujet dans un contexte plus large de services et de décisions.

Cet article est directement lié aux services suivants.

Développement d'applications Windows

La conception de la surface d'intégration entre VBA, COM, Office, .NET 8 et la génération de bibliothèque de types est étroitement liée au développement d'applications Windows, ce qui rend ce thème compatible avec notre service de développement d'applications Windows.

Conseil technique et revue de conception

Si vous souhaitez clarifier la conception de la frontière entre des actifs VBA existants et .NET 8 - y compris la bitness, l'enregistrement, la génération de TLB et la stratégie de déploiement - cela se prête bien à une mission de conseil technique et de revue de conception.

Questions fréquentes

Questions souvent posées lors d’une consultation sur le sujet de cet article.

Que faut-il pour utiliser une DLL .NET 8 depuis VBA avec typage fort (liaison anticipée) ?
Compilez la bibliothèque de classes .NET 8 avec EnableComHosting=true pour générer *.comhost.dll, définissez une interface explicite visible par COM marquée InterfaceIsDual ainsi qu'une classe réglée sur ClassInterfaceType.None, puis créez *.tlb avec dscom tlbexport. Enregistrez ensuite *.comhost.dll avec regsvr32 et *.tlb avec dscom tlbregister, puis ajoutez ce TLB dans les références VBA : vous pourrez alors écrire Dim x As NomDeLaBibliothèque.IVotreInterface pour un usage typé, avec IntelliSense et inspection de l'API dans l'Object Browser. En résumé, *.comhost.dll est le point d'entrée COM, *.tlb est l'information de type que VBA consulte, et *.dll contient l'implémentation elle-même.
Quelle est la cause de l'erreur « Le composant ActiveX ne peut pas créer l'objet » ?
La cause la plus fréquente est une incompatibilité de bitness entre Office/VBA et le serveur COM. Pour un Office 64 bits, compilez en x64/win-x64 et enregistrez avec le regsvr32 de System32 ; pour un Office 32 bits (sur un Windows 64 bits), compilez en x86/win-x86, enregistrez avec le regsvr32 de SysWOW64, et générez le TLB avec dscom32.exe. Avec l'hôte COM de .NET 5+, laisser le projet en AnyCPU tend à faire pencher *.comhost.dll du côté 64 bits, ce qui peut ne pas correspondre à un Office 32 bits ; il est donc plus sûr d'expliciter x86/x64 en fonction d'Office.
Faut-il éviter d'utiliser ClassInterfaceType.AutoDual ?
Cela semble pratique à première vue, mais c'est facile à casser dès qu'on touche à l'ordre des membres ou à la composition après publication, donc mieux vaut l'éviter. Pour un usage typé stable depuis VBA, la pratique établie consiste à définir une interface explicite, à régler la classe sur ClassInterfaceType.None, et à mettre l'interface utilisée par VBA en InterfaceIsDual. Assigner des DispId réduit les accidents lors d'un changement d'ordre des méthodes. Par ailleurs, en COM, le GUID constitue le contrat lui-même : régénérer l'IID ou le CLSID à la légère après publication casse les références et enregistrements VBA existants.
Pour la distribution, suffit-il de fournir la DLL seule ?
Non, la DLL seule ne fonctionnera pas. Il faut regrouper l'implémentation elle-même (*.dll), *.comhost.dll, *.deps.json, *.runtimeconfig.json, *.tlb, et si nécessaire l'ensemble des DLL dépendantes. Le PC client a en outre besoin du runtime .NET 8 correspondant, car l'hôte COM ne fonctionne pas en déploiement self-contained mais doit être exploité en mode framework-dependent. Notez aussi que si vous changez l'emplacement de déploiement par la suite, il faudra refaire l'enregistrement.

Profil de l’auteur

Page de présentation de l’auteur de l’article.

Go Komura

Représentant de KomuraSoft LLC

Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.

Retour au blog