Click here to Skip to main content
15,867,568 members
Articles / Programming Languages / C#
Article

SafeCOMWrapper - Managed Disposable Strongly Typed safe wrapper to late bound COM

Rate me:
Please Sign up or sign in to vote.
4.90/5 (58 votes)
19 Sep 2005CPOL10 min read 451.4K   2.1K   132   93
Make version independent COM wrapper using late bound calls yet providing strongly type and disposable interfaces. A version independent managed Outlook Automation Library.

Sample Image - SafeCOMWrapper.gif

Introduction

There are several problems using COM from .NET:

  • You cannot implement the Dispose pattern by utilizing the "using" block in order to safely dispose COM references.
  • You cannot guaranty COM references are finalized. There's no way to implement "~Destructor()" for COM references.
  • COM reference is not released when a call to Marshal.ReleaseComObject is skipped due to an Exception.
  • When you "Add Reference..." to a COM library, the reference is version specific. So, if you add a reference to the Office 2003 COM library, it does not work properly when deployed to Office 2000.
  • The only solution to version independent COM is to use Late Bound operations but you miss all the features of a strongly typed language.

Let's solve all these problems. We want to use a Managed strongly typed approach to Late Bound COM operations and also utilize the Dispose pattern on COM objects. The solution proposed here works for any COM object which can be Microsoft Office COM libraries, IE & DHTML objects and even your own COM objects. You should use this approach whenever you are dealing with any type of COM library.

Before going to the solution, let’s learn a bit of background on RealProxy and method interception.

Update

  • September 9, 2005: COM Event Support added. Thanks to Richard Deeming for adding the COM Event support and also the ByRef parameter support. He has made this a complete solution.

Introducing RealProxy and method interception

System.Runtime.Remoting.Proxies namespace offers us a class called RealProxy. You can use this class to create a proxy to any class and provide method interception. Method interception means you can intercept any method call to the target object. You can override RealProxy's Invoke method and any method called to the actual object is intercepted by the Invoke method of the proxy. Inside the Invoke method, you can either call the actual object's method, or can call a different method. You are free to do anything you like in this Invoke method.

Image 2

Figure 1: How proxy works

TransparentProxy is another type of proxy which is dynamically generated for a given type by the runtime. The actual method interception is done by the TransparentProxy. It is a hidden thing. You will never see its existence in the code. But this is the proxy which intercepts all method/property calls on a given type and then redirects the call to RealProxy. RealProxy itself does not intercept any method calls; the TransparentProxy actually provides this service of capturing a method call and calling the Invoke method of RealProxy.

RealProxy class provides a method GetTransparentProxy which you can use to create a transparent proxy for a given type. For example, if you are interested to intercept all method calls to the IList interface, you can create a TransparentProxy for the IList interface and all methods declared in the IList interface is intercepted by the TransparentProxy.

You can learn a lot about RealProxy from this MSDN TV show.

Extending RealProxy

You can extend RealProxy and make custom intercepting classes which intercept method calls to custom objects. For example, if you have a class named MyObject which is a regular class, you can create a MyObjectProxy extending the RealProxy and intercept all calls to MyObject’s instances and provide some custom services like logging, security check, resource cleanup etc.

Let’s see a simple example of method interception:

C#
public class MyObject : MarshalByRefObject
{
        public void DoSomething()
        {
               Debug.WriteLine("DoSomething called");
        }
}

This is a simple object. Note: The object extends MarhalByRefObject. This is a problem because we do not want to destroy our object model by extending from this class. We will find a solution to this problem soon.

Now we will be creating the proxy class:

C#
public class MyObjectProxy : RealProxy
{
        private MyObject _ActualObject;

        public static MyObject Create()
        {
               MyObject obj = new MyObject();
               MyObjectProxy proxy = new MyObjectProxy( obj );
               return proxy.GetTransparentProxy() as MyObject;
        }

