How to write a DCOM server in C#

Updated 12/06/2006: states that PInvoking CoRegisterClassObject is a technique which is not supported.

Well, why DCOM and not .NET Remoting? For one thing, DCOM offers a secure interprocess communication channel through TCP/IP... which .NET remoting doesn't have unfortunately. Also, a DCOM server can be hosted in almost any process, including Windows Services!

The ideas are described below (this is pretty straightforward assuming you already know COM)
1) Your server process will expose a COM class factory that would just create your .NET object.
2) In COM you register the class factory using the standard CoRegisterClassObjects API
3) Make sure you call CoInitializeSecurity on your first process, for example to allow only Administrators to call in
4) Register your .NET assemblies with REGASM.EXE. Make sure your .NET class is visible through COM so CCW can be created around it (more details in MSDN on COM Interop section).
5) Remove the auto-generated InprocServer32 key after registration (REGASM puts it there but we are going out-of-proc)
6) Add the standard LocalServer32 / AppID registry keys.

That's it! Now you have a secure DCOM service implemented entirely in C# 🙂

Here is the C# source code:

using System;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.ServiceProcess;
using System.Threading;
using System.Runtime.InteropServices;

namespace Test
// .NET class, interface exposed through DCOM

// exposed COM interface
[GuidAttribute(MyService.guidIMyInterface), ComVisible(true)]
public interface IMyInterface
string GetDateTime(string prefix);

// exposed COM class
[GuidAttribute(MyService.guidMyClass), ComVisible(true)]
public class CMyClass: IMyInterface
// Print date & time and the current EXE name
public string GetDateTime(string prefix)
Process currentProcess = Process.GetCurrentProcess();
return string.Format("{0}: {1} [server-side COM call executed on {2}]",
prefix, DateTime.Now, currentProcess.MainModule.ModuleName);

