Boost Synapse

Using Meta Signals to Connect to a C-Style Callback API

This example program demonstrates how to use Boost Synapse to bind a C-style callback API that uses function pointers. By using meta signals, such C-style callbacks can be connected directly through the Boost Synapse connect function. The program consists of several source files (also available for download on the main page):

  • callback_api.h/.c define an example 3rd-party API that uses C-style callbacks. It lets users set a single function pointer callback on objects of type api_handle. The callback is invoked with different values for the event argument to report on different events. In this case calling api_do_this generates API_EVENT_THIS, and calling api_do_that generates API_EVENT_THAT.
  • synapsify.hpp/.cpp contain the machinery needed to handle Boost Synapse meta signals. Once initialized, user calls to connect<synapse_callback> are forwarded automatically to api_set_callback/api_clear_callback (from callback_api.h) if needed.
  • Finally, main.cpp creates an api_handle object and then connects two different handlers that get called when the api_do_this/api_do_that is called.

Program output:

Detected synapse_callback connection on api_handle at 0x00000000004EE980 (first connection, calling api_set_callback)
Detected synapse_callback connection on api_handle at 0x00000000004EE980
handler1 called on api_handle object at 0x00000000004EE980, event=1
handler2 called on api_handle object at 0x00000000004EE980, event=1
handler1 called on api_handle object at 0x00000000004EE980, event=2
handler2 called on api_handle object at 0x00000000004EE980, event=2
Detected expiring synapse_callback connection on api_handle at 0x00000000004EE980
Detected expiring synapse_callback connection on api_handle at 0x00000000004EE980 (last connection, calling api_clear_callback)

callback_api.h:

typedef struct api_handle api_handle;
api_handle * api_create_object();
void api_destroy_object( api_handle * );

//The user_data pointer is typical in C-style callbacks APIs, so it's included
//here even though this example has no use for it.
typedef void (*api_callback_type)( api_handle *, void * user_data, int event );

void api_set_callback( api_handle *, api_callback_type, void * user_data );
void api_clear_callback( api_handle * );

//When api_do_this is called, it calls the callback passing API_EVENT_THIS as
//the event argument; api_do_that calls the callback with API_EVENT_THAT.
#define API_EVENT_THIS 1
#define API_EVENT_THAT 2
void api_do_this( api_handle * );
void api_do_that( api_handle * );

callback_api.c:

#include "callback_api.h"
#include <malloc.h>

struct
api_handle
    {
    api_callback_type callback;
    void * user_data;
    };

api_handle *
api_create_object()
    {
    api_handle * p=(api_handle *)malloc(sizeof(api_handle));
    p->callback=0;
    return p;
    }

void
api_destroy_object( api_handle * p )
    {
    free(p);
    }

void
api_set_callback( api_handle * p, api_callback_type c, void * user_data )
    {
    p->callback=c;
    p->user_data=user_data;
    }

void
api_clear_callback( api_handle * p )
    {
    p->callback=0;
    }

void
api_do_this( api_handle * p )
    {
    if( p->callback )
        p->callback(p,p->user_data,API_EVENT_THIS);
    }

void
api_do_that( api_handle * p )
    {
    if( p->callback )
        p->callback(p,p->user_data,API_EVENT_THAT);
    }

synapsify.hpp:

extern "C"
    {
    #include "callback_api.h"
    }

//This is the Synapse Signal that corresponds to the callbacks received from the C-style callback API.
typedef struct synapse_callback_(*synapse_callback)( api_handle *, int event );

//Connect synapse::meta::connected<synapse_callback> and synapse::meta::disconnected<synapse_callback>
//to set/clear the api_handle callbacks when synapse_callback signals are connected or disconnected.
void synapsify();

synapsify.cpp

#include "synapsify.hpp"
#include <boost/synapse/connect.hpp>
#include <boost/synapse/connection.hpp>
#include <iostream>

namespace synapse=boost::synapse;

namespace
    {
    void
    emit_fwd( api_handle * h, void *, int v )
        {
        (void) synapse::emit<synapse_callback>(h,h,v);
        }
    }

void
synapsify()
    {
    static boost::shared_ptr<synapse::connection> c=synapse::connect<synapse::meta::connected<synapse_callback> >(synapse::meta::emitter(),
        [ ]( synapse::connection & c, unsigned flags )
            {
            boost::shared_ptr<api_handle> h=c.emitter<api_handle>();
            std::cout << "Detected synapse_callback " << ((flags&synapse::meta::connect_flags::connecting)?"":"dis") << "connection on api_handle at 0x" << h.get();
            if( flags&synapse::meta::connect_flags::first_for_this_emitter )
                {
                assert(flags&synapse::meta::connect_flags::connecting);
                std::cout << " (first connection, calling api_set_callback)";
                api_set_callback(h.get(),&emit_fwd,0);
                }
            else if( flags&synapse::meta::connect_flags::last_for_this_emitter )
                {
                assert(!(flags&synapse::meta::connect_flags::connecting));
                std::cout << " (last connection, calling api_clear_callback)";
                api_clear_callback(h.get());
                }
            std::cout << std::endl;
            } );
    }

main.cpp

#include "synapsify.hpp"
#include <boost/synapse/connect.hpp>
#include <iostream>

namespace synapse=boost::synapse;

namespace
    {
    void
    handler1( api_handle * h, int event )
        {
        std::cout << "handler1 called on api_handle object at 0x" << h << ", event=" << event << std::endl;
        }
    void
    handler2( api_handle * h, int event )
        {
        std::cout << "handler2 called on api_handle object at 0x" << h << ", event=" << event << std::endl;
        }
    }

int
main()
    {
    //Connect the meta signal handlers to deal with  api_set_callback and api_clear_callback
    //automatically -- see callback_api.h and synapsify.cpp.
    synapsify();

    //Use shared_ptr with a custom deleter to hold an api_handle object.
    boost::shared_ptr<api_handle> h(api_create_object(),&api_destroy_object);

        {
        //The meta signal handlers deal with api_set_callback and api_clear_callback as needed,
        //so now we can use Synapse to connect to the C-style API callbacks. Note that we can
        //create many connections even though the C-style API supports only a single callback
        //per api_handle object.
        auto c1=synapse::connect<synapse_callback>(h,&handler1);
        auto c2=synapse::connect<synapse_callback>(h,&handler2);

        //This invokes handler1 and handler2, in that order, passing API_EVENT_THIS.
        api_do_this(h.get());

        //This invokes handler1 and handler2, in that order, passing API_EVENT_THAT.
        api_do_that(h.get());
        }

    //At this point all synapse_callback connections have expired and the meta handlers have
    //cleared the callback on the api_handle object, so the calls below do not invoke any handlers.
    api_do_this(h.get());
    api_do_that(h.get());

    return 0;
    }

See also: meta::connected | Tutorial