Search code examples
haskellc2hs

How to call a C function that uses a callback in Haskell?


I am trying to call the following C function from Haskell using c2hs.

void rd_kafka_conf_set_rebalance_cb (
    rd_kafka_conf_t *conf,
    void (*rebalance_cb) (rd_kafka_t *rk,
                          rd_kafka_resp_err_t err,
                          rd_kafka_topic_partition_list_t *partitions,
                          void *opaque));

I am not familiar with c2hs and am having troubles with declaring bindings.

This is what I've tried:

--callback type
type RebalanceCbFun = 
    Ptr RdKafkaT -> CInt -> Ptr RdKafkaTopicPartitionListT -> Ptr Word8 -> IO ()

foreign import ccall safe "wrapper" 
    mkRebalanceCallback :: RebalanceCbFun -> IO (FunPtr RebalanceCbFun)

foreign import ccall unsafe "rd_kafka.h &rd_kafka_conf_set_rebalance_cb"
    rdKafkaConfSetRebalanceCb :: Ptr RdKafkaConfT -> FunPtr RebalanceCbFun -> IO ()

However I have the following error while compiling this code:

Unacceptable type in foreign declaration:
  ‘Ptr RdKafkaConfT
   -> FunPtr
        (Ptr RdKafkaT
         -> Int32 -> Ptr RdKafkaTopicPartitionListT -> Ptr Word8 -> IO ())
   -> IO ()’ cannot be marshalled in a foreign call
  A foreign-imported address (via &foo) must have type (Ptr a) or (FunPtr a)
When checking declaration:
  foreign import ccall unsafe "static rd_kafka.h &rd_kafka_conf_set_rebalance_cb" rdKafkaConfSetRebalanceCb
    :: Ptr RdKafkaConfT -> FunPtr RebalanceCbFun -> IO ()

I don't understand what part is missing Ptr or FunPtr here. I also tried to wrap the whole rdKafkaConfSetRebalanceCb into FunPtr like:

foreign import ccall unsafe "rd_kafka.h &rd_kafka_conf_set_rebalance_cb"
    rdKafkaConfSetRebalanceCb :: FunPtr (Ptr RdKafkaConfT -> FunPtr RebalanceCbFun -> IO ())

Not sure if it makes sense although it compiles... But then I don't know how to use this function, this is what I tried (and this is the signature I'd like to have in the end):

kafkaConfSetRebalanceCb :: RdKafkaConfTPtr -> RebalanceCbFun -> IO ()
kafkaConfSetRebalanceCb conf cb = do
    cb' <- mkRebalanceCallback cb
    withForeignPtr conf $ \c -> rdKafkaConfSetRebalanceCb c cb'
    return ()

Now it complains that I don't have a function to call, just a pointer to a function (because of that FunPtr wrapping).

Can you show me how C bindings could be done correctly for the C signature above?


Solution

  • The following file compiles fine for me (with ghc -c Test.hs). The only real difference is that I have omitted the & in the foreign import.

    {-# LANGUAGE ForeignFunctionInterface #-}
    module Test where
    import Data.Word
    import Foreign.C.Types
    import Foreign.Ptr
    import Foreign.ForeignPtr
    
    newtype RdKafkaT = RdKafkaT (Ptr RdKafkaT)
    newtype RdKafkaConfT = RdKafkaConfT (Ptr RdKafkaConfT)
    newtype RdKafkaTopicPartitionListT = RdKafkaTopicPartitionListT (Ptr RdKafkaTopicPartitionListT)
    
    type RebalanceCbFun = 
        Ptr RdKafkaT -> CInt -> Ptr RdKafkaTopicPartitionListT -> Ptr Word8 -> IO ()
    
    foreign import ccall safe "wrapper" 
        mkRebalanceCallback :: RebalanceCbFun -> IO (FunPtr RebalanceCbFun)
    
    foreign import ccall unsafe "rd_kafka.h rd_kafka_conf_set_rebalance_cb"
        rdKafkaConfSetRebalanceCb :: Ptr RdKafkaConfT -> FunPtr RebalanceCbFun -> IO ()
    
    kafkaConfSetRebalanceCb :: ForeignPtr RdKafkaConfT -> RebalanceCbFun -> IO ()
    kafkaConfSetRebalanceCb conf cb = do
        cb' <- mkRebalanceCallback cb
        withForeignPtr conf $ \c -> rdKafkaConfSetRebalanceCb c cb'