Is there a way to expose a .NET Core WPF app to other processes with COM?
See the question and my original answer on StackOverflowThere are complex ways with COM, CLSIDs, IIDS, registry registration, proxies, interfaces, the whole shebang... but I will show you an easier way using the Running Object Table ("ROT")
Here is a sample WPF app that contains all what you need, pure C# code, no registration needed.
The app registers your live object in the ROT. You can check it's there using this ROTVIEW tool (run it as x64 if your process/OS is x64)
This is how your live object registration appears
Here is the VBScript to call it (make sure the WPF app is running)
Set app = GetObject("x:\somepath")
WScript.Echo app.ToUpperCase("hello")
The path here is artificial it doesn't need to point to a real file, it's just so VBScript can pick it. Outputs:
And here is the WPF app code:
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Windows;
using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
namespace MyWpfApp
{
public partial class MainWindow : Window
{
private readonly int _cookie;
private readonly MyComObject _myComObject;
public MainWindow()
{
InitializeComponent();
// register my object, the path is just to be able to get it from VBS
_myComObject = new();
RunningObjectTable.RegisterFile(_myComObject, @"x:\somepath");
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
// revoke the object
if (_cookie != 0)
{
RunningObjectTable.Revoke(_cookie);
}
}
}
[ComVisible(true)]
// VBScript only knows IDispatch
[ClassInterface(ClassInterfaceType.AutoDispatch)]
public class MyComObject
{
// add any public method here
// note you are limited to COM automation types
public string? ToUpperCase(string? text) => text?.ToUpper();
}
public static partial class RunningObjectTable
{
public static int RegisterFile(object instance, string filePath, ROTFLAGS flags = ROTFLAGS.ROTFLAGS_REGISTRATIONKEEPSALIVE)
{
Marshal.ThrowExceptionForHR(GetRunningObjectTable(0, out var table));
Marshal.ThrowExceptionForHR(CreateFileMoniker(filePath, out var mk));
Marshal.ThrowExceptionForHR(table.Register(flags, instance, mk, out var cookie));
return cookie;
}
public static void Revoke(int cookie)
{
Marshal.ThrowExceptionForHR(GetRunningObjectTable(0, out var table));
table.Revoke(cookie);
}
[DllImport("ole32")]
private static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable pprot);
[DllImport("ole32", CharSet = CharSet.Unicode)]
private static extern int CreateFileMoniker(string lpszPathName, out IMoniker ppmk);
[ComImport, Guid("00000010-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IRunningObjectTable
{
[PreserveSig]
int Register(ROTFLAGS grfFlags, [MarshalAs(UnmanagedType.Interface)] object punkObject, IMoniker pmkObjectName, out int pdwRegister);
[PreserveSig]
int Revoke(int dwRegister);
[PreserveSig]
int IsRunning(IMoniker pmkObjectName);
[PreserveSig]
int GetObject(IMoniker pmkObjectName, [MarshalAs(UnmanagedType.Interface)] out object ppunkObject);
[PreserveSig]
int NoteChangeTime(int dwRegister, ref FILETIME pfiletime);
[PreserveSig]
int GetTimeOfLastChange(IMoniker pmkObjectName, out FILETIME pfiletime);
[PreserveSig]
int EnumRunning(out IEnumMoniker ppenumMoniker);
}
}
[Flags]
public enum ROTFLAGS
{
ROTFLAGS_NONE = 0,
ROTFLAGS_REGISTRATIONKEEPSALIVE = 1,
ROTFLAGS_ALLOWANYCLIENT = 2,
}
}