Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

Building COM Servers in .NET

, 2 Feb 2006
Learn the fundamental principles of building COM DLL and EXE Servers using a .NET language.
buildcomserversindotnet_src.zip
SimpleCOMObject
CSharpExeCOMServers
interfaces
implementations
clients
SimpleCOMObject
SimpleCOMObject.def
SimpleCOMObject.rgs
SimpleCOMObject.snk
SimpleCOMObject1.rgs
SimpleCOMObjectps.def
SimpleCOMObject.vcproj.vspscc
SimpleCOMObjectPS.vcproj.vspscc
Interop.SimpleCOMObject.dll
CPP
C#
SimpleCOMObject_CPPImpl
SimpleCOMObject_CPPImpl.def
SimpleCOMObject_CPPImpl.rgs
SimpleCOMObject_CPPImpl1.rgs
SimpleCOMObject_CPPImplps.def
SimpleCOMObject_CPPImpl.vcproj.vspscc
SimpleCOMObject_CPPImplPS.vcproj.vspscc
SimpleCOMObject_CSharpImpl
KeyFile.snk
KeyFile.snk.bak_
SimpleCOMObject_CSharpImpl.csproj.user
VB
CPP
VBClient01
FormMain.frm
MSSCCPRJ.SCC
VBClient01.exe
VBClient01.vbw
VBClient01.vbp
CPPClient01
UsingStandardCOMInterop
IDotNetClassFactory
ManagedCOMLocalServer
implementations
clients
SimpleCOMObject_CSharpExeImpl
App.ico
keyfile.snk
SimpleCOMObject_CSharpExeImpl.csproj.user
CPP
CPPClient01
testobjects
interfaces
implementations
clients
interface
implementations
ITestCSharpObjectInterfaces
TestCSharpObjectInterfaces.csproj.user
TestCSharpObjectInterfaces.snk
ITestCSharpObjectInterfacesImpl01
App.ico
ITestCSharpObjectInterfacesImpl01.csproj.user
ITestCSharpObjectInterfacesImpl01.snk
IDotNetClassFactory
IDotNetClassFactory.csproj.user
IDotNetClassFactory.snk
IDotNetClassFactory_Impl01
IDotNetClassFactory_Impl01.csproj.user
IDotNetClassFactory_Impl01.snk
CPPClient01
Debug
ITestCSharpObjectInterfacesImpl01.exe
implementations
clients
ManagedCOMLocalServer_Impl01
App.ico
keyfile.snk
ManagedCOMLocalServer_Impl01.csproj.user
CPPClient01
CPPClient01.ncb
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using System.Text;
using System.Threading;

namespace ManagedCOMLocalServer_Impl01
{
	[Flags]
	enum COINIT : uint
	{
		/// Initializes the thread for multi-threaded object concurrency.
		COINIT_MULTITHREADED = 0x0,
		/// Initializes the thread for apartment-threaded object concurrency. 
		COINIT_APARTMENTTHREADED = 0x2,
		/// Disables DDE for Ole1 support.
		COINIT_DISABLE_OLE1DDE = 0x4,
		/// Trades memory for speed.
		COINIT_SPEED_OVER_MEMORY = 0x8
	}