        public MyObjectProxy(MyObject obj) : base( typeof(MyObject) )
        {
               _ActualObject = obj;
        }

Here we are creating a proxy object which remembers the reference to the actual object. We will need this reference when we will be calling the actual method during interception. When the proxy is created, we generate a dynamic transparent proxy and return the reference to the proxy instead of the actual object.

Image 3

Figure 2: Client sees the interface to the object, but it is actually interface to proxy.

Here’s how we use the class:

C#
[STAThread]
C#
public static void Main()
{
        MyObject obj = MyObjectProxy.Create();
        obj.DoSomething();
}

Now let’s look at the Invoke method where the actual work is done:

C#
public override IMessage Invoke(IMessage msg)
{
        Debug.WriteLine(" -- Intercepted -- ");
        
        // We are assuming it is a method call
        IMethodCallMessage callMessage = msg as IMethodCallMessage;

        // Do a lot of things here. Log the method call. You will get all the
        // arguments from the msg. Do security checks. Validate arguments.
        // Anything you can do here. The world is yours.

        // Call the actual method
        this._ActualObject.DoSomething();

        // Construct a return message which contains the return value 
        ReturnMessage returnMessage = new ReturnMessage( null, null, 
               0, callMessage.LogicalCallContext,
               callMessage );

        return returnMessage;
}

Let’s see what happens in this method:

  • Invoke method is called for any method or property access.
  • Information about the call is available in the IMessage message.
  • IMessage is converted to IMethodCallMessage which contains all the information about a method call.
  • We can write anything we want in this method before and after calling the actual object’s method.
  • Finally construct a return message which contains information about the return value (void functions have null return value) and all the out parameters.

No more MarshalByRef

We do not want to inherit our objects from the MarshalByRef object but still want to use RealProxy. The solution is to create an interface for the class we want to intercept. So, for our simple class, we will be creating an interface which declares all the public methods and properties:

C#
public interface IMyObject
{
    void DoSomething();
}
public class MyObject : IMyObject
{
    public void DoSomething()
    {
        Debug.WriteLine("DoSomething called");
    }
}

See, no more MarshalByRef.

Now we need to modify the Real Proxy a bit:

C#
public class MyObjectProxy : RealProxy
{
    private IMyObject _ActualObject;

    public static IMyObject Create()
    {
        MyObject obj = new MyObject();
        MyObjectProxy proxy = new MyObjectProxy( obj );
        return proxy.GetTransparentProxy() as IMyObject;
    }

    public MyObjectProxy(IMyObject obj) : base( typeof(IMyObject) )
    {
        _ActualObject = obj;
    }

The changes are pretty simple. We have replaced all MyObject with IMyObject.

So, the usage will also be changed this way:

C#
IMyObject obj = MyObjectProxy.Create();
obj.DoSomething();

Although introducing interface for each concrete class seems like a bad idea, actually it’s a very good idea to always have interfaces for your concrete classes and write code against the interfaces, not the concrete classes. All the design pattern books, object oriented purists will tell you the same. There is a long list of benefits of writing code against an interface than a concrete class. Hundreds of scenarios can be shown in favor of this idea. However, that’s not our discussion topic.

Making managed, disposable, strongly typed but late bound COM wrappers

Now I will be introducing an interesting concept which may be hard to grasp but is very simple. Here it goes:

We will make our own hand coded interfaces for COM objects.

Instead of adding references to COM objects from Visual Studio, we will create a similar interface by ourselves. So, let’s say we want to use Outlook’s COM interface. Instead of adding a reference to the Outlook COM library, we will make our own interface for the methods and properties we want to use. Hard to grasp? An example will make it easier.

Let’s make an interface for the Outlook.Application object:

C#
public interface Application : IDisposable
{
   string Name { get; }
   void Quit();
}

We are making a subset of the actual complex Outlook.Application object. There are three reasons for doing so:

  • We are not interested to use all the functionality exposed by the COM object.
  • We want to make a version independent interface for Outlook. Only the methods and properties that we expect in all versions of Outlook will be in this interface.
  • We need the IDisposable interface so that we can use the using construct. If we add a reference to Outlook’s library, we cannot modify it and extend the interfaces from the IDisposable interface.

Now comes the great DisposableCOMProxy. It has four responsibilities:

  • Create COM objects.
  • Provide a disposable interface for COM so that you can use the COM object inside a using block.
  • Safely dispose COM reference.
  • Provide strongly typed interfaces to COM type but still perform late bound calls to the actual COM object.
C#
public class DisposableCOMProxy : RealProxy
{
        public object COM;

        /// <summary>
        /// We will be using late bound COM operations. The COM object
        /// is created from the Prog ID instead of CLSID which makes it
        /// a version independent approach to instantiate COM.
        /// </summary>
        /// <param name="progID">Prog ID e.g. Outlook.Application</param>
        /// <returns></returns>
        private static object CreateCOM( string progID )
        {
               // Instantiate the COM object using late bound
               Type comType = Type.GetTypeFromProgID( progID, true );
               return Activator.CreateInstance( comType );
        }

