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 = """ """;
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(); }