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.
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 -- ");
IMethodCallMessage callMessage = msg as IMethodCallMessage;
this._ActualObject.DoSomething();
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:
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:
- 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.
public class DisposableCOMProxy : RealProxy
{
public object COM;
private static object CreateCOM( string progID )
{
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:
public override IMessage Invoke(IMessage msg)
{
IMethodCallMessage callMessage = msg as IMethodCallMessage;
object returnValue = null;
MethodInfo method = callMessage.MethodBase as MethodInfo;
if( method.Name == "Dispose" )
{
this.Release();
}
else
{
object invokeObject = this.COM;
Type invokeType = this.COM.GetType();
if( method.Name.StartsWith("get_") )
{
string propertyName = method.Name.Substring(4);
returnValue = invokeType.InvokeMember( propertyName,
BindingFlags.GetProperty, null,
invokeObject, callMessage.InArgs );
}
else if( method.Name.StartsWith("set_") )
{
string propertyName = method.Name.Substring(4);
returnValue = invokeType.InvokeMember( propertyName,
BindingFlags.SetProperty, null,
invokeObject, callMessage.InArgs );
}
else
{
returnValue = invokeType.InvokeMember( method.Name,
BindingFlags.InvokeMethod, null,
invokeObject, callMessage.Args );
}
}
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()
{
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.
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
:
- 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:
if( method.ReturnType.IsInterface && null != returnValue )
{
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:
- 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.