This section presents a couple of worked out examples where
HaskellDirect is used to create bindings to C libraries. If
you do want to experiment with these examples (or maybe simply
just use), the examples/ directory of the distribution
contains the code.
As an initial example of IDL and how HaskellDirect can help you to create bindings to external libraries, here's the IDL specification for ANSI C's math library:
module Math {
[pure]int abs([in]int x);
[pure]long labs([in]long x);
[pure]double fabs([in]double x);
[pure]double ceil([in]double x);
[pure]double floor([in]double x);
[pure]double fmod([in]double x,[in]double y);
[pure]double exp([in]double x);
[pure]double log([in]double x);
[pure]double log10([in]double x);
[pure]double frexp([in]double x,[in,out,ref]int* nptr);
[pure]double ldexp([in]double x,[in]int n);
[pure]double modf([in]double x,[in,out,ref]double* nptr);
[pure]double pow([in]double x,[in]double y);
[pure]double sqrt([in]double x);
[pure]int rand(void);
void srand([in]int seed);
[pure]double cos([in]double x);
[pure]double sin([in]double x);
[pure]double tan([in]double x);
[pure]double acos([in]double x);
[pure]double asin([in]double x);
[pure]double atan([in]double x);
[pure]double atan2([in]double x,
[in]double y);
[pure]double cosh([in]double x);
[pure]double sinh([in]double x);
[pure]double tanh([in]double x);
};
The functions that make up the math library are all encapsulated in an
IDL module declaration, which causes the IDL compiler to generate
a Haskell module Math containing the stubs that take care of
interfacing to the external functions.
The specification is almost identical to the ANSI C function
prototypes that can be found in a math.h include file, but the
IDL function signatures does contain some extra information:
[in] parameter passes
information to the callee, an [out] parameter goes the other way,
whereas [in,out] is (as you've probably guessed by now)
bi-directional.[pure] tells the IDL
compiler that an external function is to be considered a pure
function, so the Haskell version should be given a functional type
rather than being an IO action.By attributing the parameter and result types with these extra attributes, the ambiguities of C function prototypes are made clear. The IDL compiler takes advantage of this information when generating the corresponding Haskell stub functions:
module Math where
import qualified Prelude
sin :: Prelude.Double -> Prelude.Double
sin x = ...
srand :: Int.Int32 -> IO ()
modf :: Prelude.Double
-> Prelude.Double
-> Prelude.IO (Prelude.Double, Prelude.Double)
modf x nptr = ...
(Notice how the generated Haskell code takes care of fully qualifying any imported functions or types it uses, avoiding unnecessary name clashes).
The presence of the [pure] attribute on sin causes it to
be given the functional type Double -> Double, whereas the
side-effecting srand is wrapped up as an IO action. The IDL types
double and int correspond to the Haskell types Double
and Int32, respectively (Int32 is one of the sized numeric
types that are part of the Hugs/GHC extension libraries).
The rules for converting from an IDL function type signature with directional parameters into its corresponding Haskell signature is as expected:
[in] parameters become arguments to the Haskell
function (order is preserved).[out] parameters together with the function result
are returned in the Haskell function's result (all tupled up).[in,out] parameters occur in both argument and result
position in the corresponding Haskell type.The HaskellDirect distribution contains the Math specification
together with some simple code that uses the generated stubs; have
a look in the examples/math directory.
Here's an example of how to handle character strings more compactly from within Haskell, representing them as C character strings and by providing the standard C string operations over this compact representation. The IDL is as follows:
module FastString {
// [ptr] turns FastString into an abstract type,
// that is, it will not be packed (or unpacked)
// into the Haskell representation of a string
// when passing the border between Haskell and
// the outside world.
typedef [ptr]char* FastString;
[unique]FastString malloc([in]unsigned int sz);
void strcat([in,out]FastString dest,[in]FastString src);
void strncat([in,out]FastString dest,
[in]FastString src,
[in]int n);
int strcmp ([in]FastString s1,[in]FastString s2);
int strncmp([in,string]char* s1,[in,string]char* s2,[in]int n);
double atof([in]FastString str);
int atoi([in]FastString str);
long atol([in]FastString str);
//A White Lie - str*cpy returns the dest. string.
void strcpy([in,out]FastString dest,[in,string]FastString src);
void strncpy([in,out]FastString dest,
[in]FastString src,
[in]int n);
FastString strchr([in]FastString s, [in]char c);
FastString strrchr([in]FastString s, [in]char c);
int strpos([in]FastString s, char c);
int strrpos([in]FastString s, char c);
int strspn([in]FastString s, [in]FastString set);
int strcspn([in]FastString s, [in]FastString set);
FastString strpbrk([in]FastString s, [in]FastString set);
FastString strrpbrk([in]FastString s, [in]FastString set);
FastString strtok([in]FastString s, [in]FastString set);
};
Points worth noting about this spec:
ptr attribute:
typedef [ptr]char* FastString;
This attribute signals to the IDL compiler that FastString
should be treated as a pointer to an external object, and no
attempts should be made to unmarshal this object.
FastString aren't deallocated when the
garbage collector has deemed them to be unreachable. This has to be
done explicitly, by providing a binding to C's free().
In the next section we will see how to use HDirect IDL extensions to shift this memory management burden over on the garbage collector instead.
strcat, say. Not particularly convenient
to use, but there you go.Should you want to play with this example, examples/string
contains the IDL specification along with a simple application that
uses it.
A third example of how to use IDL to create Haskell bindings
to external libraries is an interface to the Gd GIF library
(available from
Gd library.) The IDL specification for the library is
included in the HaskellDirect distribution (see examples/gd),
but since it's rather long, we won't include it here. Compared
to the previous examples, the Gd IDL specification does
make use of a couple of features we haven't seen so far:
interface declaration is used
in IDL:
interface gdImage {};
This informs the IDL compiler that the gdImage should
be represented as (a pointer to) an abstract type in Haskell.
Optionally, the operations provided over the abstract type
can be specified within the body of the interface declaration
(i.e., within its the curly braces.)
The name of the generated abstract type will be GdImage,
i.e., we upper case the first character of the original name to
make it a legal Haskell type constructor name.
GdImages when we're
through with them, the interface declaration is annotated
with the finaliser attribute:
[finaliser("gdFreeImage")]
interface gdImage {};
The attribute forces the abstract type to be represented in Haskell by
a foreign object. The effect of this is that the garbage
collector will now finalise GdImages, invoking
the finaliser gdFreeImage() when the image becomes garbage,
i.e., at the point when the garbage collector discovers that the
foreign object has become unreachable.
deriving attribute,
which instructs the compiler to add a deriving clause when defining
the corresponding Haskell abstract type,
[deriving("Eq")]interface FILE {};
with the string literal given to the deriving attribute is copied
verbatim into the generated Haskell source.
const keyword,
a feature that's used to define the standard values provided
by the Gd header file:
const int gdStyled = -2;
which will give rise to an equivalent constant on the Haskell
side. For better or worse, constant declarations mirror here the
#defines given in the Gd header file.
The directory examples/gd contains the IDL specification
along with Haskellised versions of the two demo programs that
comes with the gd distribution, webgif and gddemo.
The stub code that the IDL compiler generates take care of converting between the Haskell representation of argument and result values, and their external representations. On top of that, the stub code ensures that the storage taken up by the external data is freed up when we're through with it in the Haskell world. For instance, the following IDL type signature:
int strlen([in,string]char* str);
generates a Haskell function with the type signature
strlen :: String -> IO Int32
The body for this (rather pointless) function, marshals the
String into a zero-terminated sequence of characters
before calling the external function. When it returns, the
storage allocated for the marshalled string is released.
When the external function returns a value that we want to unmarshal into the Haskell world, the situation changes:
[string]char* itoa([in]int i);
Here, the result is a zero terminated character string that will
be unmarshaled into a Haskell String. The
type of the generated Haskell stub for itoa then
becomes:
itoa :: Int -> IO String
When the itoa action has finished unmarhaling the string, it
releases the C string. HaskellDirect currently makes the assumption
that the C heap is used for all dynamic allocation, so free()
is used.
In the cases where the character string doesn't point to dynamically
allocated memory, but a static chunk, you don't want to call
free() (or any other freeing routine) when you've finished
unmarshaling. To signal that the generated stub code shouldn't free
an argument/result, use the custom attribute nofree. An
example of this attribute in action can be found in examples/env.
As a final example in this section, here's a Haskell binding to
zlib, a general purpose data compression library (format and
methods of compression described in RFC-1950/1951/1952), see
the Zlib home page. The library provides functions for
compressing and uncompressing data in memory, and on top of which
a stdio-like interface to .gz compressed files
is also provided.
The IDL specification for zlib is a relatively
straightforward conversion of the contents of its C header file (from
the zlib-1.1.3 distribution), but the spec does use the DCE
IDL's support for 'dependent arguments' in a couple of places.
For instance, the C function
uLong crc32 (uLong crc, const Bytef* buf, uInt len);
uses the third argument to carry the length of the sequence of bytes passed as second argument. DCE IDL does provide support for expressing this kind of dependency between arguments,
unsigned long crc32 ( unsigned long crc, [in,size_is(len)]char* buf, [in]unsigned int len);
The size_is() attribute pinned on the second arg convey this
piece of information. The IDL compiler makes use of this when
generating the Haskell stub for crc32, giving it the type:
crc32 :: Word32 -> [Char] -> IO Word32
The sequence of bytes is represented as a list, whose length carry the
value that the stub will pass as third argument when invoking
crc32().
This representation of a buffer of bytes is only meant to illustrate
how the size_is() attribute is made use of by the compiler;
using a more compact buffer representation with crc32() is
probably worth your while if you care about performance. (A future
extension of HaskellDirect would be to generate stubs here that instead
of lists uses a more compact representation, such as a byte array.)
The zlib specification can be found in examples/zlib
together with an extremely simple unzipper example.