IMO, the easiest way to create a COM out-of-proc server in C# is to use a DLL surrogate process.
You're still limited to dual interfaces (ComInterfaceType.InterfaceIsDual
), and you'd need to register the generated type library (and do it as part of deployment, too):
C:WindowsMicrosoft.NETFrameworkv4.0.30319RegAsm.exe ManagedServer.dll /codebase /tlb
This will allow you to utilize the COM type library marshaller, because you don't have a dedicated COM proxy/stuf DLL for your C# COM objects.
Make sure to use the correct RegAsm.exe
binary, depending on the target bit-ness of your ManagedServer.dll
assembly. The above assumes x86 code.
Here is a complete working template example. It takes care of the surrogate registration:
using Microsoft.Win32;
using System;
using System.Runtime.InteropServices;
namespace ManagedServer
{
[ComVisible(true), Guid("1891CF89-1282-4CA8-B7C5-F2608AF1E2F1")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IManagedComObject
{
string ComMethod(string data);
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IManagedComObject))]
[Guid("989162CD-A6A6-4A7D-A7FB-C94086A4E90A")]
[ProgId("Noseratio.ManagedComObject")]
public class ManagedComObject : IManagedComObject
{
// public constructor
public ManagedComObject()
{
}
// IManagedComObject
public string ComMethod(string data)
{
return data;
}
// registration
[ComRegisterFunction()]
public static void Register(Type type)
{
var guid = type.GUID.ToString("B");
using (var appIdKey = Registry.ClassesRoot.CreateSubKey(@"AppID" + guid))
{
appIdKey.SetValue("DllSurrogate", String.Empty);
}
using (var appIdKey = Registry.ClassesRoot.CreateSubKey(@"CLSID" + guid))
{
appIdKey.SetValue("AppId", guid);
}
}
[ComUnregisterFunction()]
public static void Unregister(Type type)
{
var guid = type.GUID.ToString("B");
using (var appIdKey = Registry.ClassesRoot.OpenSubKey(@"AppID" + guid, writable: true))
{
if (appIdKey != null)
appIdKey.DeleteValue("DllSurrogate", throwOnMissingValue: false);
}
Registry.ClassesRoot.DeleteSubKeyTree(@"CLSID" + guid, throwOnMissingSubKey: false);
}
}
}
By default, the objects will be created in MTA apartment, so the interface methods may possibly be called on any thread, you'd need to implement thread safety.
If you need an STA thread with message pump inside the surrogate process for your objects, you could do that explicitly by implementing a factory singleton and using CoMarshalInterThreadInterfaceInStream
/CoGetInterfaceAndReleaseStream
to export objects outside the STA thread (this might be related).
Another point, your COM client code should use CLSCTX_LOCAL_SERVER
when creating this an instance of ManagedComObject
. This is not the case with Activator.CreateInstance(Type.GetTypeFromProgID("Noseratio.ManagedComObject"))
, which apparently uses CLSCTX_ALL
. This can be easily taken care of:
using System;
using System.Runtime.InteropServices;
namespace Client
{
class Program
{
static void Main(string[] args)
{
// dynamic obj = Activator.CreateInstance(Type.GetTypeFromProgID("Noseratio.ManagedComObject"));
dynamic obj = ComExt.CreateInstance(
Type.GetTypeFromProgID("Noseratio.ManagedComObject").GUID,
localServer: true);
Console.WriteLine(obj.ComMethod("hello"));
}
}
// COM interop
public static class ComExt
{
const uint CLSCTX_LOCAL_SERVER = 0x4;
const uint CLSCTX_INPROC_SERVER = 0x1;
static readonly Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046");
[DllImport("ole32.dll", ExactSpelling = true, PreserveSig = false)]
static extern void CoCreateInstance(
[MarshalAs(UnmanagedType.LPStruct)] Guid rclsid,
[MarshalAs(UnmanagedType.IUnknown)] object pUnkOuter,
uint dwClsContext,
[MarshalAs(UnmanagedType.LPStruct)] Guid riid,
[MarshalAs(UnmanagedType.Interface)] out object rReturnedComObject);
public static object CreateInstance(Guid clsid, bool localServer)
{
object unk;
CoCreateInstance(clsid, null, localServer ? CLSCTX_LOCAL_SERVER : CLSCTX_INPROC_SERVER, IID_IUnknown, out unk);
return unk;
}
}
}