	[Flags]
	enum CLSCTX : uint
	{
		CLSCTX_INPROC_SERVER    = 0x1, 
		CLSCTX_INPROC_HANDLER   = 0x2, 
		CLSCTX_LOCAL_SERVER     = 0x4, 
		CLSCTX_INPROC_SERVER16  = 0x8,
		CLSCTX_REMOTE_SERVER    = 0x10,
		CLSCTX_INPROC_HANDLER16 = 0x20,
		CLSCTX_RESERVED1        = 0x40,
		CLSCTX_RESERVED2        = 0x80,
		CLSCTX_RESERVED3        = 0x100,
		CLSCTX_RESERVED4        = 0x200,
		CLSCTX_NO_CODE_DOWNLOAD = 0x400,
		CLSCTX_RESERVED5        = 0x800,
		CLSCTX_NO_CUSTOM_MARSHAL= 0x1000,
		CLSCTX_ENABLE_CODE_DOWNLOAD = 0x2000,
		CLSCTX_NO_FAILURE_LOG   = 0x4000,
		CLSCTX_DISABLE_AAA      = 0x8000,
		CLSCTX_ENABLE_AAA       = 0x10000,
		CLSCTX_FROM_DEFAULT_CONTEXT = 0x20000,
		CLSCTX_INPROC           = CLSCTX_INPROC_SERVER|CLSCTX_INPROC_HANDLER,
		CLSCTX_SERVER           = CLSCTX_INPROC_SERVER|CLSCTX_LOCAL_SERVER|CLSCTX_REMOTE_SERVER,
		CLSCTX_ALL				= CLSCTX_SERVER|CLSCTX_INPROC_HANDLER
	}

	[Flags]
	enum REGCLS : uint
	{ 
		REGCLS_SINGLEUSE         = 0, 
		REGCLS_MULTIPLEUSE       = 1, 
		REGCLS_MULTI_SEPARATE    = 2, 
		REGCLS_SUSPENDED         = 4, 
		REGCLS_SURROGATE         = 8
	}

	// We import the POINT structure because it is referenced
	// by the MSG structure.
	[ComVisible(false)]
	[StructLayout( LayoutKind.Sequential )]
	public struct POINT 
	{
		public int X;
		public int Y;

		public POINT( int x, int y ) 
		{
			this.X = x;
			this.Y = y;
		}

		public static implicit operator Point( POINT p ) 
		{
			return new Point( p.X,  p.Y );
		}

		public static implicit operator POINT( Point p ) 
		{
			return new POINT( p.X, p.Y );
		}
	}

	// We import the MSG structure because it is referenced 
	// by the GetMessage(), TranslateMessage() and DispatchMessage()
	// Win32 APIs.
	[ComVisible(false)]
	[StructLayout(LayoutKind.Sequential)]
	public struct MSG
	{
		public IntPtr hwnd;
		public uint message;
		public IntPtr wParam;
		public IntPtr lParam;
		public uint time;
		public POINT pt;
	}

	// Note that ManagedCOMLocalServer_Impl01 is NOT declared as public.
    // This is so that it will not be exposed to COM when we call regasm
	// or tlbexp.
	class ManagedCOMLocalServer_Impl01
	{
		// CoInitializeEx() can be used to set the apartment model
		// of individual threads.
		[DllImport("ole32.dll")]
		static extern int CoInitializeEx(IntPtr pvReserved, uint dwCoInit);

		// CoUninitialize() is used to uninitialize a COM thread.
		[DllImport("ole32.dll")]
		static extern void CoUninitialize();

		// PostThreadMessage() allows us to post a Windows Message to
		// a specific thread (identified by its thread id).
		// We will need this API to post a WM_QUIT message to the main 
		// thread in order to terminate this application.
		[DllImport("user32.dll")]
		static extern bool PostThreadMessage(uint idThread, uint Msg, UIntPtr wParam,
			IntPtr lParam);

		// GetCurrentThreadId() allows us to obtain the thread id of the
		// calling thread. This allows us to post the WM_QUIT message to
		// the main thread.
		[DllImport("kernel32.dll")]
		static extern uint GetCurrentThreadId();

		// We will be manually performing a Message Loop within the main thread
		// of this application. Hence we will need to import GetMessage(), 
		// TranslateMessage() and DispatchMessage().
		[DllImport("user32.dll")]
		static extern bool GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin,
			uint wMsgFilterMax);

		[DllImport("user32.dll")]
		static extern bool TranslateMessage([In] ref MSG lpMsg);

		[DllImport("user32.dll")]
		static extern IntPtr DispatchMessage([In] ref MSG lpmsg);

		// Define two common GUID objects for public usage.
		public static Guid IID_IUnknown  = new Guid("{00000000-0000-0000-C000-000000000046}");
		public static Guid IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");

