Skip to main content
Email Password   helpLost your password?

Sample Image - SafeCOMWrapper.gif

Introduction

There are several problems using COM from .NET:

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

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.

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:

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:

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.

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

Here�s how we use the class:

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

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

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:

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:

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:

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:

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:

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:

Now comes the great DisposableCOMProxy. It has four responsibilities:

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:

Here�s the code of the Invoke method:

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:

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:

/// <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.

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:

So, the Invoke method gets the following additional code:

// 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.

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.

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:

[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:

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:

In the source code, you will find the following:

How to make your own COM wrapper

Here are the steps for making your own COM wrapper:

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.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralExplorer.Selections vs. Explorer.Selection Pin
yarivtal
23:28 17 Nov '09  
Questionlicense? Pin
yarivtal
9:43 13 Nov '09  
AnswerRe: license? Pin
Omar Al Zabir
1:20 14 Nov '09  
QuestionInvoking COMObject overloaded method [modified] Pin
bbelliveau44
13:11 8 Jun '09  
GeneralHow can you cast object to different interfaces? Pin
safepage
9:51 29 May '09  
GeneralHot to connect ActiveExplorer Pin
_rush_
8:54 22 Apr '09  
GeneralExcellent! Pin
Moim Hossain
0:16 10 Feb '09  
GeneralContactItem?? Pin
Member 4311058
20:28 30 Jun '08  
GeneralBrilliant work Pin
Sacha Barber
5:00 15 May '08  
GeneralAdd an attribute to signal US CultureInfo is required Pin
Jeff Byrne
5:20 11 Mar '08  
GeneralHaving problems with IEnumerable and Excel [modified] Pin
Jeff Byrne
12:07 10 Mar '08  
GeneralRe: Having problems with IEnumerable and Excel Pin
Jeff Byrne
14:54 10 Mar '08  
QuestionTrusted COM add-ins to circumvent the Outlook security warning Pin
matsch_o0
0:32 31 Aug '07  
GeneralSend a Message Pin
gratajik
12:40 10 Jul '07  
GeneralRe: Send a Message Pin
TrevorJobling
2:00 5 Oct '07  
GeneralRe: Send a Message Pin
Daniel A.
23:26 10 Jun '08  
QuestionHow to split COM+ and Presentation tier and deploy it in two servers ? Pin
Sylvester george
22:17 27 Jun '07  
GeneralAttachments Pin
Steve McKean
7:03 14 Jun '07  
GeneralRe: Attachments Pin
Shine1001
8:30 17 Jul '07  
GeneralHandling collections (IEnumerable) [modified] Pin
Sefatoma
10:59 13 Jun '07  
QuestionSafeCOMWrapper with Custom COM Object Pin
j.jonez
5:49 1 Feb '07  
GeneralNameSpace.Folders throws an InvalidCast exception Pin
Derik Palacino
7:51 15 Jan '07  
QuestionMoving mail items Pin
jpadair
7:28 4 Jan '07  
GeneralGetting Inspector Pin
acheyer
16:41 13 Dec '06  
GeneralRe: Getting Inspector Pin
Omar Al Zabir
19:09 15 Dec '06  


Last Updated 19 Sep 2005 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2009