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 — COM IClassFactory ([GeneratedComInterface]) with CreateInstance and LockServer.
- Bluehill.NativeCom.ClassFactoryAttribute — annotate a factory class to wire it to a target COM class.
- Bluehill.NativeCom.DllHelper — helper that implements CreateInstance for your target class using StrategyBasedComWrappers.
- Source generator (Bluehill.NativeCom.SourceGenerator) that:
- Validates your setup and reports diagnostics.
- Generates partial implementation of your factory (method bodies for CreateInstance and, by default, LockServer).
- Emits UnmanagedCallersOnly exports: DllGetClassObject and DllCanUnloadNow.
- 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]. Do not implement the methods yourself — the generator emits the method bodies.
using Bluehill.NativeCom; using System.Runtime.InteropServices.Marshalling; [GeneratedComClass] [ClassFactory<Greeter>] public partial class GreeterFactory : IClassFactory { // The generator provides CreateInstance(...) and, by default, 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]. 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).
- For each valid pair, the generator produces:
- A partial factory implementation that forwards CreateInstance to DllHelper.CreateInstanceHelper().
- A partial LockServer method that increments/decrements a shared lock counter (unless you opt out; see below).
- It also generates an internal static Dll class with UnmanagedCallersOnly exports:
- DllGetClassObject: routes the requested CLSID to the correct factory CreateInstance.
- DllCanUnloadNow: returns S_OK (0) when there are no locks, otherwise S_FALSE (1).
If you need to provide your own exports or LockServer implementation, define the symbol NO_GENERATE_DLL_ENTRYPOINT_AND_LOCKSERVER in your project. Example:
<PropertyGroup>
<DefineConstants>$(DefineConstants);NO_GENERATE_DLL_ENTRYPOINT_AND_LOCKSERVER</DefineConstants>
</PropertyGroup>
With this symbol defined:
- The generator will NOT emit DllGetClassObject / DllCanUnloadNow.
- The generator will NOT emit LockServer in your factory class — you must implement it yourself to satisfy IClassFactory.
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 (CreateInstance rejects non-null pUnkOuter).
- NativeAOT is required to export UnmanagedCallersOnly entry points.
- This library relies on .NET source-generated COM (GeneratedComInterface/GeneratedComClass) available in .NET 8+.
MIT License — see LICENSE.