ExpandCollapsePrev Next Index

+ 10.1 Callbacks

There is a common need in many API's to provide callback functions. Lets look at an example, which we will embed in Felix:

  body demo = """
  typedef void mouse_click_handler_t 
   (int x, int y, 
   long window, 
   void *client_data
  );
  
  void register_mouse_click_handler 
  (
    long window, 
    mouse_click_handler_t * handler, 
    void * client_data
  )
  {
   // demo registration function will just call the callback
   // with some test data
   int x = 42;
   int y = 38;
   (*handler)(x,y,window, client_data);  
  }
  """;

In this example we have a C function register_mouse_click_handler which accepts a pointer to a handler function handler of type {mouse_click_handler_t) and some arbitrary client_data which is passed to the handler.

Normally, we'd be accepting a handler for each window and calling the function registered for that window when the user clicks the mouse in it, however for purpose of this demo the registration function will just call the callback immediately.

Now, whilst we could write a callback handler in C, it is more fun to write one in Felix. To do this, we will create a wrapper function to use as the callback, and then pass it the Felix function as the client_data. The wrapper function then casts it back to a Felix function and invokes it.

Here's how we generate the wrapper function:

  callback proc mouse_click_handler_wrapper: 
   int * int * long * 
   mouse_click_handler_wrapper 
  ;

In this code the function name mouse_click_handler_wrapper is used in its own type to indicate the position of the client_data pointer. Clearly this mechanism will not work on callbacks that do not accept a client_data pointer.

The effect of the function is to take the first three arguments and a Felix function which accepts the same three arguments, and apply that function to the arguments, returning the same result as that function would. In other words this is just an apply function, but with one important difference: its a C function which applies a Felix function.

Now we will write our callback in Felix:

  proc myClickHandler (x:int, y:int, w:long) 
  {
   println$ "User clicked in Window " + w.str +
      " at (" + x.str + ", " + y.str ")"
   ;
  }

This function must have the same type as the callback, except its a Felix function (or procedure), and the client_data argument is dropped (because this function actually is the client data!

Now we specify the type of the C handler for convenience:

  typedef c_handler_t = int * int * long * address --> void;

so that we can create a Felix binding of the C registration function we're going to call:

  proc register_mouse_click_handler: long * c_handler_t * address requires demo;

Notice the type of the client_data is given as address which is the Felix way of spelling {void*}.

Now all we need to do is call the registration function with our wrapper mouse_click_handler_wrapper as the callback and the Felix function myClickHandler as the client_data:

  register_mouse_click_handler (99l,
   C_hack::cast[c_handler_t] mouse_click_handler_wrapper,
   C_hack::cast[address] myClickHandler
  );

Notice the casts. The wrapper function has the correct type for the Felix function as the client_data but C just uses a {void*} so we have to cast the wrapper to the type expected in C.

Similarly, we have to cast our Felix function to an address because that's what C expects for the client_data.

User clicked in Window 99 at (42, 38)

Now, please look at the generated C++, for me this is here:

~/felix>less ~/.felix/cache/text/Users/johnskaller/felix/cbdemo.hpp
~/felix>less ~/.felix/cache/text/Users/johnskaller/felix/cbdemo.cpp

In particular the compiler generated this:

//------------------------------
//CALLBACK C PROCEDURE <40780>: mouse_click_handler_wrapper
void mouse_click_handler_wrapper(int _a0, int _a1, long _a2, _a5339t_55573 _a3){
 _pt55578* callback = (_pt55578*)_a3;
 ::flx::rtl::con_t *p = callback->call(0,_tt55577(_a0,_a1,_a2));
 while(p)p = p->resume();
 }