[Haskell-cafe] Haskell FFI and finalizers

Maxime Henrion mux at FreeBSD.org
Wed Oct 3 11:57:58 EDT 2007


	Hello all,



I have recently developed a small set of bindings for a C library, and
encountered a problem that I think could be interesting to others.

My problem was that the C function I was writing bindings to expects to
be passed a FILE *.  So, I had basically two possibles routes to take:

1) Mimic the C API and have the haskell function take a Handle.

Unfortunately, I can see no way to go from a Handle to a Ptr CFile, at
least no portable way, so I discarded this option.

2) Deviate from the C API slightly and have the haskell function take a
FilePath instead of a Handle.

This is the option I chose, and this is where things get interesting.

In order to pass a Ptr CFile (FILE *) to the C function, I had to call
fopen() myself, using a usual FFI binding:

foreign import ccall unsafe "fopen"
  fopen :: CString -> CString -> IO (Ptr CFile)

That's the easy part.  Now my problem was that I had to find a way to
automatically close this FILE * when it isn't used anymore, in order not
to leak FILE structures (and thus fds, etc).  A finalizer is typically
what I need, but unfortunately, a finalizer has a very strict shape:

type FinalizerPtr a = FunPtr (Ptr a -> IO ())

That is, a finalizer can only be a pointer to a foreign function, and
the foreign function itself needs a quite specific shape.

And then I discovered Foreign.Concurrent, which allows one to associate
a plain Haskell IO action to a pointer.  The 'Foreign.Concurrent' name
is a bit misleading to me; it seems this module is named so because it
needs concurrency itself, rather than providing stuff for concurrency.

So, in the end, I've got this code:

import Foreign
import Foreign.C
import qualified Foreign.Concurrent as FC

...

data PlayerStruct
type Player = ForeignPtr PlayerStruct

...

foreign import ccall unsafe "dd_newPlayer_file"
  dd_newPlayer_file :: Ptr CFile -> Ptr ImageStruct -> IO (Ptr PlayerStruct)
foreign import ccall unsafe "&dd_destroyPlayer"
  destroyPlayerFinal :: FunPtr (Ptr PlayerStruct -> IO ())

foreign import ccall unsafe "fopen"
  fopen :: CString -> CString -> IO (Ptr CFile)
foreign import ccall unsafe "fclose"
  fclose :: Ptr CFile -> IO CInt

...

mkFinalizedPlayer :: Ptr PlayerStruct -> IO Player
mkFinalizedPlayer = newForeignPtr destroyPlayerFinal

newPlayerFile :: FilePath -> Image -> IO Player
newPlayerFile path image = do
  withCString path $ \cpath -> do
    withCString "rb" $ \cmode -> do
      file <- throwErrnoIfNull "fopen: " (fopen cpath cmode)
      withForeignPtr image $ \ptr -> do
        player <- dd_newPlayer_file file ptr >>= mkFinalizedPlayer
        FC.addForeignPtrFinalizer player (fclose file >> return ())
        return player

So I'm adding the "usual" finalizer, and with the help of
Foreign.Concurrent, I can add a second free-form one (fclose file >>
return ()), in order to close the file I opened at an appropriate time.

I'm looking forward hearing about other people's opinions, and wether
this is a correct solution to the initial problem or not.

I think there is another way to solve this, which is to provide the
finalizer still in haskell code, but export the haskell code using FFI,
so that I can use it as a plain, normal finalizer.  I'm still unsure
about this.

Cheers,
Maxime


More information about the Haskell-Cafe mailing list