Skip to content

Latest commit

 

History

History

external-c-functions

Calling External C/C++ Functions

Categorifier-C supports generating C code that calls external C/C++ functions. This comes handy if your program cannot be implemented entirely in Haskell, and need to use a C/C++ library to do part of the work. This document explains how to do so with a small toy example.

In the example we are going to call an external C function called VeryUsefulFunction which takes two integers, and returns two integers and a double: their sum, difference and average. The code is in very_useful_function.c.

Let's use /tmp/external-c-functions as our working directory for this example. Copy very_useful_function.h and very_useful_function.c to the working directory.

These are the steps needed in order to write Haskell code that can be compiled into C that interfaces with this function.

Step 1: define the input and output types of VeryUsefulFunction in Haskell. See CInput and COutput in CTypes.hs. There should be a single Haskell type corresponding to the input of the C function, and a single Haskell type corresponding to the output.

Step 2: generate C types for these Haskell types, and helper functions for converting the C structs to/from primitive arrays. To do so, run

cabal run categorifier-c-examples:external-c-functions -- gen-ctypes /tmp/external-c-functions

This generates a bunch of C and C++ files in /tmp/external-c-functions. The ones relevant to this example are c_types.h which contains the C structs corresponding to CInput and COutput, as well as from_arrays.c and to_arrays.c which contain functions converting CInput and COutput to/from arrays.

Step 3: write a C function that wraps VeryUsefulFunction using the array calling convention. A function conforming to the array calling convention takes 22 primitive arrays, 11 for input and 11 for output. It should have the following prototype:

void fun_name(
    // input
    const bool *input_bool, const int8_t *input_int8_t,
    const int16_t *input_int16_t, const int32_t *input_int32_t,
    const int64_t *input_int64_t, const uint8_t *input_uint8_t,
    const uint16_t *input_uint16_t, const uint32_t *input_uint32_t,
    const uint64_t *input_uint64_t, const float *input_float,
    const double *input_double,

    // output
    bool *output_bool, int8_t *output_int8_t, int16_t *output_int16_t,
    int32_t *output_int32_t, int64_t *output_int64_t, uint8_t *output_uint8_t,
    uint16_t *output_uint16_t, uint32_t *output_uint32_t,
    uint64_t *output_uint64_t, float *output_float, double *output_double);

The definition of the wrapper function should construct an input struct from the input arrays, call the C function proper, and populate the output arrays from the output. See very_useful_function_wrapper.h and very_useful_function_wrapper.c for the wrapper function for VeryUsefulFunction. Copy these two files to the working directory.

Step 4: write the Haskell function to be compiled into C. We can obtain a function of type CInput -> COutput via kForeignFunctionCall:

runVeryUsefulFunction :: CInput -> COutput
runVeryUsefulFunction =
  kForeignFunctionCall
    (Proxy @C)
    "Wrap_VeryUsefulFunction"
    (Imported "very_useful_function_wrapper.h")
    Nothing

The last argument is of type Maybe (CInput -> COutput). It lets us optionally provide a Haskell implementation of the external C function, in this case VeryUsefulFunction. Providing a Haskell implementation allows us to run runVeryUsefulFunction, as well as functions calling it, in Haskell, which is convenient for development and debugging. Otherwise, we can only run these functions by compiling them to C and running the C code.

Now we can use runVeryUsefulFunction normally in Haskell. See f in F.hs for example. To compile f to C, run

cabal run categorifier-c-examples:external-c-functions -- /tmp/external-c-functions f

And now cd /tmp/external-c-functions && gcc -c f.c should succeed.