Search code examples
c#com.net-6.0out-of-process

Is there a way to extend a .net core WPF with COM?


I have a .net 6.0 MVVM WPF application that is used to walk users through an onboarding workflow. I now need to interface with this application from a legacy external process that was written in VBScript. My assumption is that this could be achieved with a COM object. I need to be able to miniplate the WPF application from the exposed COM object, like increment a counter, display a status message. I have seen Microsoft EXEs do this, registering themselves as a COM object at runtime, and then unregistering when exited.

While I'm pretty well versed in c# and web based development, I've never touched COM and have spend the last 5 days reading and trying to wrap my head around its capabilities to map my requirements back to a specific COM implementation. I'm struggling. Most of the more comprehensive explanations and walkthroughs are now pushing 20 years old and capabilities are clearly different between .net 4.7 and (core) 6.0. Based on my reading, I think what I need to implement is an "out-of-process" EXE. But how this then manifests as a consumable COM object for VB is where it all breaks down. I feel like this is where "marshaling" a "proxy" come in, but its still over my head.

My initial attempts in .net 6.0 all failed, so I went back to 4.7 just to see if I could mimic the desired behavior and wrap my head around it. Starting with a POC:

    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    [Guid("337FF551-2BDA-47B3-B5EE-6DE8171012A2")]
    public interface ITestCom
    {
        void runtest();
    }

    [ComVisible(true)]
    [Guid("5615E7EA-6F89-4474-AFD6-2DD4E52996F6")]
    [ClassInterface(ClassInterfaceType.None)]
    [ProgId("TestCom.Tester")]
    public class TestCom : ITestCom
    {
        public void runtest()
        {
            Debug.WriteLine("Hello World.");
            Console.WriteLine("Test Complete");
        }

    }

Which yielded a EXE that could be registered with regasm, but then acted like a .net Library in that there was no interaction between the EXE (server?) when running and a vbs script which instantiated the object. So, thinking out loud here, this ultimately is an in-process implementation, which doesn't even require the exe to be running. And when looking at the CLSID in the registry doesn't list a LocalServer32 key.

The most informative explanation of COM I've found has been this article which was written in 2006 and of course is referencing .net 4.7. Reading it, I'm without enough perspective to know what is still relevant and if more straight forward implementations have been adopted in the last 17 years.
https://www.codeproject.com/Articles/12579/Building-COM-Servers-in-NET

While my ultimate goal is to implement extensibility via a COM object for my .net core WPF, at this point I need to know if I'm even on the right path. If that means a POC in 4.7 as a starting point, I'll take it. I have looked at many code samples on github, and from MS articles and struggled to find a simple implementation of what I hope to accomplish I have seen other information on StackOverflow that maybe suggests that this was a pattern that was only exposed using ActiveX.Net, which feels like another can of worms.

I'd be grateful for any help.


Solution

  • The answer is based on the example project from the https://github.com/dotnet/samples/tree/main/core/extensions/OutOfProcCOM

    Steps:

    • download example

    • modify as described in 64bit C# .Net Wrapper for 32bit C# .Net DLL (update to net6 and so on)

    • additionally set <PlatformTarget>x64</PlatformTarget> for the ExeServer.csproj (or leave x86, but use the SysWOW64 registry key below. And don't use SysWOW64 when you will going to change registration code as Windows performs transparent redirection))

    • build ExeServer as described in the readme

    • register ExeServer /regserver (from Administrator command prompt)

    • import the following reg file into the registry. This will associate a ProgId with our exe server

      Windows Registry Editor Version 5.00
      
      [HKEY_CLASSES_ROOT\ExeServer.Application]
      @="ExeServer.Application"
      
      [HKEY_CLASSES_ROOT\ExeServer.Application\CLSID]
      @="{AF080472-F173-4D9D-8BE7-435776617347}"
      

      Please note that guid is exactly the same as value of the Contract.Constants.ServerClass constant. The ProgId ExeServer.Application may be whatever you want.

    • run the following VBS

      MsgBox(CreateObject("ExeServer.Application").ComputePi())
      

      and get the message box with value of pi.

    • do not forget to unregister the test server with ExeServer /unregserver

    Manually adding registry keys is just a quick hack to test the idea. For real solution you need to modify the COMRegistration.LocalServer.Register and COMRegistration.LocalServer.Unregister code to add/delete such a key.