[
The example in this section is closely based on a hybrid
example application suggested by Conal Elliott on the ffi
mailing list, March 1998.
]
Suppose you have developed a graphical editor framework in a language like C or C++. It presents a standard-looking environment for editing documents (e.g., an application that implements MDI under Win32), defining toolbars, menus, keyboard accelerators, and a workspace. Multiple document windows can be created inside the workspace window, each editing a separate document. Selecting a command from a menu causes a command to be forwarded to the document currently being edited.
The editing framework defines the set of commands it can perform
on its editable documents (e.g., Save() to store the document),
allowing you to plug in any editor that implements these commands.
Now, suppose you want to embed an editor you have implemented using
Fran (or some other Haskell library) inside this generic editor
framework - what do you do?
H/Direct can be used here to generate stubs that provide C interfaces to Haskell functions, provided you specify the functions you want to `export' from Haskell in IDL.
A systematic solution to the problem of combining code written in different languages is to use a language neutral software component technology like COM. We'll look at how H/Direct supports this in the next couple of sections, but will just remark here that there are cases where buying into a general component technology may not be worth it (e.g., for reasons of performance, effort, religion etc).For the editor framework application, here's some of the operations a plug-in editor will need to support:
module Editor {
import "win32.idl";
import "hdirect.idl";
typedef HaskellObject HEditor;
HEditor New([in,string]char* nm);
bool SetView ( [in]HEditor hed
, [in]HWND hwnd
);
void Save( [in]HEditor hed
, [in, string]char* fname
);
void Quit( [in]HEditor hed );
};
The operations perform the following tasks:
New creates a new instance of an editable document,
returning a value of type HEditor. HEditor is the
representation of an instance of an editable document/model, and is
here represented by the abstract type HaskellObject. (A
HaskellObject is also known as a stable pointer).SetView installs the editable document inside a view
window that will have been created separately by the
editing framework. SetView takes care of setting up the
distribution of events/windows messages correctly, so
that user interaction and system events (e.g., repaint) will be seen
(and appropriately handled) by the editor operating within the window.Save is used to store the document/model in some file, and
is normally invoked as a result of the save option in a menu being selected.Quit unconditionally shuts down the editing of the document.Given the above specification as input, the H/Direct IDL compiler will generate a number of input files, Figure Generated Haskell stubs shows the relationships between the different files:
Editor is the user supplied implementation of the functions
that you want to export from Haskell.EditorProxy is an automatically generated Haskell module
which contains the stubs that takes care of all details involved in
giving your Haskell functions an externally callable
interface, e.g., a stub takes care of marshaling between the
external type representation of its arguments and the Haskell types
expected by the corresponding Haskell function.EditorTypes defines the Haskell types that correspond to
the locally defined IDL types inside the Editor module
declaration. The reason for not including the type declarations in
EditorProxy is that they're also needed by Editor, so we lift
them out into a separate module in order to avoid mutual module
recursion.Editor C header file is also generated.
In the case of Editor, the following EditorProxy source will
be generated:
-- EditorProxy.hs --
module EditorProxy where
import HDirect ( HaskellObject )
import Win32 ( HWND )
import EditorTypes ( HEditor )
import Editor
(
new -- :: String -> IO HEditor
, setView -- :: HEditor -> HWND -> IO Int32
, save -- :: HEditor -> String -> IO ()
, quit -- :: HEditor -> IO ()
)
foreign export stdcall "New" primNew :: Addr -> IO HaskellObject
primNew :: Addr -> IO HaskellObject
primNew ptr = ...
foreign export stdcall "SetView" primSetView :: HaskellObject -> Addr -> IO Int32
primSetView :: HEditor -> HWND -> IO Int32
primSetView hed hwnd = ...
foreign export stdcall "Save" primSave :: HaskellObject -> Addr -> IO ()
primSave :: HEditor -> Addr -> IO ()
primSave hed ptr = ...
foreign export stdcall "Quit" primQuit :: HaskellObject -> IO ()
primQuit :: HEditor -> IO ()
primQuit hed = ...
The stubs corresponding to the Editor actions are exported using
foreign export FFI
declarations. The implementation of a stub calls upon its
corresponding Editor action once it has marshalled all the
parameters into an appropriate form.
The generated EditorTypes module contains just a single a type
synonym:
module EditorTypes where
import HDirect ( HaskellObject )
type HEditor = HaskellObject
The generated Haskell source can now be compiled with GHC to produce
object files that has got entry points that are callable from a
language like C or C++. To help out on the C side, the IDL compiler
will also generate a header file that contains the type declarations
and function prototypes corresponding to the contents of the
Editor module declaration:
/* editor.h */
#include "win32.h"
#include "hdirect.h"
typedef HaskellObject HEditor;
extern HEditor New (/*[in,string]*/ char* nm);
extern int SetView ( /*[in]*/HEditor hed
, /*[in]*/HWND hwnd
);
extern void Save( /*[in]*/HEditor hed
, /*[in, string]*/char* fname
);
extern void Quit( /*[in]*/ HEditor hed );
Assuming that the Haskell run-time environment has been packaged up in such a way that you can link in the stubs and your Haskell code with the rest of application, you should now be all set to go hybrid.
The EditorTypes module generated by the IDL compiler doesn't
convey too much type information to the Haskell programmer:
module EditorTypes where
import HDirect ( HaskellObject )
type HEditor = HaskellObject
i.e., an HEditor is implemented by a stable pointer to some
Haskell heap object. This is extremly unsafe since Editor's
exported IO actions could be passed any HaskellObject value, and the
stubs would have no way of telling if it was a stable pointer to a
heap object of the correct type, so we'd really like to impose some
checking on HaskellObjects being passed from C to Haskell.
There's a couple of ways in which to do this, all centred around
annotating HaskellObject typedefs:
HEditor type in IDL, also give the type
of the Haskell heap object it points to:
typedef [htype("User -> IO (ImageB, Event (IO ()))")]
HaskellObject
HEditor;
The (non-standard) htype() attribute will have the following
cause the following version of EditorTypes to be generated:
module EditorTypes where
import HDirect ( StablePtr )
type HEditor = StablePtr ( User -> IO (ImageB, Event (IO ())) )
The generated stubs can make use of this piece of information,
relieving the user-supplied IO actions of some of the unmarshaling:
-- EditorProxy.hs --
module EditorProxy where
import HDirect ( HaskellObject )
import Win32 ( HWND )
import EditorTypes ( HEditor )
import Editor
(
new -- :: String -> IO (User -> IO (ImageB, Event (IO ())) )
...
)
foreign export stdcall "New" primNew :: Addr -> IO HEditor
primNew :: Addr -> IO HEditor
primNew ptr = do
...
v <- new
v <- makeStablePtr v
return b
-- stubs omitted
i.e., the stubs will hide the fact that Haskell IO actions that want
to pass references to arbitrary Haskell values to the outside world
will have to package them up as stable pointers first.
htype() communicates more information to the
Haskell programmer via EditorTypes but it is just as unsafe. One
way to tackle the problem of safety is to add a uuid() attribute
to each typedef of a HaskellObject:
The uuid() attribute specifies a 128 bit globally unique identifier.
typedef [htype("User -> IO (ImageB, Event (IO ()))"),
uuid(C1DF9B10-BDDB-11d1-99CC-006597B7314A)]
HaskellObject
HEditor;
This gives rise to the following version of EditorTypes:
module EditorTypes where
import HDirect ( StablePtr )
uuid
type HEditor =
StablePtr (GUID,
User -> IO (ImageB, Event (IO ()))
)
i.e., a stable pointer to a pair containing HEditor's GUID and
the editor action along with a declaration for the GUID representing
HEditor. The stub code will then use the GUID to check that
they've been passed an OK-looking stable pointer:
primSave :: HEditor -> Addr -> IO ()
primSave hed ptr = do
(g, v) <- derefStablePointer
if g /= uuidHEditor
then ...failure..
else ...success..