Build native in-process COM servers (DLLs) on .NET with Native AOT. This library + source generator removes most of the boilerplate for COM activation, class factories, and DLL exports, making it practical to ship shell extensions and other in-proc COM components written in C#.
Windows-only. Requires .NET 8 or later and NativeAOT publishing.
Bluehill.NativeCom.IClassFactory— COMIClassFactory([GeneratedComInterface]) withCreateInstanceandLockServer.Bluehill.NativeCom.ClassFactoryAttribute<T>— annotate a factory class to wire it to a target COM class.Bluehill.NativeCom.DllHelper— helper that implements CreateInstance for your target class usingStrategyBasedComWrappers.- Source generator (
Bluehill.NativeCom.SourceGenerator) that:- Validates your setup and reports diagnostics.
- Generates partial implementation of your factory (method bodies for
CreateInstanceandLockServer). - Emits
UnmanagedCallersOnlyexports:DllGetClassObjectandDllCanUnloadNow.
- Windows
- .NET 8+ (recommended TargetFramework: net8.0-windows)
- Native AOT publishing
- C# 12 (default in .NET 8)
Add the following packages to your class library project:
- Bluehill.NativeCom
- Bluehill.NativeCom.SourceGenerator
You can add them via NuGet Package Manager or dotnet CLI.
-
Enable NativeAOT and unsafe code in your .csproj:
<PropertyGroup> <TargetFramework>net8.0-windows</TargetFramework> <PublishAot>true</PublishAot> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> </PropertyGroup>
-
Define COM interface(s) using the new source-generated COM attributes:
using System; using System.Runtime.InteropServices; using System.Runtime.InteropServices.Marshalling; [GeneratedComInterface] [Guid("a08ce4d0-fa25-44ab-b57c-c7b1c323e0b9")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] public partial interface IExplorerCommand { { ... }
-
Implement the COM class that provides the behavior. It must be annotated with
[GeneratedComClass]and have a CLSID via[Guid]:using System; using System.Runtime.InteropServices; // for [Guid] using System.Runtime.InteropServices.Marshalling; [GeneratedComClass] [Guid("22222222-2222-2222-2222-222222222222")] // CLSID used to activate this class public partial class ExplorerCommand : IExplorerCommand { ... }
-
Declare a factory class and connect it to your target via
[ClassFactory<TTarget>]. Do not implement the methods yourself — the generator emits the method bodies.using Bluehill.NativeCom; using System.Runtime.InteropServices.Marshalling; [GeneratedComClass] [ClassFactory<ExplorerCommand>] public partial class ExplorerCommandFactory : IClassFactory { // The generator provides CreateInstance(...) and LockServer(...). }
-
Publish with NativeAOT:
# From your project directory dotnet publish -c Release
The native DLL (your COM server) will be in the publish\native folder.
- The source generator scans for classes annotated with
[ClassFactory<TTarget>]. It validates that:- The factory class implements
Bluehill.NativeCom.IClassFactory. - Both the factory and the target class have
[GeneratedComClass]. - The target class has a
[Guid]attribute (its CLSID).
- The factory class implements
- For each valid pair, the generator produces:
- A partial factory implementation that forwards CreateInstance to
DllHelper.CreateInstanceHelper<TTarget>(). - A partial LockServer method that do nothing.
- A partial factory implementation that forwards CreateInstance to
- It also generates an internal static Dll class with
UnmanagedCallersOnlyexports:DllGetClassObject: routes the requested CLSID to the correct factoryCreateInstance.DllCanUnloadNow: returnsS_FALSE(1), since .NET Native AOT DLLs cannot be unloaded.
If you need to provide your own exports implementation, define the symbol NO_GENERATE_DLLGCO in your project. Example:
<PropertyGroup>
<DefineConstants>$(DefineConstants);NO_GENERATE_DLLGCO</DefineConstants>
</PropertyGroup>With this symbol defined, the generator will NOT emit DllGetClassObject.
The generator reports clear diagnostics to help you fix setup issues:
NATIVECOM0001: Required type '{0}' couldn't be found.NATIVECOM0002: Factory class does not implement IClassFactory.NATIVECOM0003: Factory class does not have GeneratedComClassAttribute.NATIVECOM0004: Target class does not have GeneratedComClassAttribute.NATIVECOM0005: Target class does not have GuidAttribute.
This package generates DllGetClassObject and DllCanUnloadNow, but it does NOT generate DllRegisterServer/DllUnregisterServer. You are responsible for registration (e.g., via an installer, script, or custom code). At minimum, in-proc registration requires keys like:
- HKCR\CLSID{Your-Target-Class-CLSID}\ (default: your class name or description)
- HKCR\CLSID{Your-Target-Class-CLSID}\InprocServer32\ (default: full path to your compiled DLL, ThreadingModel=Both) Ensure you register the DLL for the correct bitness (x64 vs x86) and that your process matches. Shell extensions typically require additional shell-specific registration.
- In-process servers only (DLL). No out-of-proc server support.
- Aggregation is not supported (
CreateInstancerejects non-null pUnkOuter). - NativeAOT is required to export
UnmanagedCallersOnlyentry points. - This library relies on .NET source-generated COM (
GeneratedComInterface/GeneratedComClass) available in .NET 8+.
MIT License — see LICENSE.