        public static IDisposable Create( string progID, Type interfaceType )
        {       
               object theCOM = CreateCOM( progID );
               DisposableCOMProxy wrapper = 
                  new DisposableCOMProxy( theCOM, interfaceType );
               return wrapper.GetTransparentProxy() as IDisposable;
        }

        public DisposableCOMProxy( object theCOM, 
               Type interfaceType ) :base( interfaceType )
        {
               this.COM = theCOM;
        }

The Create static method takes a ProgID and an interface type (e.g. Application) which defines the methods and properties of the COM. Then it creates a wrapper proxy which holds the COM reference.

Now, we need to intercept all method calls to the Application interface and delegate the call to the actual COM reference.

The Invoke method does the following:

  • Checks what the method name is. If it is Dispose then it releases the COM reference by calling Marshal.RelaseComObject and exits.
  • For any other method, it redirects the call to the actual COM object.

Here’s the code of the Invoke method:

C#
public override IMessage Invoke(IMessage msg)
{
        IMethodCallMessage callMessage = msg as IMethodCallMessage;

        object returnValue = null;

        MethodInfo method = callMessage.MethodBase as MethodInfo;
        
        // We intercept all method calls on the interface and delegate the method
        // call to the COM reference.
        // Only exception is for "Dispose" which needs to be called on this class
        // in order to release the COM reference.
        // COM reference does not have Dispose method
        if( method.Name == "Dispose" )
        {
               this.Release();
        }
        else
        {
               object invokeObject = this.COM;
               Type invokeType = this.COM.GetType();

               // Get Property called: Retrieve property value
               if( method.Name.StartsWith("get_") )
               {
                       string propertyName = method.Name.Substring(4);
                       returnValue = invokeType.InvokeMember( propertyName, 
                               BindingFlags.GetProperty, null,
                               invokeObject, callMessage.InArgs );
               }
                       // Set Property Called: Set the property value
               else if( method.Name.StartsWith("set_") )
               {
                       string propertyName = method.Name.Substring(4);
                       returnValue = invokeType.InvokeMember( propertyName, 
                                     BindingFlags.SetProperty, null,
                                     invokeObject, callMessage.InArgs );
               }
                       // Regular method call
               else
               {
                       returnValue = invokeType.InvokeMember( method.Name, 
                                     BindingFlags.InvokeMethod, null,
                                     invokeObject, callMessage.Args );
               }
        }

        // Construct a return message which contains the return value 
        ReturnMessage returnMessage = new ReturnMessage( returnValue, null, 
               0, callMessage.LogicalCallContext,
               callMessage );

        return returnMessage;
}

Remember all property calls are also method calls. .NET runtime dynamically generates “get_PropertyName” and “set_PropertyName” methods on the proxy in order to intercept property calls.

So, we have our disposable COM wrapper, now we can happily use COM objects without worrying about memory leaks:

C#
public static void Main()
{
        // Instantiate Outlook.Application and wrap the COM with the Application
        // interface so that we have a strongly type managed wrapper to late bound 
        // COM
        using( Application app = 
             ( Application)DisposableCOMProxy.Create( "Outlook.Application", 
              typeof( Application ) ) )
        {
               Debug.WriteLine( app.Name );
        }
}

The release of COM reference is ensured in two ways:

  • Dispose method of the COM wrapper releases the COM reference.
  • Destructor of the COM wrapper releases the COM reference. Even if you forget to dispose, the reference will be properly released when the object is finalized by the garbage collector.
C#
/// <summary>
/// Safely release the COM object
/// </summary>
private void Release()
{
        if( null == this.COM ) return;

        Marshal.ReleaseComObject( this.COM );
        this.COM = null;

        Debug.WriteLine( "COM released successfully" );
}

~DisposableCOMProxy()
{
        this.Release();
}

So, by doing all these, we are actually simulating an IDisposable interface on a COM object.

Image 4

Now you have a version independent managed Outlook wrapper which runs on all versions of Outlook. Try this on Outlook 2000, 2002, XP and 2003.

However, this tiny application interface actually provides nothing useful. You need a full fledged interface collection for the entire Outlook library. Moreover we need to address another issue:

How to handle objects returned by any property or method of a COM object? The returned objects are pure COM objects with no wrapper. For example, if you call the ActiveExplorer() method on the Application object, it returns the instance of the running Explorer. We need to provide a COM wrapper for the returned object in order to write strongly typed code and also implement the Disposable pattern.

The solution is simple. Inside the Invoke method, we analyze what is in the returnValue. If it is an object, then definitely it is a COM object. So, we need to do the following for all returned types which are object:

