Search code examples
chaskellffi

Calling Haskell FFI Function Ptrs from C


I am trying to get the following code to work:

sample_hs :: CInt -> (CInt -> CInt)
sample_hs x = (x+)

foreign export ccall sample_hs :: CInt -> (CInt -> CInt)

I would like to be able to do something like this in c:

pf = sample_hs(2);
result = pf(3); //Should be 5;

When I try to do this, however, I get an error message:

error: too few arguments to function ‘sample_hs’

I am guessing that the interface between the language isn't working how I thought it would. Is there a way to do what I'm trying to do?


Solution

  • It is possible, FFI Does allow Higher-Order functions to be exported. Some modifications to your Haskell is required though:

    {-# LANGUAGE ForeignFunctionInterface #-}
    module Main where
    
    import Foreign.C.Types
    import Foreign
    
    foreign export ccall sample_hs :: CInt -> IO (FunPtr Sample)
    
    type Sample = CInt -> CInt
    foreign import ccall "wrapper" mkSample :: Sample -> IO (FunPtr Sample)
    
    sample_hs :: CInt -> IO (FunPtr Sample)
    sample_hs x = mkSample (x+) 
    
    main = return ()
    

    Higher-Order functions are exported in Haskell by using the explicit FunPtr type. Just to make it a bit clear I've named the higher ordered type Sample in this case. In order to be able to create a function pointer you need to use a "wrapper" function, hence the extra FFI declaration.

    I haven't tested this, But it should work fine, It compiles anyway. More on FunPtr here

    -- EDIT I have tested It and it works fine. returns 5 as expected.

    If you're by any chance doing this on windows, I have a package on hackage Hs2Lib that would export Haskell functions and compile them to a .DLL automatically for you. It also provides you includes for C/C++ and C#. If You're on Linux however, I'm still working on that.

    shameless plug :P

    Using Hs2Lib the only thing you need in your file is:

    module Test where
    
    -- @@ Export
    sample_hs :: Int -> IO (Int -> Int)
    sample_hs x = return (x+) 
    

    and a simple call to Hs2lib

    PS C:\Users\Phyx\Desktop> hs2lib .\Test.hs
    Linking main.exe ...
    Done.
    

    The reason for the IO and explicit return is that Int -> (Int -> Int) is just Int -> Int -> Int, since types are right associative. But Int -> IO (Int -> Int) indicates that you want to return a function. It's in IO because creating a function pointer is a side-effecting operation. For completeness the C file used is:

    #include <stdio.h>
    #include <stdlib.h>
    #include "Hs2lib_FFI.h"
    
    /*
     * 
     */
    int main(int argc, char** argv) {
    
        HsStart();
    
        CBF1_t pf = sample_hs(2);
        int result = pf(3);
        printf("%d\n", result);
    
        HsEnd();
        return (EXIT_SUCCESS);
    }
    

    So It's pretty plug-n-play. But again, It only works for Windows for now.