ExpandCollapsePrev Next Index

+ 2.1 Callback Magic

In the previous tutorial we saw how to use a Felix function as a callback that C code can invoke, by using a wrapper function that depends only on the callback type.

Unfortunately, we had to write some messy C code and also export the Felix type to establish a type name we could use in C.

There must be a better way! And of course, there is!

Let's start again with our requirement, the registration function:

    body cback_h =
    """
    void register_callback(int x, void (*cb)(int, void*), void *client_data)
    {
      cb (x, client_data);
    }
    """;

which, for pedagogical purpose will actually call the callback.

Here is te Felix binding for the function we need to use.

  proc register_callback: int * (int * address --> void) * address
    requires cback_h;

Again, what we want to do here is use a felix function for the callback:

  proc flx_cb(x:int){ println$ "Called back with " + str x; }

Now, to generate the wrapper we just say this:

  callback proc wrap_flx_cb: int * wrap_flx_cb;

Note the weird argument type: its a trick! We use the name of the wrapper itself in the position of the client data pointer argument of the C callback function type. Recall the C type is given by

void (*)(int, void*)

which in Felix is written

int * address --> void

and so you see the client data pointer is the second argument.

Now we can just do it: here's our test case:

  register_callback
    (
      42, 
      C_hack::cast[int * address --> void] wrap_flx_cb, 
      C_hack::cast[address] flx_cb
    )
  ;

Of course we expect the result

Called back with 42

as before.

+ 2.1.1 Why the casts?

You may wonder, why does the generated wrapper function wrap_flx_cb have the wrong type? If the compiler is generating it, why can't it have the correct type?

The answer is: Felix rules! The generated wrapper has the correct type. It's C that gets the type wrong, as usual!

The type of the wrapper is actually:

int * (int -> void) --> void)

which means: this is a C procedure, since it has a long arrow to void, accepting two arguments, an int and a Felix function of type int->void which is just a procedure of accepting an int. To prove this works correctly without any casts we do another test, with the registration function written in Felix instead of C:

  wrap_flx_cb(66, flx_cb);

and of course we expect:

Called back with 66