Integrating Eiffel with COM

by Colin LeMahieu (modified: 2008 May 09)

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.

Reference counting

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!

EiffelCOM Wizard

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:

import "ocidl.idl"; [ uuid(3d423184-b3be-4105-b6e8-43aa59b035f9) //Generate a new GUID here ] library OCIDL { // interface IEnumConnections; // interface IEnumConnectionPoints; interface IConnectionPoint; // 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.

ECOM_INTERFACE objects

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. make is local shell: SHELL_WINDOWS_PROXY l_count: INTEGER l_counter: INTEGER counter_variant: ECOM_VARIANT browser: IWEB_BROWSER2_IMPL_PROXY browser_i: ECOM_INTERFACE do create proc io.put_string ("Starting") io.put_new_line create shell.make l_count := shell.count from l_counter := 0 until l_counter = l_count loop create counter_variant.make counter_variant.set_integer4 (l_counter) browser_i := shell.item1 (counter_variant) if browser_i /= void then create browser.make_from_other (browser_i) -- Equivalent to QueryInterface io.put_string (browser.location_name + " in hwnd: " + browser.hwnd.out) io.put_new_line end l_counter := l_counter + 1 end end

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.