Haskell Bindings to C – c2hs

September 22, 2009

When making hsXenCtrl bindings I was thrilled to learn and use hsc2hs – I considered it all that was needed for sugar on top of the FFI. However, the recent Kernel Modules work has opened my eyes to various limitations of hsc2hs and forced me to learn c2hs – which is superior so I’ll go though a simple-stupid tutorial here.

This is intended as a starter companion to the official documentation – not a substitute! Read the docs, they are quite understandable!

While there are the normal issues of calling functions and accessing structure fields, it’s no coincidence that this code embodies the two issues I came across when using c2hs. First, it has a macro, for which there is no automatic FFI generation, and secondly it has structures that aren’t typedef’ed to a single keyword. We’ll see how to handle both those along with the rest of our simple chs code.

The .chs File

We now write a test.chs file that will interface with the code defined in our c header.

The boiler plate shouldn’t be surprising – the only new aspect is the inclusion of the header file that defines the types and functions to which we must bind.

Now lets define a pointer type. Unfortunately this code doesn’t use any typedefs and c2hs can’t parse “struct foo” as a single C type identifier. To fix this we use a section of C code, which will appear in the file “test.chs.h” once we run c2hs:

#c
typedef struct test test_t;
#endc

We may now define the pointer types using “test_t” instead of “struct test”

{#pointer *test_t as Test#}

This is amazingly simple – it translates into “type Test = Ptr ()”. You can add the keyword “newtype” at the end to obtain type enforcement if you so desire.

To access fields in this structure you could define a whole storable instance, using c2hs keywords ‘sizeof’, ‘get’, and ‘put’ the whole way – but we didn’t create a ‘newtype’ on which a typeclass instance can be defined. All we need are a pair of get and set functions – but you can see how to use these and thus create any Storable instance you want.

As you can see, use the {#get …} and {#set …} hooks along with the C type (“test_t”) and C field name(s) (“a”) to create a function that will access the types appropriately. Now we need to handle the damned macro. There exists no {#macro …} hook, and function hooks translate directly to “foreign import …” so the first step is using another C section to make a function wrap the macro:

#c
int add_one_(int x)
{
return (add_one(x));
}
#endc

This doesn’t warrant any more discussion, so lets move onto the function calls! The function hook is hands-down ugly, but powerful. First you tell it the C function you’re calling (add_one_). Then specify the name of the Haskell function; hat (“^”) means convert the C name to camel case (addOne), or you can specify any name you desire (ex: “hs_add_one”). After the naming is handled then you provide a list of arguments in curry braces – {arg1, arg2, …, argn} – then an arrow and a result Type. Each argument and return value is a Haskell type and enclosed in a back tick and single quote, such as `Int’. Before and after each argument you can provide in and out marshalling functions – this is where all the power of c2hs is and is also well stated in the documentation so I won’t be repeating it here – just notice that I marshal CInt to and from Int via “fromIntegral” and no marshalling is needed for the pointer (“Test”) so we use identity (“id”).

{#fun add_one_ as ^
{fromIntegral `Int' } -> `Int' fromIntegral#}

{#fun get_the_struct as ^
{} -> `Test' id#}

The remaining task is to define a simple ‘main’ function for testing purposes.

This is only the beginning – I’m using this with kernel headers and as such test.chs.h will be built using the kernel build system. For this reason I need to manually modified the generated haskell FFI to use the “regparm3″ calling convention or not use the c2hs function hooks ({#fun …}). Other than that, and an small gcc non-standard “flexability” that c2hs can’t parse without minor correction, it seems c2hs is a great tool for kernel bindings and I hope to have a real (though simple) driver done soon!

(replying here because there is no reply button for the deeper comment)

But #c and #endc aren’t even functions and won’t appear in your haskell source. What command are you running? Are you complaining that cabal doesn’t run c2hs on .chs files automatically they way it runs chs2hs? That is a problem, but running c2hs manually isn’t that big a deal.

In the kernel marcos and inlined functions often do hugely useful tasks that you don’t want to repeatedly type. In C, Haskell, or any other language, chances are high that if you find a commonly used expression in one then similar expressions will be needed in the other.

Another way to look at it is implementing similarly named marcos and functions makes it easier to transition between the two systems as you have similar convenience functions.