Using a .NET 8 DLL from VBA with Full Typing - COM Exposure and dscom TLB
· Go Komura · C#, .NET 8, VBA, COM, Office, dscom
There are still plenty of situations where you want to call .NET 8 code from VBA. In particular, when you want to keep your existing Excel or Access assets as they are, while offloading just the heavy parts - string processing, HTTP, cryptography, business logic - to C#.
However, if you lean on late binding via CreateObject, the VBA side ends up littered with Object. IntelliSense becomes weak, typos in method names go unnoticed until runtime, and you gradually sink into a swamp of string-based plumbing.
So this time, we focus on exposing a .NET 8 DLL to COM, generating a type library (TLB) with dscom, and consuming it from VBA with typed early binding.
We will set aside the old .NET Framework + RegAsm story, hand-writing IDL and compiling it with MIDL, and Reg-Free COM. Here we cover only the single path of .NET 8 / COM host / dscom / VBA early binding.
All the code in this article is published on GitHub as a complete, buildable, verifiable sample set (the COM-exposed library, TLB generation and registration scripts, VBA modules, and unit tests).
dotnet8-dll-typed-vba-com-dscom-tlb - komurasoft-blog-samples (GitHub)
1. The Conclusion First
Laying out the conclusion up front, the flow is as follows.
- Build a .NET 8 class library with
EnableComHosting=true - Create an explicit interface and a class to expose to COM
- Set the class to
ClassInterfaceType.None; do not fall back onAutoDual - Make the interface consumed from VBA
InterfaceIsDual - From the built
*.dll, generate a*.tlbwithdscom tlbexport - Register the
*.comhost.dllwithregsvr32 - Register the
*.tlbwithdscom tlbregister - In VBA, add a reference and use it with full typing, like
Dim x As LibraryName.IYourInterface
In short, the setup is: the COM entry point is the *.comhost.dll produced by the .NET SDK, the type information is the *.tlb produced by dscom, and VBA early-binds against that TLB.
2. The Big Picture
First, let’s see what plays which role in a single diagram.
flowchart LR
VBA["VBA / Excel / Access"] -->|type info from the referenced TLB| TLB["VbaTypedComSample.tlb"]
VBA -->|COM calls| COMHOST["VbaTypedComSample.comhost.dll"]
COMHOST --> DOTNET["VbaTypedComSample.dll (.NET 8)"]
DOTNET --> RUNTIME[".NET 8 Runtime"]
The roles are as follows.
| File | Role |
|---|---|
VbaTypedComSample.dll |
The .NET 8 implementation itself |
VbaTypedComSample.comhost.dll |
The entry point called from COM |
VbaTypedComSample.tlb |
The type information VBA sees |
VbaTypedComSample.deps.json |
Dependency resolution information |
VbaTypedComSample.runtimeconfig.json |
.NET runtime startup information |
What matters here is that the TLB is what VBA needs in order to know the types, and the comhost is what is needed as the COM activation entry point.
The fact that you cannot just hand over a single .dll and call it a day is one of the less straightforward aspects of the COM world.
3. Decide This First - Match 32-bit / 64-bit
Get this wrong, and you will very likely roll downhill toward ActiveX component can't create object.
Make sure the bitness of Office / VBA and the COM server match.
| Consumer | .NET side | TLB generation | Registration command |
|---|---|---|---|
| 64-bit Office | x64 / win-x64 |
dscom |
C:\Windows\System32\regsvr32.exe |
| 32-bit Office (on 64-bit Windows) | x86 / win-x86 |
dscom32.exe |
C:\Windows\SysWOW64\regsvr32.exe |
With the COM host in .NET 5+, leaving the project as AnyCPU tends to push the *.comhost.dll toward the 64-bit side, which may not mesh with 32-bit Office. So it is safer to explicitly specify x86 / x64 to match your Office installation.
The code in this article uses 64-bit Office as the example. For 32-bit Office, read the x64 that appears later as x86, and win-x64 as win-x86.
4. Building the .NET 8 Side
Here we build a minimal sample where VBA can call Add, Divide, and 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>
The key point is EnableComHosting. With this set, VbaTypedComSample.comhost.dll is generated at build time.
4.2 Keep the Whole Assembly COM-Invisible by Default
Since we only want the types we expose to COM marked ComVisible(true), the easy approach is to set the whole assembly to false.
using System.Runtime.InteropServices;
[assembly: ComVisible(false)]
4.3 Write the Interface and Class to Expose
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}";
}
}
The points worth noting in this code are the following.
- Assign separate
Guids to the interface and the class - Use
ClassInterfaceType.Noneso you do not depend on an auto-generated class interface - Use
InterfaceIsDualso it is easy to work with from VBA - Assigning
DispIds helps reduce accidents when the method order is changed after publication - COM will
Newthe class, so provide a public parameterless constructor
5. Building
Do a Release build.
dotnet build -c Release
After the build, the output folder contains at least the following files.
bin/
Release/
net8.0-windows/
VbaTypedComSample.dll
VbaTypedComSample.comhost.dll
VbaTypedComSample.deps.json
VbaTypedComSample.runtimeconfig.json
This folder is what you use for deployment and registration. If you move things to a different location later, you have to redo the registration.
6. Generating the TLB with dscom
6.1 For 64-bit
First, install dscom.
dotnet tool install --global dscom
Then generate the TLB from the built assembly.
dscom tlbexport .\bin\Release\net8.0-windows\VbaTypedComSample.dll --out .\bin\Release\net8.0-windows\VbaTypedComSample.tlb
6.2 For 32-bit Office
This part is a small trap. If you are generating a TLB for 32-bit Office, it is safer to use dscom32.exe.
.\tools\dscom32.exe tlbexport .\bin\Release\net8.0-windows\VbaTypedComSample.dll --out .\bin\Release\net8.0-windows\VbaTypedComSample.tlb
7. Registering the COM Host and TLB
Run this from an elevated (administrator) Command Prompt / PowerShell.
7.1 For 64-bit Office / 64-bit 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 For 32-bit Office (on 64-bit 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"
Two things are happening here.
regsvr32registers the*.comhost.dllas a COM servertlbregisterregisters the*.tlbas a type library
8. Adding the Reference in VBA and Using It with Typing
- Open Excel or Access
- Open the VBA editor
Tools->References- If the library appears in the list, check it
- If it does not appear in the list, choose
VbaTypedComSample.tlbviaBrowse...
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
With this, the VBA side gets the following benefits.
- IntelliSense works
- Typos in method names are easier to catch before execution
- The public API can be inspected in the Object Browser
- More readable than writing raw
Objecteverywhere
8.1 Exceptions Surface as COM Errors on the VBA Side
For example, when an exception is thrown on the .NET side, as with Divide(10, 0), it appears as a COM error on the VBA side.
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. How to Think About Deployment
The important thing at deployment time is to ship the full output set, not just the DLL by itself.
VbaTypedComSample.dll
VbaTypedComSample.comhost.dll
VbaTypedComSample.deps.json
VbaTypedComSample.runtimeconfig.json
VbaTypedComSample.tlb
(plus any dependency DLLs if needed)
In addition, the client PC needs the corresponding .NET 8 runtime. The COM host does not work as a self-contained deployment; in practice it has to be operated as framework-dependent.
10. Pitfalls
10.1 Do Not Leave It on AnyCPU
If the bitness of VBA / Office and the bitness of the COM host get out of sync, things fail in rather unpleasant ways.
- For 64-bit Office:
x64/win-x64 - For 32-bit Office:
x86/win-x86
10.2 Do Not Use ClassInterfaceType.AutoDual
It looks convenient at first glance, but it breaks easily once you touch member order or composition after publication.
If you want stable, typed use from VBA, the established practice is to define explicit interfaces and set the class to ClassInterfaceType.None.
10.3 Do Not Regenerate GUIDs Carelessly
In COM, the GUID is the contract itself. Carelessly swapping out an IID or CLSID after publication breaks existing VBA references and registrations.
10.4 Do Not Break a Published Interface
In COM, even “just adding one method later” does not always end peacefully.
- Keep
ICalculatoras is - If the change is substantial, introduce a new
ICalculator2 - The class may implement both
10.5 Keep the Types Plain
At the boundary exposed to VBA, it is safer not to get fancy.
The types that play well are, to start with, around here.
intdoubleboolstringDateTimedecimalenum
10.6 Do Not Update While Office Is Open
Excel or Access can keep a hold on the DLL, causing trouble during builds or re-registration.
- Close Office
- Unregister if necessary
- Rebuild
- Register again
11. Summary
The topic of using a .NET 8 DLL from VBA with full typing is not such a scary procedure once you narrow it down to COM exposure + TLB generation with dscom. On the .NET 8 side, set EnableComHosting=true and prepare explicit interfaces (class set to ClassInterfaceType.None, the VBA-facing interface set to InterfaceIsDual), generate the TLB with dscom tlbexport, register the *.comhost.dll with regsvr32 and the *.tlb with dscom tlbregister. After that, just add the reference in VBA and use early binding.
When in doubt, the trick is to think of the COM host and the TLB separately.
- The activation entry point is the
*.comhost.dll - The type information is the
*.tlb - The implementation itself is the
*.dll
12. References
- Complete sample code for this article (COM-exposed library, scripts, VBA, tests) - komurasoft-blog-samples (GitHub)
- Expose .NET components to COM - Microsoft Learn
- Qualify .NET types for interoperation - Microsoft Learn
- ComInterfaceType Enum - Microsoft Learn
- ClassInterfaceType Enum - Microsoft Learn
- COM Callable Wrapper - Microsoft Learn
- DispIdAttribute Class - Microsoft Learn
- dscom - NuGet Gallery
- How to use the Regsvr32 tool and troubleshoot Regsvr32 error messages - Microsoft Support
- .NET 8 downloads
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
Windows App Outsourcing and Contract Development: What to Sort Out Before You Ask
Before commissioning Windows app outsourcing or contract development, here is how to sort out existing software modification, device inte...
Why ActiveX Stops Working in Office 2024/Microsoft 365 and How to Diagnose It
When ActiveX stops working in Office 2024/Microsoft 365, work through the causes in order: default disablement, 32-bit/64-bit mismatch, C...
Preparing for VBScript Deprecation: An Audit Guide for VBA and Internal Tools
Preparing for the phased deprecation of VBScript: inventorying VBA, Excel macros, and internal tools, static detection, execution logging...
What Is VBA? - Its Constraints, Its Future, When to Replace It, and Realistic Migration Patterns
The basics and constraints of VBA, its outlook, when replacement makes sense, and a realistic approach to gradually migrating Excel macro...
How to Build Excel Report Output - COM / Open XML / Templates
The design of Excel report output changes considerably depending on whether you automate Excel itself, generate xlsx files directly, or k...
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
ActiveX Migration
Topic page for staged decisions around keeping, wrapping, or replacing COM / ActiveX / OCX assets.
Where This Topic Connects
This article connects naturally to the following service pages.
Windows App Development
Designing the integration surface across VBA, COM, Office, .NET 8, and type library generation is closely tied to Windows application development, so this topic pairs well with our Windows app development service.
Technical Consulting & Design Review
If you want to sort out the boundary design between existing VBA assets and .NET 8 - including bitness, registration, TLB generation, and deployment strategy - this works well as a technical consulting and design review engagement.
Author Profile
Profile page for the article author.
Go Komura
Representative of KomuraSoft LLC
Focused on Windows software development, technical consulting, and investigations into failures that are difficult to reproduce.
Public links