		protected static uint	m_uiMainThreadId;  // Stores the main thread's thread id.
		protected static int	m_iObjsInUse;  // Keeps a count on the total number of objects alive.
		protected static int	m_iServerLocks;// Keeps a lock count on this application.

		// This property returns the main thread's id.
		public static uint MainThreadId
		{
			get
			{
				return m_uiMainThreadId;
			}
		}

		// This method performs a thread-safe incrementation of the objects count.
		public static int InterlockedIncrementObjectsCount()
		{
			Console.WriteLine("InterlockedIncrementObjectsCount()");
			// Increment the global count of objects.
			return Interlocked.Increment(ref m_iObjsInUse);
		}

		// This method performs a thread-safe decrementation the objects count.
		public static int InterlockedDecrementObjectsCount()
		{
			Console.WriteLine("InterlockedDecrementObjectsCount()");
			// Decrement the global count of objects.
			return Interlocked.Decrement(ref m_iObjsInUse);
		}

		// Returns the total number of objects alive currently.
		public static int ObjectsCount
		{
			get
			{
				lock(typeof(ManagedCOMLocalServer_Impl01))
				{
					return m_iObjsInUse;
				}
			}
		}

		// This method performs a thread-safe incrementation the 
		// server lock count.
		public static int InterlockedIncrementServerLockCount()
		{
			Console.WriteLine("InterlockedIncrementServerLockCount()");
			// Increment the global lock count of this server.
			return Interlocked.Increment(ref m_iServerLocks);
		}

		// This method performs a thread-safe decrementation the 
		// server lock count.
		public static int InterlockedDecrementServerLockCount()
		{
			Console.WriteLine("InterlockedDecrementServerLockCount()");
			// Decrement the global lock count of this server.
			return Interlocked.Decrement(ref m_iServerLocks);
		}

		// Returns the current server lock count.
		public static int ServerLockCount
		{
			get
			{
				lock(typeof(ManagedCOMLocalServer_Impl01))
				{
					return m_iServerLocks;
				}
			}
		}

		// AttemptToTerminateServer() will check to see if 
		// the objects count and the server lock count has
		// both dropped to zero.
		// If so, we post a WM_QUIT message to the main thread's
		// message loop. This will cause the message loop to
		// exit and hence the termination of this application.
		public static void AttemptToTerminateServer()
		{
			lock(typeof(ManagedCOMLocalServer_Impl01))
			{
				Console.WriteLine("AttemptToTerminateServer()");

				// Get the most up-to-date values of these critical data.
				int iObjsInUse = ObjectsCount;
				int iServerLocks = ServerLockCount;

				// Print out these info for debug purposes.
				StringBuilder sb = new StringBuilder("");		  
				sb.AppendFormat("m_iObjsInUse : {0}. m_iServerLocks : {1}", iObjsInUse, iServerLocks);
				Console.WriteLine(sb.ToString());

				if ((iObjsInUse > 0) || (iServerLocks > 0))
				{
					Console.WriteLine("There are still referenced objects or the server lock count is non-zero.");
				}
				else
				{
					UIntPtr wParam = new UIntPtr(0);
					IntPtr lParam = new IntPtr(0);
					Console.WriteLine("PostThreadMessage(WM_QUIT)");
					PostThreadMessage(MainThreadId, 0x0012, wParam, lParam);
				}
			}
		}

