using the ffi with callbacks
Suppose I have a C function like this: void register_callback( void (*callback_fcn)(void *data), void *callback_data, void (*free_fcn)(void *data)); I think this is pretty common in C libraries. The idea is that you can register a callback along with a pointer to some data to pass to it, and when the C code is done with the callback+data it'll call your free_fcn, again passing the same data. I'd like to wrap this in Haskell, allowing me to pass in a Haskell function as a callback. The tricky part is that to pass in Haskell functions, I need to use the FFI "wrapper" import, which means I need to later free them. But the only place I can free them is within the "free" callback, and I've just discovered this isn't allowed! To elaborate, the code setting this up looks something like this: callback_fcn <- ... -- get a FunPtr using "wrapper" from the ffi free_fcn <- ... -- as above -- the callback data is just stuff that needs freeing callback_data <- newStablePtr (callback_fcn, free_fcn) register_callback callback_fcn callback_data free_fcn And my plan was: within the function free_fcn wraps, free callback_fcn, free the StablePtr, and then finally free free_fcn itself. However, I discovered this thread, which indicates that having a function free its FunPtr is not allowed: http://www.haskell.org//pipermail/glasgow-haskell-users/2006-March/009907.ht... I've poked around a bit online, and so far, the only solutions I've seen are 1) http://darcs.haskell.org/packages/GLUT/Graphics/UI/GLUT/Callbacks/Registrati... , which seems to involve three extra IORefs and unsafePerformIOs as well as an external library function for registering the scavenger. 2) http://darcs.haskell.org/gtk2hs/glib/System/Glib/ , which seems to either use a bunch of Haskell-specific C code I don't quite follow or use the self-delete pattern I described above (see mkFunPtrDestroyNotify in http://darcs.haskell.org/gtk2hs/glib/System/Glib/GObject.chs.pp). So my question is: is there really no simple way to support this pattern (which I would broadly refer to as "callbacks in general"), or am I missing something?
Evan Martin wrote:
Suppose I have a C function like this: void register_callback( void (*callback_fcn)(void *data), void *callback_data, void (*free_fcn)(void *data)); I think this is pretty common in C libraries. The idea is that you can register a callback along with a pointer to some data to pass to it, and when the C code is done with the callback+data it'll call your free_fcn, again passing the same data.
I'd like to wrap this in Haskell, allowing me to pass in a Haskell function as a callback. The tricky part is that to pass in Haskell functions, I need to use the FFI "wrapper" import, which means I need to later free them. But the only place I can free them is within the "free" callback, and I've just discovered this isn't allowed! ... However, I discovered this thread, which indicates that having a function free its FunPtr is not allowed: http://www.haskell.org//pipermail/glasgow-haskell-users/2006-March/009907.ht...
My take on this is that it should be allowed, and when we revise the FFI definition for Haskell' I think we should pin this down. Wolfgang Thaller argued in that thread that we can reasonably expect to be able to implement it for any architecture, and in fact GHC already does implement it for all architectures (except for IA64, IIRC). Cheers, Simon
On 7/20/06, Evan Martin <martine@danga.com> wrote:
The tricky part is that to pass in Haskell functions, I need to use the FFI "wrapper" import, which means I need to later free them. But the only place I can free them is within the "free" callback, and I've just discovered this isn't allowed!
free_fcn is normally a static function. It shouldn't need to free itself. I'm thinking something like: type Callback = StablePtr (Callback, DataStructure)-> IO () foreign import ccall "wrapper" mkCallback :: Callback -> IO (FunPtr Callback) foreign export "free_fcn" free_fcn :: Callback free_fcn sp = do (cb, ds) <- deRefStablePtr sp freeStablePtr sp freeHaskellFunPtr cb -- Taral <taralx@gmail.com> "You can't prove anything." -- Gödel's Incompetence Theorem
On 7/21/06, Taral <taralx@gmail.com> wrote:
On 7/20/06, Evan Martin <martine@danga.com> wrote:
The tricky part is that to pass in Haskell functions, I need to use the FFI "wrapper" import, which means I need to later free them. But the only place I can free them is within the "free" callback, and I've just discovered this isn't allowed!
free_fcn is normally a static function. It shouldn't need to free itself. I'm thinking something like:
type Callback = StablePtr (Callback, DataStructure)-> IO () foreign import ccall "wrapper" mkCallback :: Callback -> IO (FunPtr Callback) foreign export "free_fcn" free_fcn :: Callback
free_fcn sp = do (cb, ds) <- deRefStablePtr sp freeStablePtr sp freeHaskellFunPtr cb
Ah, I think I see. I hadn't looked at "export", but a static function does seem like what I want. But I also don't quite understand how the typing works out. For example, can you use StablePtr directly with FFI functions, or do they require you casting through Ptr? /* C code, as before but with a typedef for clarity */ typedef void (*Callback)(void*); void register_callback( Callback callback_fcn, void *callback_data, Callback free_fcn); -- importing this function. -- you had: type Callback = StablePtr (Callback, DataStructure)-> IO () foreign import ccall register_callback :: FunPtr Callback -> -- because mkCallback gives a FunPtr Ptr (Callback, DataStructure) -> -- or StablePtr? Callback -> -- but here don't we again need a FunPtr? IO ()
On 7/20/06, Evan Martin <martine@danga.com> wrote:
But I also don't quite understand how the typing works out. For example, can you use StablePtr directly with FFI functions, or do they require you casting through Ptr?
Yes. You can use any Storable with FFI functions, as far as I know. StablePtr is explicitly designed for this purpose, allowing you to "export" an object to the FFI while keeping the GC apprised of its use by external code.
/* C code, as before but with a typedef for clarity */ typedef void (*Callback)(void*); void register_callback( Callback callback_fcn, void *callback_data, Callback free_fcn);
-- importing this function. -- you had: type Callback = StablePtr (Callback, DataStructure)-> IO ()
foreign import ccall register_callback :: FunPtr Callback -> -- because mkCallback gives a FunPtr Ptr (Callback, DataStructure) -> -- or StablePtr? Callback -> -- but here don't we again need a FunPtr? IO ()
Oh, right. Try: type CallbackData = StablePtr (CallbackData, DataStructure) type Callback = CallbackData -> IO () foreign import ccall register_callback :: FunPtr Callback -> CallbackData -> FunPtr Callback -> IO () You get that FunPtr by the import/export trick described by Anatoly or you can just construct the FunPtr in main or an unsafePerformIO CAF. (We need a constructor syntax for top-level objects requiring IO!) -- Taral <taralx@gmail.com> "You can't prove anything." -- Gödel's Incompetence Theorem
On 7/20/06, Evan Martin <martine@danga.com> wrote:
To elaborate, the code setting this up looks something like this: callback_fcn <- ... -- get a FunPtr using "wrapper" from the ffi free_fcn <- ... -- as above -- the callback data is just stuff that needs freeing callback_data <- newStablePtr (callback_fcn, free_fcn) register_callback callback_fcn callback_data free_fcn And my plan was: within the function free_fcn wraps, free callback_fcn, free the StablePtr, and then finally free free_fcn itself.
As Taral mentioned there's no need in creating different free_fcn's for each new callback. The following may be considered as a linker hack but I see no reason why it could not work:
freeCallback :: StablePtr (FunPtr a) -> IO () freeCallback sPtr = do fPtr <- deRefStablePtr sPtr freeStablePtr sPtr freeHaskellFunPtr fPtr
foreign export ccall "_hs_some_really_private_symbol" freeCallback :: StablePtr (FunPtr a) -> IO () foreign import ccall "&_hs_some_really_private_symbol" free_fcn :: FunPtr (StablePtr (FunPtr a) -> IO ())
And then you can use it like this: ... callback_data <- newStablePtr callback_fcn register_callback callback_fcn callback_data free_fcn -- Tolik
participants (4)
-
Anatoly Zaretsky -
Evan Martin -
Simon Marlow -
Taral