Using COM in NativeAOT

Andrii Kurdiumov
4 min readApr 23, 2021

--

A lot of people trying to use WinForms or WPF in NativeAOT and immidiately hit the roadblock — COM is not supported by Native AOT. Fortunately, after https://github.com/dotnet/runtimelab/pull/653 landed in the NativeAOT, the future seems a bit brighter. That PR unlocks ability to use RCW in the NativeAOT i.e — passing .NET to COM side.

Unfortunately, this is not yet enough for you to try WinForms or WPF application on NativeAOT, but be patient, changes are on the way.

So what can you do now with current state of COM support? And more importantly how?

In order to use COM in NativeAOT you have to use ComWrappers, feature introduced in the .NET 5 which allow application provide their own RCW and CCW for the runtime. I think this feature not very well documented so example how to use it would be handy.

Let’s create IMallocSpy implementation, so we can capture any COM memory allocations. To be true, this is a bit artificial example, but I cannot find something more practical to show RCW.

Let’s create IMallocSpy declaration.

Then I create implementation which just log what kind of memory would be allocated.

As you can see, implementation is pretty straightforward. It will be used from following test program:

Program just allocate some memory block using CoTaskMemAlloc, and release it using CoTaskMemFree. It is pretty standard code, with only one difference — ComWrappers.RegisterForMarshalling(new MallocSpyComWrapper()); . This is magic call which do the trick and allow custom COM proxy generator injects in the NativeAOT application. I do not yet provide implementation for that class. Before I provide implementation for that class, I will try to explain how ComWrappers wrap .NET object into COM object.

Let’s take a closer look at CoRegisterMallocSpy(spy) call. This call in order to marshall .NET object to COM, find globally registered ComWrapper, registered using ComWrappers.RegisterForMarshalling . This is instance of our future MallocSpyComWraper . Then runtime calls GetOrCreateComInterfaceForObject(spy, CreateComInterfaceFlags.None) which produce pointer (in form of IntPtr) to COM object. Construction of this object take following steps:

  1. Create Virtual Table (Vtbl) for COM object. Creation of that Vtbl is my responsibility as developer. I implement that by overriding ComputeVtables . I provide list of Vtbl which would be exposed by .NET object. In our case just 1 Vtbl needed.
  2. Then runtime create ManagedObjectWrapper structure in the heap. This structure provide basic services for COM runtime, and you can think about it as about implementation of COM object itself. This class hold GC handle on the marshalled object and created Vtbl at step 1. At this stage we have proper IUnknownimplementation. Note: ManagedObjectWrapper is internal implementation details, do not rely too much on its structure.
  3. Then created IUnknown implementation converted to desired interface using QueryInterface . And that pointer passed to native side.

Now let’s refresh a bit how VTbl works in COM. VTbl in COM is just array of pointers to functions which represents interface + pointer to this at index -1. When I say index -1 I mean, that pointer to this lays in memory before actual array of function addresses. this provided by runtime, but actual array of pointers to functions provided by developer (see step 1 above).

This leads to following structures which can represents our VTbl:

As you can see, I try here to reflect that IMallocSpy inherits from IUnknown .

Now it’s time to implement our ComWrappers class.

Basically implementation of ComWrappers which supports RCW create do following:

  • Allocate type associated memory of Vtbl
  • Populate Vtbl with pointers to managed function (basically unmanaged delegates)

The actual implementation of functions for COM interface is pretty mundane.

I group them in the IMallocSpyProxy class, just for convenience, in case I have more then one interface for which I want to have proxy.

As shown above, implementation of methods for COM interface is

  • Obtain managed object from COM this pointer using ComIntefaceDispatch.GetInstance<T> ,
  • then doing actual managed call, and if needed return results back.

Once you combine everything together, you will be able compile application inside NativeAOT.

Source code for article can be found at kant2002/CoMallocNativeAot (github.com).

Be part of the change what you want to see.

Free you CPU from JIT-overlords!

--

--

Andrii Kurdiumov

Math lover, lost in the woods of software development