		// ProcessArguments() will process the command-line arguments
		// of this application. 
		// If the return value is true, we carry
		// on and start this application.
		// If the return value is false, we terminate
		// this application immediately.
		protected static bool ProcessArguments(string[] args)
		{
			bool bRet = true;

			if (args.Length > 0)
			{
				RegistryKey key = null;
				RegistryKey key2 = null;

				switch (args[0].ToLower())
				{
					case "-embedding":
						Console.WriteLine("Request to start as out-of-process COM server.");
						break;

					case "-register":
					case "/register":
						try 
						{
							key = Registry.ClassesRoot.CreateSubKey("CLSID\\" + Marshal.GenerateGuidForType(typeof(SimpleCOMObject)).ToString("B"));
							key2 = key.CreateSubKey("LocalServer32");
							key2.SetValue(null, Application.ExecutablePath);
						} 
						catch (Exception ex)
						{
							MessageBox.Show("Error while registering the server:\n"+ex.ToString());
						}
						finally
						{
							if (key != null)
								key.Close();
							if (key2 != null)
								key2.Close();
						}
						bRet = false;
						break;

					case "-unregister":
					case "/unregister":
						try 
						{
							key = Registry.ClassesRoot.OpenSubKey("CLSID\\" + Marshal.GenerateGuidForType(typeof(SimpleCOMObject)).ToString("B"), true);
							key.DeleteSubKey("LocalServer32");
						} 
						catch (Exception ex)
						{
							MessageBox.Show("Error while unregistering the server:\n"+ex.ToString());
						}
						finally
						{
							if (key != null)
								key.Close();
							if (key2 != null)
								key2.Close();
						}
						bRet = false;
						break;

					default:
						Console.WriteLine("Unknown argument: " + args[0] + "\nValid are : -register, -unregister and -embedding");
						break;
				}
			}

			return bRet;
		}

		/// <summary>
		/// The main entry point for the application.
		/// </summary>
		[STAThread]
		static void Main(string[] args)
		{
			if (!ProcessArguments(args))
			{
				return;
			}

			// Initialize critical member variables.
			m_iObjsInUse = 0;
			m_iServerLocks = 0;
			m_uiMainThreadId = GetCurrentThreadId();

			// Register the SimpleCOMObjectClassFactory.
			SimpleCOMObjectClassFactory factory = new SimpleCOMObjectClassFactory();
			factory.ClassContext = (uint)CLSCTX.CLSCTX_LOCAL_SERVER;
			factory.ClassId = Marshal.GenerateGuidForType(typeof(SimpleCOMObject));
			factory.Flags = (uint)REGCLS.REGCLS_MULTIPLEUSE | (uint)REGCLS.REGCLS_SUSPENDED;
			factory.RegisterClassObject();
			ClassFactoryBase.ResumeClassObjects();

			// Start up the garbage collection thread.
			GarbageCollection	GarbageCollector = new GarbageCollection(1000);
			Thread				GarbageCollectionThread = new Thread(new ThreadStart(GarbageCollector.GCWatch));
			
			// Set the name of the thread object.
			GarbageCollectionThread.Name = "Garbage Collection Thread";
			// Start the thread.
			GarbageCollectionThread.Start();

			// Start the message loop.
			MSG			msg;
			IntPtr		null_hwnd = new IntPtr(0);
			while (GetMessage(out msg, null_hwnd, 0, 0) != false) 
			{
				TranslateMessage(ref msg);
				DispatchMessage(ref msg);
			}
			Console.WriteLine("Out of message loop.");

			// Revoke the class factory immediately.
			// Don't wait until the thread has stopped before
			// we perform revokation.
			factory.RevokeClassObject();
			Console.WriteLine("SimpleCOMObjectClassFactory Revoked.");

			// Now stop the Garbage Collector thread.
			GarbageCollector.StopThread();
			GarbageCollector.WaitForThreadToStop();
			Console.WriteLine("GarbageCollector thread stopped.");

			// Just an indication that this COM EXE Server is stopped.
			Console.WriteLine("Press [ENTER] to exit.");
			Console.ReadLine();
		}
	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Lim Bio Liong
Web Developer
Singapore Singapore
Lim Bio Liong is a Specialist at a leading Software House in Singapore.
 
Bio has been in software development for over 10 years. He specialises in C/C++ programming and Windows software development.
 
Bio has also done device-driver development and enjoys low-level programming. Bio has recently picked up C# programming and has been researching in this area.

| Advertise | Privacy | Mobile
Web03 | 2.8.140814.1 | Last Updated 2 Feb 2006
Article Copyright 2006 by Lim Bio Liong
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid