This section contains examples of how to write Haskell COM components, including how to generate the surrounding support code using HaskellDirect.
This currently only works together with ghc.
All the example code given in this section can be found in
the examples/comserv directory of the HaskellDirect
distribution, together with a Makefile for building
the component.
We want to write a simple phone directory abstraction which supports lookups and the addition of entries to a directory. The external programming interface to the directory would be as follows:
newPBook :: PBook
addDirectoryEntry :: PBook -> String -> String -> PBook
lookupName :: PBool -> String -> Maybe String
lookupNumber :: PBook -> String -> Maybe String
What the individual operations do should hopefully be clear - here's one possible implementation of the interface:
module PBook
( PBook
, newPBook
, addEntry
, lookupNumber
, lookupName
) where
import List ( find )
newtype PBook = PBook [PBookEntry]
data PBookEntry
= PBookEntry
{ entryName :: String
, entryNumber :: String
}
newPBook :: PBook
newPBook = PBook []
addEntry :: PBook
-> String
-> String
-> PBook
addEntry (PBook ls) name no
= PBook (PBookEntry{entryName=name, entryNumber=no} : ls)
lookupNumber :: PBook
-> String
-> Maybe String
lookupNumber (PBook ls) no
= fmap (entryName) (find ((==no).entryNumber) ls)
lookupName :: PBook
-> String
-> Maybe String
lookupName (PBook ls) name
= fmap (entryNumber) (find ((==name).entryName) ls)
Not much to it, so let's make things a little bit more interesting and try to wrap this phone book up as a COM component.
First step along the way is to try to express the interfaces of a component in IDL, splitting up the operations for looking up entries from that of adding one into separate interfaces:
[....]
interface ILookup : IUnknown {
HRESULT LookupName ([in]BSTR name, [out,retval]BSTR* number);
HRESULT LookupNumber ([in]BSTR number, [out,retval]BSTR* name);
};
[....]
interface IDirectory : IUnknown {
HRESULT AddEntry ([in]BSTR number, [in]BSTR numb);
};
[....]
coclass PBook {
[default]interface ILookup;
interface IDirectory;
};
The PBook is declared to be a component supporting two
interfaces, ILookup and IDirectory. Nice and
clean. Unfortunately though, scripting languages such as
VBScript and JavaScript forces us to rethink our
pursuit of Component Beauty. For one, they only let you access a
component via a scripting-friendly IDispatch interface, that
is, the notion that a component can consist of more than one interface
is just lost on these languages. Only one IDispatch derived interface
can be used by these clients, so in order to be more scripting
language friendly, let's rephrase the above interface definition:
There are ways and means of working around the problems thatIDispatch-based clients such asVBScriptposes to the component designer/implementor. An overview of proposed solutions and workarounds can be found at IDispatch workarounds. These solutions can be carried over into our setting, but haven't been (yet). One response to all this might be, ``why bother?''. The short answer to that is that we dearly want to be able to embed and script Haskell components from within web pages.
[....]
interface ILookup : IUnknown {
HRESULT LookupName ([in]BSTR name, [out,retval]BSTR* number);
HRESULT LookupNumber ([in]BSTR number, [out,retval]BSTR* name);
HRESULT AddEntry ([in]BSTR number, [in]BSTR numb);
};
[....]
dispinterface DILookup {
interface ILookup;
};
[....]
library PhBook {
[....]
coclass PBook {
[default]dispinterface DILookup;
};
};
The dispinterface declaration exposes the methods of
ILookup via the aforementioned scripting-friendly
IDispatch interface (details of which aren't that
important here).
The above specification also embeds the declaration of PBook
inside a library declaration. This is done in order for the Haskell
implementation of IDispatch, which HaskellDirect generates
code for, to work properly.
When operating in 'Haskell COM server' mode, HaskellDirect generates
the underlying support code which takes care of the details of how
to wrap up a Haskell implementation of a PBook component
(see separate document for details of what's generated - ToDo: add xref).
The HaskellDirect command line is as follows:
ihc -s -c pbx.idl -o PbxProxy.hs --output-tlb=pbx.tlb
where -s puts the compiler in server-mode, and
--output-tlb= is used to generate the type library
which is used to implement IDispatch.
If you feed HaskellDirect the extra option --skeleton, it
will generate the type signatures and empty definitions of the methods
it expects the component implementor to supply. In the case of
PBook, it generates the following:
module PBX where
data State = State
new :: IO State
new = ...
lookupName :: String -> State -> IO String
lookupName name st = ...
lookupNumber :: String -> State -> IO String
lookupNumber number st = ...
addEntry :: String -> String -> State -> IO ()
addEntry name numb st = ...
Each method is an IO action that takes as its last argument the state
of the PBook component which it then updates as needed.
Not quite the signature of our original PBook abstraction,
but impedance matching the two is not much work:
module PBX where
import IOExts
import qualified PBook
data State = State (IORef PBook.PBook)
new :: IO State
new = do
let x = PBook.newPBook
r <- newIORef x
return (State r)
lookupName :: String -> State -> IO String
lookupName name (State st) = do
pb <- readIORef st
case PBook.lookupName pb name of
Nothing -> return "not found"
Just x -> return x
lookupNumber :: String -> State -> IO String
lookupNumber number (State st) = do
pb <- readIORef st
case PBook.lookupNumber pb number of
Nothing -> return "not found"
Just x -> return x
addEntry :: String -> String -> State -> IO ()
addEntry name number (State st) = do
pb <- readIORef st
let new_pb = PBook.addEntry pb name number
writeIORef st new_pb
Ideally, we shouldn't have to write this layer of code - the
HaskellDirect generated code should just 'plug into' the
PBook signature we wrote earlier, right? That would certainly
be nice, but our experiences of writing components in Haskell are so
far quite limited, so a pattern of how to express components
functionally has yet to emerge. But when or if such a pattern steps
forward, HaskellDirect should be extended to avoid having to write
code such as the above.
That's it really, next step is to assemble the bits that make up the
component, see the examples/comserv/Makefile for details of
how this is done.
Once you've successfully built the component, make sure you register it to the world using
sh$ regsvr32 test.dll
sh$
With that done, you can now start to use the component - in
examples/comserv you'll also find an example of how it
can be embedded in a web page (and accessed via VBScript) +
there's also some sample code which demonstrates how it can
be accessed from a Visual Java client.