I have defined the following module to help me with FFI function export:
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, TypeSynonymInstances #-}
module ExportFFI where
import Foreign
import Foreign.C
class FFI basic ffitype | basic -> ffitype where
toFFI :: basic -> IO ffitype
fromFFI :: ffitype -> IO basic
freeFFI :: ffitype -> IO ()
instance FFI String CString where
toFFI = newCString
fromFFI = peekCString
freeFFI = free
I'm struggling with the instance for functions. Can someone help me?
There are two things you can do with functions involving the FFI: 1) Marshalling: this means converting a function to a type that can be exported through the FFI. This accomplished by
FunPtr
. 2) Exporting: this means creating a means for non-Haskell code to call into a Haskell function.Your FFI class helps with marshalling, and first I create a few sample instances of how to marshal functions.
This is untested, but it compiles and I expect it would work. First, let's change the class slightly:
This says that given the type of either "basic" or "ffitype", the other is fixed[1]. This means it's no longer possible to marshal two different values to the same type, e.g. you can no longer have both
The reason for this is because
freeFFI
can't be used as you've defined it; there's no way to determine which instance to select from just the ffitype. Alternatively you could change the type tofreeFFI :: ffitype -> basic -> IO ()
, or (better?)freeFFI :: ffitype -> IO basic
. Then you wouldn't need fundeps at all.The only way to allocate a FunPtr is with a "foreign import" statement, which only works with fully instantiated types. You also need to enable the
ForeignFunctionInterface
extension. As a result thetoFFI
function, which should return anIO (FunPtr x)
, can't be polymorphic over function types. In other words, you'd need this:for every different function type you want to marshal. You also need the
FlexibleInstances
extension for this instance. There are a few restrictions imposed by the FFI: every type must be a marshallable foreign type, and the function return type must be either a marshallable foreign type or an IO action which returns a marshallable foreign type.For non-marshallable types (e.g. Strings) you need something slightly more complex. First of all, since marshalling happens in IO you can only marshal functions that result in an IO action. If you want to marshal pure functions, e.g. (String -> String), you need to lift them to the form (String -> IO String).[2] Let's define two helpers:
These convert the types of functions to the appropriate marshalled values, e.g.
wrapStrFn :: (String -> IO String) -> (CString -> IO CString); wrapStrFn = wrapFn
. Note thatunwrapFn
uses "Control.Exception.bracket" to ensure the resource is freed in case of exceptions. Ignoring this you could writeunwrapFn fn = toFFI >=> fn >=> fromFFI
; see the similarity to wrapFn.Now that we have these helpers we can start to write instances:
As before, it's not possible to make these functions polymorphic, which leads to my biggest reservation about this system. It's a lot of overhead because you need to create separate wrappers and instances for each type of function. Unless you're doing a lot of marshalling of functions, I would seriously doubt it's worth the effort.
That's how you can marshal functions, but what if you want to make them available to calling code? This other process is exporting the function, and we've already developed most of what's necessary.
Exported functions must have marshallable types, just like
FunPtr
s. We can simply re-use thewrapFn
to do this. To export a few functions all you need to do is wrap them withwrapFn
and export the wrapped versions:Unfortunately this setup only works for single-argument functions. To support all functions, let's make another class:
Now we can use
exportFunction
for functions with 1 and 2 arguments:Now you just need to write more instances of
ExportFunction
to automatically convert any function to the appropriate type for exporting. I think this is the best you can do without either either using some type of pre-processor or unsafePerformIO.[1] Technically, I don't think there's any need for the "basic -> ffitype" fundep, so you could remove it to enable one basic type to map to multiple ffitypes. One reason to do so would be to map all sized ints to Integers, although the
toFFI
implementations would be lossy.[2] A slight simplification. You could marshal a function
String -> String
to the FFI type ofCString -> IO CString
. But now you can't convert theCString -> IO CString
function back toString -> String
because of the IO in the return type.