Writing Your Own COM Interop in C#

Introduction

Although the automatic creation of COM interop DLLs is very useful, it is not perfect.

In the case of interfacing with OLE conforming COM objects (for example, interfaces derived from IDispatch and method parameters that are part of the VARIANT structure), the interop DLL automatically created by VisualStudio (or rather by TlbImp.exe, the application that generates the interop DLL) is more often than not sufficient.

However, this method does have its drawbacks. First, it requires the creation of an additional DLL for each COM DLL.

Second, if you need to use IUnknown-based interfaces that may not even be defined in the type library, no automatic interop DLL can be created.

Third, the marshalling of method parameters in such cases is often incorrect, rendering the automatic interop DLL useless.

And finally, there’s no way to group the objects into seperate namespaces—meaning the interop DLL for a COM DLL containing many objects tends to create a huge jumble of classes with no clear ordering. One way around this problem is to write a wrapper in C++.NET, but this still requires an additional interfacing DLL to exist in between the C# client and the COM DLL.

In this article, I will explore how to use P/Invoke, and the COM interop classes to gain access to COM objects and present a class structure to ease the process. I will concentrate on COM inproc-servers in this article, but the methods described here can be applied to any type of server—even DCOM-based servers.

Instansiating a COM Object

How do you instansiate a COM object? In actual fact, the same way that you do in C++—by using the CoCreateInstance method. This is defined in ole32.dll.

For information about the CoCreateInstance method, see the following Web site:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/com/htm/cmf_a2c_1nad.asp.

The interop declaration is as follows:

public class Ole32Methods
{
   [DllImport("ole32.Dll")]
   static public extern uint CoCreateInstance(ref Guid clsid,
      [MarshalAs(UnmanagedType.IUnknown)] object inner,
      uint context,
      ref Guid uuid,
      [MarshalAs(UnmanagedType.IUnknown)] out object rReturnedComObject);
}

A point to note is the [MarshalAs(UnmanagedType.IUnknown)] attributes. These inform the P/Invoke mechanism that the objects are in fact IUnknown interfaces and should be treated as such. The result of these attributes is to wrap up the ‘object’ parameters in an internal .NET class that handles the IUnknown methods of AddRef, QueryInterface, and Release (__ComObject). Therefore, because P/Invoke knows that the return object is in fact an IUnknown, its Release method will be called when the object is finalized.

If the COM object doesn’t exist, or there was an error in the creation of the COM object, the rReturnedComObject parameter will be set to null and an error code (for example, a value not equal to zero) will be returned from the function. The Guid struct is already defined in .NET, and is synonymous with the GUID struct in C++.

Finally, I’ve labelled rReturnedComObject as ‘out’ and not ‘ref’. This indicates that the object returned is created inside of the method. An example of the code to instansiate a COM object using this method is shown below.

static bool CreateObjectExample()
{
   const uint CLSCTX_INPROC_SERVER = 1;

   // CLSID of the COM object
   Guid clsid = new Guid("F333F56A-B59D-41FE-822D-27989266A535");

   // GUID of the required interface
   Guid IID_IUnknown = new Guid("00000000-0000-0000-C000-000000000046");

   object instance = null;

   uint hResult = Ole32Methods.CoCreateInstance(ref clsid, null,
                  CLSCTX_INPROC_SERVER, ref IID_IUnknown, out instance);

   return hResult == 0;
}

Unfortunately, the object that is returned isn’t much use at present. It effectively represents an instance of the COM object without any interfaces, or at least with just an IUnknown interface to which you don’t have access.

Defining Interfaces

You need to manually define each interface on the COM object. These can be derived from the .idl of the COM DLL in question. In defining the methods of the interfaces, standard P/Invoke marshalling applies. Consider the following method definition as it would appear in a .idl file.

[
   object,
   uuid(F333F56A-B59D-41FE-822D-27989266A535),
        pointer_default(unique)
]
interface ITestInterface1 : IUnknown
{
   HRESULT GetString(LPSTR szString, int * pnLength);
};

Below is shown the C# definition of this interface using P/Invoke.

[Guid("F333F56A-B59D-41FE-822D-27989266A535"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface ITestInterface1
{
   int GetString([MarshalAs(UnmanagedType.LPStr)] StringBuilder builder,
                 ref int pnLength);
}

Now, examine the attributes attached to the interface itself. The Guid attribute is the ID of the interface. The InterfaceType attribute indicates the type of the interface—in this case, that it is an IUnknown-derived interface.

Note: P/Invoke has no way of determining the order of the methods in the interface, and so they must therefore be in the same order as in the .idl file.

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read