  • Get the method definition from the Type of the interface the proxy is intercepting (e.g. Application).
  • Find out what is defined as the return type of the method or property being called, e.g., Explorer ActiveExplorer().
  • If the return type is an interface, then we make a COM wrapper on the returned object using the interface type.

So, the Invoke method gets the following additional code:

C#
// Now check if the method return value is also an interface. if it is an 
// interface, then we are interested to intercept that too
if( method.ReturnType.IsInterface && null != returnValue )
{       
        // Return a intercepting wrapper for the com object
        DisposableCOMProxy proxy = 
            new DisposableCOMProxy( returnValue, method.ReturnType );
        returnValue = proxy.GetTransparentProxy();
}

We are also modifying the interfaces we have defined in order to introduce the Explorer type.

C#
public interface Application : IDisposable
{
        string Name { get; }
        Explorer ActiveExplorer();
        void Quit();
}
public interface Explorer : IDisposable
{
        string Caption { get; }
        void Close();
        void Display();
        void Activate();
}

This way, we can use more features of Outlook in a safe managed, strongly typed, disposable way which still works over late bound calls to COM and thus makes it a lot safer.

C#
public static void Main()
{
        using( Application app = 
             ( Application)DisposableCOMProxy.Create( "Outlook.Application", 
               typeof( Application ) ) )
        {
               Debug.WriteLine( app.Name );

               using( Explorer explorer = app.ActiveExplorer() )
               {
                       Debug.WriteLine( explorer.Caption );
               }
        }
}

What is in the Source Code

You will get a complete interface collection of Outlook library. I have decompiled the entire interop assembly generated from Outlook, cleaned every single method and property to get rid of those nasty attributes.

So, instead of this:

C#
[ComImport, TypeLibType((short) 4160), 
            Guid("00063001-0000-0000-C000-000000000046")]
public interface _Application
{
      [DispId(61440)]
      Application Application { [return: MarshalAs(UnmanagedType.Interface)] 
                  [MethodImpl(MethodImplOptions.InternalCall, 
                  MethodCodeType=MethodCodeType.Runtime), DispId(61440)] get; }
      [DispId(61450)]
      OlObjectClass Class { [MethodImpl(MethodImplOptions.InternalCall, 
                  MethodCodeType=MethodCodeType.Runtime), DispId(61450)] get; }
      [DispId(0xf00b)]
      NameSpace Session { [return: MarshalAs(UnmanagedType.Interface)] 
                  [MethodImpl(MethodImplOptions.InternalCall, 
                  MethodCodeType=MethodCodeType.Runtime), DispId(0xf00b)] get; }
      [DispId(0xf001)]
      object Parent { [return: MarshalAs(UnmanagedType.IDispatch)] 
                  [MethodImpl(MethodImplOptions.InternalCall, 
                  MethodCodeType=MethodCodeType.Runtime), DispId(0xf001)] get; }
      [DispId(0x114)]
      Assistant Assistant { [return: MarshalAs(UnmanagedType.Interface)] 
                  [MethodImpl(MethodImplOptions.InternalCall, 
                  MethodCodeType=MethodCodeType.Runtime), DispId(0x114)] get; }

You will find the following in the provided source code:

C#
public interface Common : IDisposable
{
        Application Application { get; }
        NameSpace Session { get; }
        object Parent { get; }
        OlObjectClass Class { get; }
}

public interface Application : Common
{
        string Name { get; }

        Explorer ActiveExplorer();
        Explorers Explorers{ get; }

        string Version { get; }
        Inspector ActiveInspector();
        object CreateItem(OlItemType ItemType);
        object CreateItemFromTemplate(string TemplatePath, 
                                         object InFolder);
        object CreateObject(string ObjectName);
        NameSpace GetNamespace(string Type);
        
        void Quit();
}

An entire collection of nice and clean interfaces.

I have also modified the interfaces in order to provide some sort of generalization. The object model (or you can say interface model) is as follows:

Image 5

In the source code, you will find the following:

  • COMWrapper.cs - This is the final wrapper that you will use.
  • TestOutlook.cs – A complete test of various operations on Outlook.
  • MyObject.cs – Simple example of method interception.
  • OfficeWrappers.cs – Almost complete Outlook 2003 interface set.
  • SimpleOutlookWrapper.cs – All the code you have seen so far in this article.

How to make your own COM wrapper

Here are the steps for making your own COM wrapper:

