|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThere 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 Update
Introducing RealProxy and method interception
Figure 1: How proxy works
You can learn a lot about Extending RealProxyYou can extend 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 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 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 MarshalByRefWe do not want to inherit our objects from the public interface IMyObject
{
void DoSomething();
}
public class MyObject : IMyObject
{
public void DoSomething()
{
Debug.WriteLine("DoSomething called");
}
}
See, no more 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 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 wrappersNow 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 public interface Application : IDisposable
{
string Name { get; }
void Quit();
}
We are making a subset of the actual complex
Now comes the great
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 Now, we need to intercept all method calls to the The
Here’s the code of the 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 “ 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 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 The solution is simple. Inside the
So, the // 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 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 CodeYou 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 wrapperHere are the steps for making your own COM wrapper:
ConclusionThe | ||||||||||||||||||||