COM Interop Question
-
I am currently reading, ".NET and COM: The Complete Interoperability Guide", by Adam Nathan. As a way to work my way through the material (to better understand it), I am creating a Windows Forms project from which I can programmatically manipulate MS Word. The authors recommends using the Type Library Importer (TLBIMP.EXE) to convert the Microsoft Word 10.0 Object Library (MSWORD.OLB) into an Interop Assembly. The assembly is then referenced in the Visual C++ .NET project and placed in the same directory as the compiled executable. This is surprisingly easy, and the assembly can then be inspected with the IL Disassembler (very similar to the way the OLE/COM Object Viewer works). At this point, in your source code you can instantiate COM objects very simply with the new operator. The author describes all this clearly and in great detail in his book. Anyway, many of the method calls on the COM object involve passing structures to the COM component as a VARIANT. The Interop Marshaler apparently maps the COM VARIANT type to the .NET System::Object type, but, there is no way to pass a struct via a System::Object type to the COM object. So the method call throws an exception if you attempt to pass a System::Object to it (or just doesn't compile in the first place). However, the author has included in his book a code listing which manually marshals the VARIANT type in a given example to a structure which the COM component accepts and the function call then will succeed. It is confusing as hell, frankly, and I don't yet fully understand why it works. The basic idea is to reassemble the assembly after changing the signature of the original method so that it accepts an IntPtr instead of the VARIANT. He then writes a class that is like an Interop chimera, and which converts the original VARIANT into a struct via the IRecordInfo interface (InteropServices) and a couple of functions that you've probably never heard of: GetRecordInfoFromTypeInfo (oleaut32.dll), Marshal::GetITypeInfoForType, and Marshall::GetObjectForIUnknown. (I'm leaving alot of information out of this explanation just for brevity and clarity.) This seems like an awful lot of trouble to go to, and potentially, you might have to write many of these custom marshalers, all different and complex. I'm wondering if any of you have had to implement these kinds of marshalers in .NET applications, and if so, just how do they perform, and is the development process as insane as it looks? Any opinion would be appreciated, thanks.
-
I am currently reading, ".NET and COM: The Complete Interoperability Guide", by Adam Nathan. As a way to work my way through the material (to better understand it), I am creating a Windows Forms project from which I can programmatically manipulate MS Word. The authors recommends using the Type Library Importer (TLBIMP.EXE) to convert the Microsoft Word 10.0 Object Library (MSWORD.OLB) into an Interop Assembly. The assembly is then referenced in the Visual C++ .NET project and placed in the same directory as the compiled executable. This is surprisingly easy, and the assembly can then be inspected with the IL Disassembler (very similar to the way the OLE/COM Object Viewer works). At this point, in your source code you can instantiate COM objects very simply with the new operator. The author describes all this clearly and in great detail in his book. Anyway, many of the method calls on the COM object involve passing structures to the COM component as a VARIANT. The Interop Marshaler apparently maps the COM VARIANT type to the .NET System::Object type, but, there is no way to pass a struct via a System::Object type to the COM object. So the method call throws an exception if you attempt to pass a System::Object to it (or just doesn't compile in the first place). However, the author has included in his book a code listing which manually marshals the VARIANT type in a given example to a structure which the COM component accepts and the function call then will succeed. It is confusing as hell, frankly, and I don't yet fully understand why it works. The basic idea is to reassemble the assembly after changing the signature of the original method so that it accepts an IntPtr instead of the VARIANT. He then writes a class that is like an Interop chimera, and which converts the original VARIANT into a struct via the IRecordInfo interface (InteropServices) and a couple of functions that you've probably never heard of: GetRecordInfoFromTypeInfo (oleaut32.dll), Marshal::GetITypeInfoForType, and Marshall::GetObjectForIUnknown. (I'm leaving alot of information out of this explanation just for brevity and clarity.) This seems like an awful lot of trouble to go to, and potentially, you might have to write many of these custom marshalers, all different and complex. I'm wondering if any of you have had to implement these kinds of marshalers in .NET applications, and if so, just how do they perform, and is the development process as insane as it looks? Any opinion would be appreciated, thanks.
Well, apparently, no one has any interest in this subject. I've spent some time exploring the Word.dll assembly, and as it turns out the methods referred to in the above message were alot more easily implemented than I had originally thought. In the Word assembly (which is a huge library) in most of the methods that I've encountered so far, the Type Library Importer attaches a custom attribute (the MarshalAsAttribute) to the [in] parameters of the methods (which is a .NET object&) that apparently is type cast to an ordinary struct (not a struct contained within a VARIANT), so there was no need to write a custom marshaler for any of the data conversions. And, many of the parameters that are required for function calls on an interface or instantiated object are optional. However, the Visual C++ .NET compiler requires an object be supplied for these optional parameters (which override default settings). Weirdly enough, this is easily accomplished by casting Type::Missing to a System::Object using the dynamic_cast operator (or the __try_cast operator, if you'd rather handle the exceptions) and supplying this as the optional parameter. Example:
Object* OptionalParameter = dynamic_cast(Type::Missing);
Where you want to supply a structure for one of the optional parameters, it must be boxed. There is a help file supplied that describes the various objects that can be instantiated and the interfaces that can be used to make function calls. Unfortunately, the help file is written for Visual Basic programmers, and provides only minimal guidance in writing the C++ code. It does, however, tell you how to obtain the many objects, and essentially what functions you must implement to accomplish various tasks in the Word application. So, as it turns out, Microsoft Word can be automated fairly easily.