[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