Hacking Outlook Express - COM Interop |
Written by Mike James | ||||||
Thursday, 23 July 2009 | ||||||
Page 2 of 5
Creating a COM objectOur first problem is creating a raw COM object. Start a new project called OE1 and add a class called OE.cs. The idea is that the new class will do all of the work in creating and handling the COM object, i.e. it will wrap the COM object and make it available to the rest of the program as a standard .NET class. It makes sense to place the COM creation and initialisation code in the class’s constructor. This makes sure that everything is ready to use when you call a method. The constructor first creates a .NET type corresponding to the COM object: public OE() The only mystery here is what is a Guid and where do you discover what its value should be. The answer is that a Guid is a Globally Unique Id and every COM object has one. In this case it is also called a CLSID, a class ID. The simplest way of discovering the correct Guid is to read through either the IDL file or the C header file, in this case msoeapi.idl and msoeapi.h.
The CLSID is enough for the .NET system to be able to create a _ComObject type. With this you can then create an instance of the COM object using: OECOM = Activator.CreateInstance(OEType); This process should always work no matter how primitive or sophisticated the COM object is. If you know the CLSID you can create a type and then an instance of the object from the type.
InterfacesIf the COM object supports an Idispatch interface you can start using its methods as if it was a standard .NET object.This is the description of COM interop that you will most often encounter and if you can use it then do so - its easier. However, the simplest COM objects, like the one we are trying to use, don’t support Idispatch, only a more basic type derived from the strangely named “Iunknown” interface. This might sound advanced but it is a very simple piece of technology. The Iunknown interface is just a table of function calls – a vtable in C++ jargon – and as long as you know the order in which the functions are stored in the table you can make use of them. To do this in a .NET language we simply define an Interface that has the same functions defined in the same order. The only complication is that a COM class can offer more than one Iunknown derived interface, but you don’t need to worry because the .NET framework will find the one you want from its Guid – which again can be found in the IDL or header file along with an exact specification of all of the functions in each interface. We want to use the IStoreNamespace Interface. If you look in the header file you will find that this is defined as: MIDL_INTERFACE("E70C92AA-4BFD- and so on. To get us started we will just convert the two functions Initialize and GetDirectory into a .NET Interface. The C# code for this is: [Guid("E70C92AA-4BFD-11d1- From this you should be able to see that converting a COM interface to a .NET interface is relatively easy. You use the Guid to identify it and tell the .NET framework using InterfaceType that it is a primitive Iunknown derived COM interface. After this we simply define .NET functions that are used to call the corresponding COM methods. These are usually defined with the same name and same parameters, but they don’t have to be – only their order in the list is important. The biggest difficulty in performing a manual conversion is that.NET doesn’t support the same data types as COM. However if you look in the documentation for “COM Data Types” you will find a table that translates them – e.g. DWORD (double word) goes to Int32. You also need to include “MarshalAs” attributes to tell the COM interop handler what to do with the parameters. The COM interop handler can be complicated but essentially what it does is to copy managed data into unmanaged memory and then pass it to the COM object and vice versa. It can even perform some data conversions if necessary, such as Unicode to ANSI. If you want to take advantage of this you have to state what the .NET data type should be converted into. For example, the Int32 data type is marshalled as U4, i.e. unsigned 4-byte integer. As a safeguard you have to specify if the marshalling is to be performed on the way to the COM object, i.e. In, or from the COM object, i.e. Out, or both. For example, to tell the interop handler that a string is to be “marshalled” as a LPStr, i.e. long pointer to string, both in and out, use: [In, Out, This automatically converts the .NET string from Unicode to an ANSI 8-bit C style string to pass to the COM object and it performs the conversion the other way on the result. The correct use of MarshalAs makes using a COM object easy and you can generally fine-tune your selection of data types and attributes by trial and error if the documentation doesn’t provide enough information. First MethodWith just these two functions defined in the Interface we can already start to use the COM object. You don’t need a complete Interface definition, just one that is complete up to the functions you want to use. When you create a COM object in .NET what you get back is a reference to its Iunknown Interface but in this case we want a reference to its IStoreNamespace Interface. To do this we simply change the Type of the COM object to the new interface we have just defined: OEFolders = OECOM as IStoreNamespace; The .NET framework takes care of the details in the conversion and this is a completely general way of obtaining and using a COM Interface. Now we can use the functions as if they were standard methods of the OEFolders object. The last thing we have to do in the constructor is initialise the OE COM API using: OEFolders.Initialize(0, 0); That’s all there is too it. At this point we have successfully created the COM object and used one of its methods. To make everything work we also need to declare two global variables and add a using statement: private object OECOM; <ASIN:1430210230> <ASIN:0735618755> <ASIN:1572313498>
|
||||||
Last Updated ( Wednesday, 16 September 2009 ) |