Integrating Eiffel with COM
- Reference counting
- EiffelCOM Wizard
- Accessing existing COM objects
- Existing component wrapping
- .IDL compiling to .TLB pitfall
- Big projects, long compilation time
- ECOM_INTERFACE objects
- Creating integer VARIANT objects
- QueryInterface on ECOM_INTERFACE objects
Integrating Eiffel in to a COM environment is remarkably easy as the tools that handle the majority of the heavy lifting related to COM development already exist. Programming dynamically with COM is very different than programming statically with Eiffel so I've included common pitfall concept translations for these instances. This doesn't go over COM programming concepts such as In-Process versus Out-of-process components, COM threading models, or CoClass creation.
As long as you wrap everything using the EiffelCOM wizard and don't attempt to use your own reference counting, the EiffelCOM runtime will handle all reference counting. Reachability determined by a garbage collector instead of ad-hoc-ly by every programmer?!? Amazing technology!
The primary tool you'll use when creating COM components or trying to access existing COM components is the EiffelCOM Wizard. This tool is packaged with the standard EiffelStudio distribution and is located in the Start Menu -> EiffelStudio -> Tools or the executable is in (EiffelStudio)\wizards\com\com_wizard_launcher.exe.
Accessing existing COM objects
In order to access existing COM objects you'll need:
1) The .IDL file or the .TLB file or an OCX, DLL, or EXE that has been bound with a COM type library.
2) Generate a wrapper Eiffel system for the type library using EiffelCOM
3) Add the wrapper system as a library to the project from which you want to be able to access the COM objects.
Existing component wrapping
Start up the EiffelCOM Wizard. Under "General Setting" leave "Current Project" as "default", set "Project Type" to "Access an existing COM component", and browse to the location of the component definition .IDL, .TLB, .DLL, etc in "Component Information." Select the "Generation Options" tab. Select "In-process (*.dll)", browse to a directory where you want the Eiffel wrapper system to be generated in to, check "Compile C code" and "Compile Eiffel code", select "Clean destination folder prior to generation" and push "Generate." During generation the EiffelCOM Wizard will compile your .IDL file to a .TLB file, if applicable. After the .TLB file is generated it analyzes the interface structure, defined by the .TLB file, and generates C++ and Eiffel code to correspond to with it. It will then compile the C code and then the Eiffel project.
.IDL compiling to .TLB pitfall
Sometimes a .IDL file will not compile to a .TLB file and the EiffelCOM wizard will complain that .IDL compilation might not have been successful. If the "Output" pane is free of errors other than the wizard suggesting the IDL file might not have compiled, more than likely your .IDL file doesn't have a library target defined. If this is the case the MIDL compiler will complete successfully but not generate a .TLB file which the EiffelCOM Wizard is expecting. If this is the case you can create your own library target. The example I have below is for the ocidl.idl file in the Microsoft Windows PlatformSDK which does not have a library target and I wanted to use the IConnectionPoint interface which was implemented by another COM component. Create a new file called ocidl_lib.idl right next to ocidl.idl. In the content of the IDL file add:
uuid(3d423184-b3be-4105-b6e8-43aa59b035f9) //Generate a new GUID here
// interface IEnumConnections;
// interface IEnumConnectionPoints;
// interface IConnectionPointContainer;
There are existing references on the web for the complete IDL language but the short definition is, import the .IDL file which defines the interfaces you want but does not have a library target, generate a GUID for this IDL file, create your library target with a name that you pick, add the interfaces you want to generate in to the library target. You don't have to put all interfaces in to the library definition and the EiffelCOM Wizard will automatically get needed references. In this case IConnectionPointContainer is commented out but referenced by IConnectionPoint so it will *actually* be included in your Eiffel wrapper in the end. Generate your EiffelCOM wrapper project with the new IDL file you just created.
Big projects, long compilation time
Unfortunately the wrapper projects are very big and take a long time to compile. I'd suggest precompiling the wrapper project and catching some sun while it works or letting it precompile over night. Hopefully in the future it will be more lean. The finalized versions are still large but significantly smaller. When wrapping the MsHTML.idl file I got a 573MB library file and a 230MB driver file and it took 8 hours or so to compile on my slow laptop. When the system is finalized it was about 40MB, still big but not too bad.
If you're reading documentation on the COM object and it states an interface is returned by a function or property but you're seeing the Eiffel object return an ECOM_INTERFACE object, this is equivalent to getting an IUnknown or IDispatch reference in COM. You'll need to query that object for the interface you're looking for.
Creating integer VARIANT objects
If you put an INTEGER_32 in to a parameter that is expecting a VARIANT, the conversion routine for VARIANT from INTEGER_32 is make_from_integer which calls set_integer. If the destination component is expecting something like a long or int_4 it will throw an "Invalid Parameter" exception. You'll want to create a default VARIANT object and use the appropriate and exact set_ routine to set the value.
QueryInterface on ECOM_INTERFACE objects
With EiffelCOM you don't directly call QueryInterface on ECOM_INTERFACE objects. What you'll do is create the Eiffel equivalent instance of the interface using the make_from_other creation procedure.
Since all com wrapper objects inherit from ECOM_INTERFACE you can QueryInterface on any com object simply by using the make_from_other constructor. Very simple when you know the pattern.