  • Create an interface which defines the signatures of the methods and properties of the COM object, e.g.: Application.
  • Create all other related interfaces and enumerations, e.g., Explorer, olItemType.
  • Use the DisposableCOMProxy.Create method to create the first COM object.
  • Use all objects inside using( ... ) block to ensure they are properly disposed.

Conclusion

The COMWrapper proxy is a generic COM wrapper. It does not depend on Office. An Office wrapper is shown as an example. You can use this wrapper on any COM object including IE Browser Control reference, DHTML object library, any Office Application’s COM library and even your own COM library. You should use it everywhere whenever you are dealing with COM objects. Do not trust your instinct that you will never forget to call Marshal.RelaseComObject. Use the proven Dispose Pattern on COM objects.

License

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


Written By
Architect BT, UK (ex British Telecom)
United Kingdom United Kingdom

Comments and Discussions

 
QuestionInvoke Doesn't handle all situations... Pin
SolarNigerija8-Dec-16 11:50
SolarNigerija8-Dec-16 11:50 
QuestionAutomatically dispose dependent objects Pin
ellarr26-Jul-12 8:31
ellarr26-Jul-12 8:31 
QuestionSupport DispID invocation on accessors/modifiers Pin
ellarr26-Jul-12 8:06
ellarr26-Jul-12 8:06 
QuestionEvent Handling with Excel 2010 Pin
buehlert4-Apr-12 21:46
buehlert4-Apr-12 21:46 
GeneralMy vote of 5 Pin
lRUSHl3-Apr-12 16:55
lRUSHl3-Apr-12 16:55 
QuestionExecuting Macros Pin
buehlert8-Mar-12 22:16
buehlert8-Mar-12 22:16 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey28-Feb-12 18:14
professionalManoj Kumar Choubey28-Feb-12 18:14 
QuestionLock being held on DLL [modified] Pin
dendle22-Sep-10 22:14
dendle22-Sep-10 22:14 
QuestionRe: Lock being held on DLL Pin
thomasholme27-Sep-10 5:14
thomasholme27-Sep-10 5:14 
AnswerRe: Lock being held on DLL Pin
thomasholme29-Sep-10 23:42
thomasholme29-Sep-10 23:42 
GeneralMy vote of 5 Pin
JS0000110-Aug-10 4:39
JS0000110-Aug-10 4:39 
GeneralExplorer.Selections vs. Explorer.Selection Pin
yarivtal17-Nov-09 22:28
yarivtal17-Nov-09 22:28 
Questionlicense? Pin
yarivtal13-Nov-09 8:43
yarivtal13-Nov-09 8:43 
AnswerRe: license? Pin
Omar Al Zabir14-Nov-09 0:20
Omar Al Zabir14-Nov-09 0:20 
QuestionInvoking COMObject overloaded method [modified] Pin
bbelliveau448-Jun-09 12:11
bbelliveau448-Jun-09 12:11 
QuestionHow can you cast object to different interfaces? Pin
safepage29-May-09 8:51
safepage29-May-09 8:51 
GeneralHot to connect ActiveExplorer Pin
_rush_22-Apr-09 7:54
_rush_22-Apr-09 7:54 
GeneralExcellent! Pin
Moim Hossain9-Feb-09 23:16
Moim Hossain9-Feb-09 23:16 
QuestionContactItem?? Pin
thansen102430-Jun-08 19:28
thansen102430-Jun-08 19:28 
GeneralBrilliant work Pin
Sacha Barber15-May-08 4:00
Sacha Barber15-May-08 4:00 
GeneralAdd an attribute to signal US CultureInfo is required Pin
Jeff Byrne11-Mar-08 4:20
Jeff Byrne11-Mar-08 4:20 
GeneralHaving problems with IEnumerable and Excel [modified] Pin
Jeff Byrne10-Mar-08 11:07
Jeff Byrne10-Mar-08 11:07 
GeneralRe: Having problems with IEnumerable and Excel Pin
Jeff Byrne10-Mar-08 13:54
Jeff Byrne10-Mar-08 13:54 
QuestionTrusted COM add-ins to circumvent the Outlook security warning Pin
matsch_o030-Aug-07 23:32
matsch_o030-Aug-07 23:32 
GeneralSend a Message Pin
gratajik10-Jul-07 11:40
gratajik10-Jul-07 11:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.