Chicken-Scheme FFI Examples

I'm currently working on refactoring the FFI implementation for the Rebel Game Engine. It was previously written using the Bind chicken egg but I wanted to have more control over the implementation by using the low level foreign functions.

To help me better understand I made some examples that has the basic FFI implementations that I'll be needing for my project.


foreign-lambda example

Let's say we have a structure Vec3 and a function Vec3Create that we want to access from chicken-scheme.

typedef struct Vec3 {
    float x;
    float y;
    float z;
} Vec3;

Vec3* Vec3Create(float x, float y, float z)
{
    Vec3* v = (Vec3*)malloc(sizeof(Vec3));
    v->x = x;
    v->y = y;
    v->z = z;
    return v;
}

We could use foreign-lambda to bind to the function:

(define vec3_create
  (foreign-lambda
    (c-pointer (struct "Vec3"))   ; Return type, a pointer to a struct object of Vec3
    "Vec3Create"                  ; Name fo the function
    float float float))           ; The three parameters (x,y,z) to pass to the function

This would allow us to call the vec3_create function like so:

(vec3_create 1.1 2.2 3.3)

foreign-lambda* example

Let's bind another C function Vec3Print.

void Vec3Print(Vec3 v)
{
    printf("Vec3 to print: (%f, %f, %f)\n", v.x, v.y, v.z);
}

We could also use foreign-lambda* (Notice the asterisk). This is similar to foreign-lambda but accepts C code as a string.

(define vec3_print
  (foreign-lambda*
    void                          ; The return type
    (((c-pointer (struct "Vec3")) a0)) ; The parameter to pass, a pointer to a Vec3 object
    "Vec3Print(*a0);"))           ; The C code in string from
                                  ; Vec3Print accepts a non pointer, we dereference it

(vec3_print (vec3_create 1.1 2.2 3.3))   ; Creates a vec3 and prints it

Inline C code in foreign-lambda*

Here's another example using foreign-lambda*. This time there is no predefined C function, but instead we define the code inside the lisp function's body.

(define vec3_zero
  (foreign-lambda*
    (c-pointer (struct "Vec3"))
    ()                            ; Empty variables
    "Vec3* v = (Vec3*)Vec3Create(0.0f, 0.0f, 0.0f); 
    C_return(v);"))               ; Instead of "return", we use "C_return".

(vec3_print (vec3_zero))          ; Calls vec3_zero and prints the returned Vec3

Note that due to obscure technical reasons C_return must be used instead of return when returning a value. More info about this here.


Free-ing Vec3 pointers

Since Vec3Create allocates memory for a Vec3 struct using malloc, it's a good idea to free this when we are done using it. To do this we could bind a function to free.

(define free (foreign-lambda void "free" c-pointer))

(let ((v (vec3_zero)))
  (vec3_print v)
  (free v))

Setting up getters and setters

If we want to have access to variables of a struct object. We could do something like this:

(define vec3_x
  (foreign-lambda*
    float
    (((c-pointer (struct "Vec3")) a0))
    "C_return(a0->x);"))

(display (vec3_x (vec3_create 8.8 8.8 8.8)))

Now this is fine but it'll be a pain to specify an accessor for every variable. A better solution is to use the foreigners egg which allows the use of macros that will make our lives easier.

(import (chicken foreign))
(import foreigners)

;; Set up the accessors for Vec3 struct
(define-foreign-record-type (vec3 Vec3)
  (float x vec3_x vec3_x!)   ; vec3_x is a getter, vec3_x! is a setter
  (float y vec3_y vec3_y!)
  (float z vec3_z vec3_z!))

(let ((v (vec3_create 4.4 5.5 6.6)))
  (display (vec3_x v)) ; Display value of x
  (newline)

  (vec3_x! v 7.7)      ; Set x to 7.7
  (display (vec3_x v)) ; Display value of x
  (newline)

  (free v))

Binding enums

The foreigners egg also allows for the binding of enums using define-foreign-enum-type. Say we have an enum declaration Keys.

enum Keys {
  UP = 0,
  RIGHT = 1,
  DOWN = 2,
  LEFT = 3
};

:::scheme
(define-foreign-enum-type (keys int)
  (keys->int int->keys)
  ((up keys/up) UP)
  ((right keys/right) RIGHT)
  ((down keys/down) DOWN)
  ((left keys/left) LEFT))

(display keys/right)
(display keys/down)

These are the basic FFI implementations that I have explored. It should be enough for most uses. The example project with working code can be found on Github.

Also, check out the chicken-bind egg, it already has all of the code above conveniently in one package. If you don't need full control and just want a simple FFI solution then this is what you need.