[Mono-dev] COM Interop in Mono
Jonathan S. Chambers
Jonathan.Chambers at ansys.com
Mon Nov 14 22:05:33 EST 2005
Here is a brief overview of some work involving COM Interop support in mono. I'm sure I'm wrong on multiple things below, so feel free to suggest/correct.
Don't feel like saying alot. Lots of docs available.
MS COM Interop
Again, well documented. Especially via '.NET and COM: The Complete Interoperability Guide' by Adam Nathan. COM interop is twofold.
1. Managed Client, COM Server - This allows for unmanaged com components to be used in the managed runtime. This occurs via runtime callable wrappers (RCW). The RCW is a managed wrapper that manages a single unmanaged com object. The lifetime of the COM object is managed via the RCW; the RCW's lifetime is managed like any other managed object. Also, the interop can occur via early binding (using the Interop assembly and vtables) or late binding via reflection.
2. COM Client, Managed Server - In this case, an managed object is exposed to COM via a COM callable wrapper (CCW). The CCW behaves like a normal COM object to the client. The CCW exposes interfaces corresponding to the managed object's methods/properties, as well as IUnknown, IDispatch, and other interfaces.
Another thing to note about COM interop is the runtime support for casting. Normal managed objects fail upon a cast when an interface is not implemented by that object. A RCW will try to cast the normal way, via the metadata provided about the class. If the cast fails, the runtime will then call QueryInterface on the underlying COM object for the interface in question. If the QueryInterface succeeds, then the runtime allows the cast to occur.
I'm ignoring other semi-important (depending on your situation) aspects of COM interop such as COM threading, connection points, etc.
Old Approach (all in C#, runtime agnostic; i.e. worked on MS. Net and Mono)
I'm not going to spend much time on this. This work initially grew out of looking at some code by Peter Bartok in Win32Dnd.cs in MWF. A COM interface (really just a vtable) is essentially a structure of function pointers. Function pointers can be marshalled as delegates (in 2.0) and vice versa. So, for each COM interface a corresponding struct of delegates was defined; except, the all the method signatures were modified to take an IntPtr as the first argument, the 'this; pointer. Once the COM object was created, it was QI'd for that interface. Then, the returned pointer (which points to the vtable) was read via Marshal.ReadIntPtr. This value is then marshalled as the struct previously defined. The delegates can now be invoked.
In addition, custom marshallers were written for the marshalling of interfaces and BSTRs. Tricks were played with pinning, GCHandles, etc for lifetime management. Also, to get around the fact that there was no runtime support for 'late' casting, Reflection.Rmit was used to build dynamic wrappers.
This method proved useful, but ugly and inefficient to say the least.
This method involves the mono runtime directly. Currently, I'm focusing on RCW, i.e. using COM components in managed code. I'm also using the Interop assemblies generated via MS's tlbimp.exe (in the future, someone could easily write a tool to generate compatible assemblies via an XML definition or whatever instead of from a typelib).
When a RCW class is loaded by the runtime, extra space is allocated in the class instance to contain the unmanaged object pointer. Upon creation of a RCW, the underlying COM object is created via CoCreateInstance and it's pointer is stored in the RCW.
All methods on a RCW are marked internal call. Thus, for each method a managed->unmanaged wrapper is generated. Currently, here is the process for that.
1. The pointer to the COM Object is obtained from the RCW.
2. The function pointer to the method obtained. The method layout in the interop assembly is the same order as that of the vtable of the COM object. Thus, the offset of the method on the RCW is used to offset into the vtable of the COM object, and get the correct function pointer.
3. The interface pointer and method arguments are pushed on the stack. If the managed method has a return value, a reference to a local variable of that type is pushed on the stack as the last arg to the COM method. The function pointer is called, expecting a int32 as a return value (an HRESULT that will be translated to an exception in the case of a failure). If there is a return value, this return value is pushed onto the stack and the method returns.
Currently, this mechanism allows for the use of a COM collection of doubles in managed code.
TODO (lots, I'll stick to near term)
1. Get lifetime management of COM object working correctly. Perhaps RCW will need finalizer that calls Release on COM object?
2. RCW has one pointer to COM object, but COM object has multiple vtables. Need correct vtable for current interface that is being called.
3. BSTR marshalling
4. Interface marshalling
5. Use COM marshalling rules instead of PInvoke rules
6. 'late' casting
7. Wrap this into all into a separate library, and communicate with runtime via API so no component technology specific code is in mono.
59. connection points
I know very little about XPCOM, but I understand that it is quite similar. This should be able to be extended to that quite easily I believe.
More information about the Mono-devel-list