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,IIntRefinheriting fromIUnknownsimply means that theIIntRefinterface supportsIUnknown's methods on top of the methods given inIIntRef'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:
IntRef which exports the methods
supported by the IntRef component along with the necessary
declarations needed to let you create instances of the component.IIntRef which implements the
stubs for IIntRef's methods, invoking the methods via the
interface pointer they're passed.
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:
data IUnknown a = IUnknown Pointer -- i.e., contains the raw pointer. --
data IntRef a = IntRef
type IIntRef a = IUnknown (IntRef a)
An interface pointer that supports IIntRef's methods is
represented by the type (IIntRef a). If you look at the right
hand side of IIntRef's type definition, it exactly encodes the
interfaces that IIntRef implements, IUnknown at the base,
extended with the pair of methods that IIntRef adds. The type
signature for these two methods ensure that they can only be applied
to interface pointers that (at least) implement the IIntRef
interface:
set :: Int32 -> IIntRef a -> IO ()
get :: IIntRef a -> IO Int32
The pay off from using this type representation is that it allows us
to reuse the stubs of the interface we're deriving from in an
interface declaration:
interface IIntRef2 : IIntRef { void reset(); };
i.e., IIntRef2 extends the IIntRef with a single operation,
reset. Using H/Direct, this declaration will generate the
following type definitions for the IintRef2 interface pointer:
data IntRef2 = IntRef2
type IIntRef2 a = IIntRef (IntRef2 a)
An IIntRef2 interface pointer has type (IIntRef2 a) which is
a synonym for (IIntRef (IntRef2 a)), i.e., an IIntRef
interface pointer that also supports IIntRef2's method. Since the
type signatures for get and set stated that any interface
pointer that at least supports IIntRef's methods can be applied,
an IIntRef2 interface pointer can clearly be used. The
implication of this is that we only need to generate stubs for the
reset method when processing the IIntRef2 interface.
[The above interface pointer representation is due to Daan Leijen,
and is supported by H/Direct and the libraries that come with the
latest version of HaskellScript.]
IIntRef inherited from the IUnknown interface, yet
the generated Haskell source doesn't contain stubs for any of
IUnknown's methods. The reason for this is that since all COM
interfaces inherit from IUnknown, library implementation of these
methods are provided instead.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.