Using withPtr From inline-c in Haskell
A friend was creating an experiment in Haskell where he wanted to use the
POSIX fork
and execve
calls, through
System.Posix.Process
. The problem he encountered was that
he could not find a way to wait for the forked process to complete running,
which caused some odd behaviour with stdin
sometimes being captured
by one process, and sometimes by the other.
Now, of course, the getProcessStatus
function in the same
module calls waitpid
and thus waits for the child process – but if we ignore
this, we have encountered an interesting opportunity to flex our muscles with
some inline C code.
Inline C
The inline-c
library is uh-maz-ing. It lets you write C code in your Haskell source
files very seamlessly. The level of integration is above and beyond. The
library is recent so the documentation is sparse, and there was one thing
in particular that took me a while to figure out, which I'll share with you now.
Embedding regular C code in your Haskell code is very easy. You could imagine doing something like
{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}
module Main where
import qualified Language.C.Inline as C
C.include "<stdio.h>"
main :: IO ()
main = do
putStrLn "Pick your lucky number"
number <- readLn
[C.block| void {
printf("Lucky number: %i\n", $(int number));
} |]
putStrLn "Wasn't that nice?"
After including the proper C header files, we can introduce a C code block
with the block
quasi-quoter. There are two bits of "new" syntax
inside the block, which is
- The "return type declaration" – we specify that we expect the block to
return "void", which gets translated by the quasi-quoter to
IO ()
. - The variable interpolation. By saying
$(int number)
we interpolate a variable from the surrounding Haskell environment. This might very well be the best thing about theinline-c
library. How cool isn't that!? You can just interpolate your Haskell values into the C code!
(Oh, and by the way, if you try to build your program and you get linker
errors (errors from ld
) in the spirit of (.text): undefined
reference to inline_c
, you have forgot to specify a c-sources
field in your cabal file. Had me puzzled for a while, because I didn't read
the documentation properly …)
waitpid
The POSIX waitpid
function is declared as follows:
pid_t waitpid(pid_t pid, int* status, int options)
This looks easy, right? We just use the knowledge we have from the previous example!
{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}
module WaitPid where
import qualified Language.C.Inline as C
C.include "<sys/wait.h>"
waitpid :: ProcessID -> IO ()
waitpid pid =
[C.block| void {
waitpid($(int pid), ???, 0);
} |]
The problem is, as you may have guessed, that waitpid
expects a
pointer to an integer – and what's worse, waitpid
is going to
mutate whatever is at the memory location of that pointer! (Technically,
we could also pass in a null pointer and waitpid
would not touch
it, but that's no fun, is it?)
The inline-c library provides a withPtr
function, which sounds
like it might be just what we need. It has the following type signature
(simplified for our case):
withPtr :: (Ptr Int -> IO b) -> IO (Int, b)
So as its argument, it takes a function of a pointer to an Int
,
and then returns the result of the IO
computation that resulted
together with whatever value the pointer now points at. We start by turning our
C code block into a function that takes a status pointer.
waitPidStatus :: ProcessID -> Ptr Int -> IO ()
waitPidStatus pid status =
[C.block| void {
waitpid($(int pid), $(int* status), 0);
} |]
The inline-c
library knows how to interpolate Ptr
Int
values into the C code, so we're done with that function.
Now we can call this under withPtr
, like so:
waitPid :: ProcessID -> IO (Int, ())
waitPid pid =
withPtr (\status -> waitPidStatus pid status)
And that's the magic. withPtr
will allocate an Int
and invent a pointer to it, and then the C code can mutate that Int
as much as it wants through the pointer. When the withPtr
function
is finished, nobody has access to the pointer anymore so whatever it points
to will be stable, and that's why it can return it as a regular Int
.
We quickly realise as a final refactoring step that compacting the two definitions to one loses us nothing.
waitpid :: ProcessID -> IO (Int, ())
waitpid pid =
withPtr (\status ->
[C.block| void {
waitpid($(int pid), $(int* status), 0);
} |]
)
We could tidy up the return type (either by using the withPtr_
function or by unpacking the tuple and just returning the first element of it),
but that is simple and beyond what I'm interested in for this article.
This can hopefully be used as a simple example on how withPtr
from the fantastic inline-c
library can be used for fun and profit.