How To Link A C Library In Haskell
In this blog post, I am going to show you how to link a C library in Haskell. Each step is kept as simple as possible in order to focus on the overall goal.
I will:
- develop a small C program
- generate two static C libraries from the C program
- merge both libraries into one
- develop a small Haskell program
- let GHC link to the merged C library
- use the functions exported by the C library within Haskell
The code snippets can be found here.
The C program
The C program is going to be extremely simple.
The main.c
is calling a library function and printing the result:
The library function liba_func()
is provided by, who guessed it, liba.c
.
The latter refers to libb_func()
in order to carry out its work.
The corresponding (educational) Makefile:
The static libraries
We can convert lib*.o
into static libraries using ar
.
This tool creates a so called archive file which is basically just a collection of object files.
In our case, both archives each contain only one object file.
For completeness, we’ll let gcc
link again the static libraries instead of the object files for main
, too.
One can inspect the archive content with nm <file.a>
.
We see that libb.a
defines the function (“symbol”) libb_func
and expects the symbol fmod
.
The latter comes from math.h
and the actual function is linked by gcc
(see -lm
in the Makefile’s main
target).
Merging the libraries
The obvious way to merge both static libraries is to create a single archive from both object files (i.e. ar -csr libab.a liba.o libb.o
).
However, I am going to assume the libraries come as is and the object files are not available.
This allows me to show a trick with ar
.
The issue with ar
is that you cannot simply create an archive by listing archive files instead of object files on the command line.
ar
will happily archive the input files without looking into them and thus create a nested archive.
The inner archives are not accessible anymore:
We can however ask ar
to add the contents of one archive to the current one using the ADDLIB directive from GNU ar’s “librarien” compatibility mode.
In this mode, ar
is controlled with a script which we can also simply pipe from the command line.
This time, inspecting the archive yields the exported symbols (functions) as expected. The necessary Makefile changes:
The Haskell program
The Haskell program is going to be straight forward. I’ll let stack create the a simply project for me
The initial Main.hs
:
Linking to the C library
stack
generates a Cabal file within the project.
This file is kind of like the Makefile of a Haskell app.
We can define GHC options within that file.
In order to tell GHC to link to an external library, we have to give a library path and a library name (pretty similar to GCC).
This is done by -L<path>
and -l<libname>
respectively.
So we could simply use stack’s ghc-options
parameter to add these.
However, it is recommended to use extra-lib-dirs
and extra-libaries
instead.
This is how we need to adapt the cabal file:
Of course, ../c
is the path to the aforementioned C program which contains libab.a
.
The file name’s prefix is striped so that libab
becomes ab
only.
Using the C library
We are going to make a wrapper Haskell library (file) that exposes the functions of the C library. Calling the actual C functions is done trough Haskell’s Foreign Function Interfarce. The source code is simple and listed below. You can google around to find lots of information about suing the FFI in more detail.
Now that we have the Haskell function liba_func
at our disposal, we can go ahead and call in Main.hs
:
This is the outcome:
Note how the pure Haskell function liba_func :: Double -> Double
produces side effects!
This is quite expected.
The Haskell compiler cannot look into the compiled C functions and cannot differentiate pure from impure code.
It merely links the foreign functions where asked to.
The programmer needs to provide this information.
In this case, it should have been liba_func :: Double -> IO Double
to accommodate for the printf
in the C function.
Done! You just learned how to combined C & Haskell! Well, at least some part of it.
You can retrace all steps by looking into the commit history of this Git repo.