// My hosting Windows service
internal class MyService :
public MyService()
// Initialize COM security
Thread.CurrentThread.ApartmentState = ApartmentState.STA;
UInt32 hResult = ComAPI.CoInitializeSecurity(
IntPtr.Zero, // Add here your Security descriptor
if (hResult != 0)
throw new ApplicationException(
"CoIntializeSecurity failed" + hResult.ToString("X"));

// The main entry point for the process
static void Main()
ServiceBase.Run(new ServiceBase[] { new MyService() });

/// On start, register the COM class factory

protected override void OnStart(string[] args)
Guid CLSID_MyObject = new Guid(MyService.guidMyClass);
UInt32 hResult = ComAPI.CoRegisterClassObject(
ref CLSID_MyObject,
new MyClassFactory(),
out _cookie);
if (hResult != 0)
throw new ApplicationException(
"CoRegisterClassObject failed" + hResult.ToString("X"));
/// On stop, remove the COM class factory registration

protected override void OnStop()
if (_cookie != 0)
private int _cookie = 0;

// Public constants
public const string serviceName = "MyService";
public const string guidIMyInterface = "e88d15a5-0510-4115-9aee-a8421c96decb";
public const string guidMyClass = "f681abd0-41de-46c8-9ed3-d0f4eba19891";

// Standard installer
public class MyServiceInstaller :
public MyServiceInstaller()
processInstaller = new ServiceProcessInstaller();
serviceInstaller = new ServiceInstaller();
// Add a new service running under Local SYSTEM
processInstaller.Account = ServiceAccount.LocalSystem;
serviceInstaller.StartType = ServiceStartMode.Manual;
serviceInstaller.ServiceName = MyService.serviceName;
private ServiceInstaller serviceInstaller;
private ServiceProcessInstaller processInstaller;

// Internal COM Stuff


/// P/Invoke calls

internal class ComAPI
public static extern UInt32 CoInitializeSecurity(
IntPtr securityDescriptor,
Int32 cAuth,
IntPtr asAuthSvc,
IntPtr reserved,
UInt32 AuthLevel,
UInt32 ImpLevel,
IntPtr pAuthList,
UInt32 Capabilities,
IntPtr reserved3
[DllImport ("ole32.dll")]
public static extern UInt32 CoRegisterClassObject (
ref Guid rclsid,
[MarshalAs (UnmanagedType.Interface)]IClassFactory pUnkn,
int dwClsContext,
int flags,
out int lpdwRegister);
[DllImport ("ole32.dll")]
public static extern UInt32 CoRevokeClassObject (int dwRegister);
public const int RPC_C_AUTHN_LEVEL_PKT_PRIVACY = 6; // Encrypted DCOM communication
public const int RPC_C_IMP_LEVEL_IDENTIFY = 2; // No impersonation really required
public const int CLSCTX_LOCAL_SERVER = 4;
public const int REGCLS_MULTIPLEUSE = 1;
public const int EOAC_DISABLE_AAA = 0x1000; // Disable Activate-as-activator
public const int EOAC_NO_CUSTOM_MARSHAL = 0x2000; // Disable custom marshalling
public const int EOAC_SECURE_REFS = 0x2; // Enable secure DCOM references
public const int CLASS_E_NOAGGREGATION = unchecked((int)0x80040110);
public const int E_NOINTERFACE = unchecked((int)0x80004002);
public const string guidIClassFactory = "00000001-0000-0000-C000-000000000046";
public const string guidIUnknown = "00000000-0000-0000-C000-000000000046";


/// IClassFactory declaration

[ComImport (), InterfaceType (ComInterfaceType.InterfaceIsIUnknown),
Guid (ComAPI.guidIClassFactory)]
internal interface IClassFactory
int CreateInstance (IntPtr pUnkOuter, ref Guid riid, out IntPtr ppvObject);
int LockServer (bool fLock);


/// My Class factory implementation

internal class MyClassFactory : IClassFactory
public int CreateInstance (IntPtr pUnkOuter,
ref Guid riid,
out IntPtr ppvObject)
ppvObject = IntPtr.Zero;
if (pUnkOuter != IntPtr.Zero)
Marshal.ThrowExceptionForHR (ComAPI.CLASS_E_NOAGGREGATION);
if (riid == new Guid(MyService.guidIMyInterface)
|| riid == new Guid(ComAPI.guidIUnknown))
// Create the instance of my .NET object
ppvObject = Marshal.GetComInterfaceForObject(
new CMyClass(), typeof(IMyInterface));
Marshal.ThrowExceptionForHR (ComAPI.E_NOINTERFACE);
return 0;
public int LockServer (bool lockIt)
return 0;

The registration CMD script:

set EXE_FULL_PATH=%~dp0windowsservice1.exe
if not exist %EXE_FULL_PATH% @echo Executable %EXE_FULL_PATH% not present in the current directory! & @goto :EOF
installutil /u %EXE_FULL_PATH%
installutil %EXE_FULL_PATH%
regasm %EXE_FULL_PATH% /codebase
REG.EXE ADD HKCR\AppID\{9922b97d-ce4a-4cc8-a26f-4944708e652d} /v LocalService /t REG_SZ /d MyService /f
REG.EXE ADD HKCR\CLSID\{F681ABD0-41DE-46C8-9ED3-D0F4EBA19891}\LocalServer32 /ve /t REG_SZ /d %EXE_FULL_PATH% /f
REG.EXE DELETE HKCR\CLSID\{F681ABD0-41DE-46C8-9ED3-D0F4EBA19891}\InprocServer32 /f

And a test VBS script that will exercise our service from a separate process obviously:

Dim obj
Set obj = CreateObject( "Test.CMyClass" )
wscript.echo obj.GetDateTime("Current date: ")

One more comment: The code above allows everybody to call into the process. This is probably not very useful since you might want to allow only administrators to call into your service. There is a solution though: all you have to do is to pass a certain security descriptor to CoInitializeSecurity that will allow only certain classes of users to call into the process. This is actually not very hard, and I'll probably post a sample code in the future. Now I have to get back to work, so see you for the next time!


Comments (12)

  1. Joe says:

    Just because you can, does not mean you should.

  2. No, really, you should. DCOM isn’t dead, and it’s still the #1 most reliable, secure, binary format on Windows. It will be happily supported in Indigo. Remember that DCOM is a protocol in this sense.

    Good stuff Adi.

  3. John H. Bergman says:

    Very interesting.

    Can you tell me how you mark a .NET assembly DLL to run out of proc? I was hoping I could do something like running inside DLLHost, but I cannot seem to get it working.

  4. Clemens Vasters says:

    Enterprise Services, Enterprise Services, Enterprise Services.

  5. Paul Gielens says:

    Overhead, Overhead, Overhead.

    Just as useless as preaching Enterprise Services, arguments please.

  6. brian says:

    I’m unable to get this example to work, first I had a problem with Reg.exe not existing on my windows 2000 server machine and now when I start up myservice I get an exception indicating that "Class not registered"

  7. Sandeep says:

    Real good work

  8. Adi Oltean says:

    To respond to a comment from Brian:

    >>> I’m unable to get this example to work, first I had a problem with Reg.exe not existing on my windows 2000 server machine and now when I start up myservice I get an exception indicating that "Class not registered"

    I actually haven’t tried this on Windows 2000 – I tried it only on Windows Server 2003.

  9. Yuri says:

    Thanks a lot! Actually i implemented this not as a service but as a C# Forms application. People connect to it and it displays in real time some useful information. You saved me lots of time with your example. Thanks.


Skip to main content