.NET Core 3 ❤️ COM

I started looking at .NET Core 3 when Microsoft announced that the existing .NET Framework would end at version 4.8. I’m traditionally a WinForms developer with a Xamarin twist, but the prospects of .NET Core really excite me, especially since .NET Core 3 will incorporate Windows Desktop (WinForms and WPF) and the drive by the community to improve it at it’s lowest levels is inspiring, really.

The process of porting a .NET WinForms app to .NET Core 3 is fairly simple and I’ll explore this in a future blog post. It’s all about the project file and upgrading dependencies. Although, like many other WinForms developers I have an existing project that interacts with COM components in both directions. That is where my .NET App invokes a COM method and the very same COM app can call back to my .NET project.

It seemed like quite a challenge to bring the new Windows Desktop framework in .NET Core 3 back to the heritage of Windows’ COM standard, I mean we’re talking about a concept and system design older than I am. My colleagues certainly laughed at the idea!

To start with, I installed the .NET Core 3 Preview 7 SDK on both my development machine and testing environment, this is because at the moment the runtime installer doesn’t include the Windows Desktop framework. One other thing you’ll need is the latest preview of Visual Studio 2019 which supports the SDK.

Once the SDK and VS 2019 previews are installed I was able to create my first COM component written in .NET Core. All I wanted to achieve on my first attempt was to show a message box from the .NET Core Windows Desktop framework but that proved a great learning experience.

For starters, .NET Core DLLs implement their own DllRegisterServer which means they can be registered within Windows using the traditional regsvr32 tool. The way this is managed is that alongside your .NET Core DLL, a library suffixed .comhost.dll is created which is the one that can be registered and effectively calls up your logic when invoked from COM.

So in order to create a COM Server we firstly have to start with a new .NET Core library. You can create this in Visual Studio. Immediately after opening the project, I modified the project file to add a new attribute into the PropertyGroup section. This is the EnableComHosting option and it needs setting to True to allow my library to behave as a COM Server.

<EnableComHosting>True</EnableComHosting>

Whilst I was in the Project file, I changed the SDK to use the Windows Desktop framework, just swap Microsoft.NET.Sdk for Microsoft.NET.Sdk.WindowsDesktop and add the below attribute into the same property group as before.

<UseWindowsForms>True</UseWindowsForms>

The next steps after enabling COM output in the project file is to actually expose a class to be called from a COM program. I’ll start by declaring an interface that my class will implement.

public interface IComMessageBox
{
  void ShowMessageBox(string title, string message);
}

The interface needs to be visible to COM too. This lets our COM client programs subscribe to it and see it. To make the interface visible to COM I firstly have to adorn it with the ComVisible attribute set as true. I will need to give it a unique identifier via the Guid attribute and finally, I will set the interface type as unknown which enables early binding. The attributes added to the class now look like this:

[ComVisible(true)]
[Guid("BE62FB2E-0029-4D6E-9703-39D4CAC888CE")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]

The next step is of course the class itself which shows the message box once invoked. This will need some similar attributes as the interface. The first is to mark it as ComVisible. I then added a new Guid attribute which must contain a different value to the interface. The ProgId is a unique identifier to late bind the program from. I need this because I’ll be using VB6 to invoke my messagebox demo as VB6/VBA are still commonly used in the enterprise and interoperability will be a common scenario.

The final attribute added to my class is the ClassInterface attribute which I have set to None in order to not have an interface generated for me and to specifically use the one I inherit from. The only thing left to do is show the message box which I have done via the System.Windows.Forms namespace, passing through my title and message as you can see below.

[ComVisible(true),
Guid("B8D9CB8F-E6A4-48A7-ACBF-FF0DCABB779E"),
ProgId("ComMessageBox.Display.1"),
ClassInterface(ClassInterfaceType.None)]
public class ComMessageBoxDisplay : IComMessageBox
{
    public void ShowMessageBox(string title, string message)
    {
        System.Windows.Forms.MessageBox.Show(title, message);
    }
}

If you are still following, you can now build the project which should generate a few files. These will be named after the name of your assembly output (or project name if you haven’t changed the assembly name). My assembly name is WinFormsCore so I get the below files compiled:

  • WinFromsCore.comhost.dll – This file is our COM server. It can be registered with regsvr32 to put the GUIDs we generated earlier into the Windows registry.
  • WinFormsCore.dll – This is our main library containing the message box display code. We can expand this to add forms and more.
  • WinFormsCore.deps.json – This JSON file describes the dependencies of our project and the runtime target for it. I’ll explore the two JSON files in a later blog post
  • WinFormsCore.runtimeconfig.json – This final important file contains the runtime configuration of the application. Things like the .NET Core version and architecture.

There is also my .pdb file generated and a dev runtime config which I don’t need to deploy for COM interoperability. Once I’m happy that these files are generated, I’ll copy them to a folder on my VB6 PC. This will usually be deployed via a setup program or other script, but I just copied over a Remote Desktop connection for my testing.

Once the files are on the target machine you can register the comhost DLL which will create some registry entries. Unfortunately the ProgID attribute I added earlier won’t be picked up in .NET Core 3 Preview 7 so that bit needs adding in manually. To register the comhost DLL just run regsvr32 in the command line as you would with any other COM DLL. Once this is done you should see a message similar to the below which will confirm the regsvr32 command ran successfully.

Now comes the complicated part. If we look in the registry straight away for my CLSID under HKEY_CLASSES_ROOT\Clsid, you’ll see it exists with an InProcServer32 key which points to the comhost file, and the description of the registry key is CoreCLR COMHost Server. The next thing to do is make the ProgID visible to COM so I can use it with the CreateObject command in VB6 / VBA or any language that doesn’t allow activation via the CLSID.

To link up the ProgID we need to do two things. The first is to create a ProgID key under my CLSID that I pointed out earlier, and set its default value to the string I want to use to create a new instance of the class. I have opted to use ComMessageBox.Display.1 for this purpose. This is how you should expect it to look:

I now need to create a corresponding key under HKEY_CLASSES_ROOT called ComMessageBox.Display.1 which points back to the CLSID. To do this, make a key under HKCR and call it the name of your ProgID, adding a CLSID key to it and setting the default value of CLSID to the Guid Attribute from earlier. I would expect it to look like this:

That’s the hard part done. The final step in my proof of concept is to instantiate the class from VB6 and call the ShowMessageBox method. Luckily VB6 supports late binding, so this bit is fairly easy.

Just declare an object and instantiate it via the CreateObject routine in the VB runtime. You can then call the ShowMessageBox method passing through a message and title as I have done in the below code snippet. Note that I did this in an event from a Command button which I placed on the form.

Private Sub Command1_Click()
  Dim comMessageBox As Object
  Set comMessageBox = CreateObject("ComMessageBox.Display.1")
  comMessageBox.ShowMessageBox "Hello from .NET Core 3 via VB6!", "Success"
End Sub

If I run the VB6 application, I get a button to click which shows my message box from .NET Core!

To summarise, I created a .NET Core DLL that was made visible to COM, I then registered it and created a ProgID to activate from VB6/VBA and tested out the proof of concept in a new VB6 application. Obviously the registry entries are a lot of effort and would usually be put into a script to create automatically.