Next Previous Contents

3.3 Calling COM from Haskell

As mentioned in the introduction, the use of IDL to deal with interoperation between Haskell and C code is a by-product of the real reason for creating an IDL mapping for Haskell, namely that of using IDL to integrate Haskell with COM. So, as an example of how H/Direct can be used to tackle the `main course', consider the following IDL specification of a COM component:

[ object,
  uuid(C1DF9B10-BDDB-11d1-99CC-006097B7314A),
  pointer_default(unique)
]
interface IIntRef : IUnknown {
  HRESULT set([in] int x);
  HRESULT get([out] int* val);
}

[uuid(116E95C0-075E-11d2-A4E9-00A0C91F393A)]
coclass IntRef {
  [default]interface IIntRef;
}

It defines the IntRef component, a component that implements a int reference abstraction, supporting operations for setting and getting the current value.

A COM component implements one or more interfaces. In IDL, an interface is specified using the interface declaration, which contains the type signatures of its methods along with any auxillary type definitions. An interface may also inherit from another, IIntRef inherits from IUnknown, the base interface that all COM interfaces by definition implement.

Inheritance is purely at the interface level, IIntRef inheriting from IUnknown simply means that the IIntRef interface supports IUnknown's methods on top of the methods given in IIntRef's body.

The coclass declaration declares a component, enumerating the interfaces the component supports, which in the case of IntRef is just the solitary IIntRef interface.

Given the above IDL specification as input, the IDL compiler will generate the modules as show in Figure Generated Haskell stubs:

Calling COM from Haskell

A coclass declaration merely states what interfaces a component supports, and the corresponding Haskell module generated by the IDL compiler is equally precise:

module IntRef (module IIntRef, clsidIntRef ) where

import IIntRef

clsidIntRef :: CLSID
clsidIntRef = mkCLSID "{116E95C0-075E-11d2-A4E9-00A0C91F393A}"

For each coclass declaration encountered, the IDL compiler will generate a corresponding Haskell module which re-exports the Haskell interfaces that implements the COM interfaces it supports. Additionally, the class identifier (CLSID) of the component is also defined and exported (see below for how this identifier is used to create a component instance.)

For an interface declaration, the IDL compiler generates a Haskell module containing the Haskell stubs that take care of invoking the interface methods:

module IIntRef where

import Com

data IntRef a = IntRef
type IIntRef a = IUnknown (IntRef a)

iidIIntRef :: IID (IIntRef Interface)
iidIIntRef = mkIID "{C1DF9B10-BDDB-11d1-99CC-006097B7314A}"

set :: Int32 -> IIntRef a -> IO ()
set x iptr = ...

get :: IIntRef a -> IO Int32
get iptr = ...

The stubs hides the details of how COM method invocation is done, and the marshaling of data that occurs when going ``across the border''. A couple of points are worth noting about the generated source though:

To demonstrate how to use a COM component and the above stubs from within Haskell, here's an example of how to use an IntRef component:

module Main(main) where

import Com
import IntRef

main = comRun $ do
   iref <- comCreateInstance clsidIntRef Nothing InProcess iidIIntRef 
   set (24::Int) iref
   v    <- get iref
   putStrLn ("And the winning number is: " ++ show v)
  

The Com interface performs a task similar to the COM library in C, providing the programmer with a set of helper functions for working with COM components. Two important ones are:

comRun :: IO a -> IO ()

comCreateInstance :: CLSID
                  -> Maybe (IUnknown a)
                  -> CLSCTX
                  -> IID (IUnknown b)
                  -> IO (IUnknown b)

comRun initialises the COM run-time before performing the action passed as the first argument. Upon completion of executing this action, comRun un-initialises the COM run-time and returns.

comCreateInstance is used to create an instance of a component at a particular interface, e.g., in the example here we're creating an IntRef component instance at its IIntRef interface. (The second and third arguments to comCreateInstance is not of direct interest here).

Assuming comCreateInstance succeeds in creating the component we're requesting, it returns an interface pointer of the correct type. This reference to the interface is then used to invoke IIntRef's two methods.

Having an IDL compiler that generates client-side Haskell stubs to COM components is a very useful addition to the Haskell programmer's toolset. Using it, a great many components and interfaces can now be accessed from within Haskell. (Examples include using Haskell to script Microsoft Office applications and Internet Explorer).

However, to be able to tell a complete story when it comes to software components, we also want to use Haskell to create such components. The next section shows how H/Direct supports the creation of Haskell COM components.


Next Previous Contents