How to Use a .NET 8 DLL from VBA with Type Safety - Expose via COM and Generate a TLB with dscom
The short version
Here is the flow for using a .NET 8 DLL from VBA with type safety (early binding):
- build the .NET 8 class library with
EnableComHosting=true - define explicit COM-facing interfaces and classes
- mark the class as
ClassInterfaceType.Noneand the interface asInterfaceIsDual - after building, generate the TLB with
dscom tlbexport - register
*.comhost.dllwithregsvr32 - register the TLB with
dscom tlbregister - add a reference in VBA and use it with full type info
The COM entry point is *.comhost.dll, the type information lives in *.tlb, and VBA reads that TLB to do early binding. That is the whole shape of it.
The big picture
| File | Role |
|---|---|
| VbaTypedComSample.dll | the actual .NET 8 implementation |
| VbaTypedComSample.comhost.dll | the entry point COM calls into |
| VbaTypedComSample.tlb | the type information VBA reads |
| VbaTypedComSample.deps.json | dependency resolution info |
| VbaTypedComSample.runtimeconfig.json | .NET runtime startup info |
Decide this first: match 32-bit / 64-bit
If the bitness of Office/VBA and your COM server do not match, you will get the classic “ActiveX component can’t create object” family of errors.
| Consumer | .NET side | TLB generation | Registration command |
|---|---|---|---|
| 64-bit Office | x64 / win-x64 | dscom | C:\Windows\System32\regsvr32.exe |
| 32-bit Office | x86 / win-x86 | dscom32.exe | C:\Windows\SysWOW64\regsvr32.exe |
Building the .NET 8 side
.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 bit is EnableComHosting=true. With that flag the build also produces *.comhost.dll.
Make the assembly non-COM-visible by default
using System.Runtime.InteropServices;
[assembly: ComVisible(false)]
The interface and class you actually expose
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 int Add(int x, int y) => checked(x + y);
public double Divide(double x, double y) { /* divide-by-zero check */ }
public string Hello(string name) { /* handles empty strings */ }
}
Things to keep in mind
- give the interface and the class their own separate Guids
- use ClassInterfaceType.None so you are not relying on AutoDual
- the interface VBA sees should be InterfaceIsDual
- assigning DispId values up front limits the damage if methods get reordered later
- you need a public parameterless constructor (COM news up the object that way)
Build and generate the TLB
dotnet build -c Release
dotnet tool install --global dscom
dscom tlbexport .\bin\Release\net8.0-windows\VbaTypedComSample.dll --out .\bin\Release\net8.0-windows\VbaTypedComSample.tlb
For 32-bit Office, swap in dscom32.exe.
Register the COM host and TLB (run elevated)
# 64-bit Office
$out = Resolve-Path .\bin\Release\net8.0-windows
C:\Windows\System32\regsvr32.exe "$out\VbaTypedComSample.comhost.dll"
dscom tlbregister "$out\VbaTypedComSample.tlb"
Using it from VBA
- open Excel/Access and switch to the VBA editor
Tools->References-> tickVbaTypedComSample.tlb- write the code:
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
IntelliSense lights up, and method-name typos surface before you ever hit Run.
Exceptions surface as COM errors on the VBA side
Public Sub UseWithErrorHandling()
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, Err.Description
End Sub
Files you actually need to ship
VbaTypedComSample.dll
VbaTypedComSample.comhost.dll
VbaTypedComSample.deps.json
VbaTypedComSample.runtimeconfig.json
VbaTypedComSample.tlb
The client PC also needs the matching .NET 8 runtime installed.
Gotchas
- don’t leave it on AnyCPU - x64 for 64-bit Office, x86 for 32-bit Office
- avoid ClassInterfaceType.AutoDual - it breaks easily when member order changes after release
- don’t regenerate GUIDs casually - in COM the GUID is the contract; changing it breaks every existing reference
- don’t break a published interface - if you need bigger changes, add a new
ICalculator2instead - keep types boring at the boundary -
int,double,bool,string,DateTime,decimal, andenumare the safe set across the VBA boundary - don’t update while Office is open - the DLL stays locked and your build or re-register will fail
Wrap-up
The trick to .NET 8 + VBA interop is to think of the COM host and the TLB as two separate things.
- entry point:
*.comhost.dll - type information:
*.tlb - actual implementation:
*.dll
Once you internalize the pipeline - EnableComHosting=true -> dscom tlbexport -> regsvr32 plus dscom tlbregister -> VBA reference - you can ship type-safe interop without surprises.
References
- Expose .NET components to COM - Microsoft Learn
- Qualifying .NET types for COM 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.
How to Build Excel Report Output - A Decision Table for COM Automation, Open XML, and Template-Based Approaches
A decision table for Excel report output, comparing COM automation, direct Open XML generation, template-based binding, and existing VBA ...
Pitfalls in COM, OCX, and ActiveX Development - Visual Studio Bitness, Registration, and Admin-Rights Traps
The traps that bite COM, OCX, and ActiveX work in practice: 32-bit/64-bit mismatches, regsvr32 vs Regasm, HKCU vs HKLM scope, and admin-r...
Where Should Unit Tests End and Integration Tests Begin - Drawing the Boundary and a Practical Decision Table
A practical guide for engineers on how to split responsibilities between unit and integration tests, organized around judgment vs. connec...
What VBA Is: Limits, Future Outlook, When to Replace It, and Practical Migration Patterns
A practical view of where VBA fits, where its limits are, and how to split responsibilities between desktop, cloud, and .NET so you can m...
Checklist for Safe Child-Process Handling in Windows Apps - Best Practices for Job Objects, Exit Propagation, stdio, and Watchdogs
A design guide for safe child-process handling on Windows, organized around four axes - process-tree ownership, exit propagation, stdio, ...
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
We support Windows desktop applications that involve resident processing, device integration, operational logging, and maintainable structure.
Legacy Asset Reuse & Migration Support
We help plan staged migration while continuing to reuse COM / ActiveX / OCX assets, native code, and 32-bit dependencies.