0

I'm working with a Blue Giga BLE112 usb dongle. They supply an API in the form of several header & *.c files. It all works off of callbacks. For example.

int main()
{
    // defined in a "cmd_def.h" & implemented in "cmd_def.c"
    ble_cmd_connection_get_status();

    // read messages off the usb serial port forever
    // At some point, we'll read the message that triggers the connection status event.
    while (true) { readMessages(); }
}

// Call back that we implement
void ble_evt_connection_status(const struct ble_msg_connection_status_evt_t *msg)
{
    // This is terrible because we could call the command from anywhere 
    // and have no idea what context the event is firing under 
    // with no way to pass any information into the callback except from global state.

    if (msg->flags & connection_connected) 
    {
        printf("WE'RE CONNECTED!);
    }
}

There are around 100 or so pairs of these Commands and responseEventCallbacks.

Now, I'm trying to implement a small C++ console application, but I'm having an extremely hard go of it. I've been trying to wrap this behavior in a C++ class, but am failing miserably. At each and every turn I run into issues trying to use these callbacks to set state in the class. (Obviously, I can't because the callbacks are static and can't access members of the class.)

Was this a misguided idea? I thought that if I could introduce an OO anti-corruption layer, I could make it easier to deal with the global state and callback hell I've found myself in.

Does anyone have any insight into how to cleanly implement this kind of C callback API in C++? I'm staring at all these callbacks thinking that this is going to be a nightmare to maintain, but I'm also unable to figure out how to wrap these command/response events into a class.

Would it be better to simply forget the classes and implement this as a "module" of functions?

For reference, here's an example of what I'm trying to accomplish.

// nice clean OO api

bool scanning = false;

BLUEGIGA_API bool BlueGiga::IsConnected()
{
    _isConnected = false;

    ble_cmd_connection_get_status(0);
    scanning = true;

    while (scanning)
    {
        if (!ReadMessage())
        {
            return false;
        }
    }

    return _isConnected;
}

// C callback that should not be part of my C++ api, nor can it be a member because C can't use C++ members as callbacks.

void ble_evt_connection_status(const struct ble_msg_connection_status_evt_t *msg)
{
    // _isConnected isn't identified because we don't have a `this` instance in here.
    _isConnected = (msg->flags & connection_connected);

    scanning = false;
}
  • I would probably make a singleton class connect to all these callbacks and work your way from there. – Teimpz Aug 24 '16 at 20:25
  • I cannot see whether the C callback allows you to hold some sort of private context (that you could make to point to your instance) - that should be somewhere in msg, itf at all. If it doesn't, its probably badly designed and you should refrain from trying to wrap it into a class. – tofro Sep 19 '16 at 8:49
  • @tofro I'm gonna go with "it's badly designed". There's no context. If we decide to use the hardware beyond this prototype, I'm going to need to re-implement their protocol parser. I think I found a way to get the code under test, but it involves making changes to the files that Blue Giga provides. – RubberDuck Sep 19 '16 at 9:21
  • I have an idea of how to do this. I am assuming that the callback function must be named that specific name. You could do it the same way that Win32++ calls CreateWindowEx: keep a map of function pointer to this pointer so that when the callback gets called it can look up the callback in the map and get the this pointer. I will try to find time to write an answer. – Jerry Jeremiah Nov 3 '16 at 3:42
1

Basically you're not only getting the API into an object-oriented way but you want to transform it into a synchronous API from an asynchronous API ?

Doesn't seems like a good idea to me if it has been made like this, there might be some reason while you should not change it.

C++ API with callbacks are nothing unseen and nothing incompatible with OOP, you can find them in boost Asynchronous networking library (Boost ASIO : http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio.html), may be looking a little in will give you some idea.

Note : Reading boost headers might be a bit difficult.

In the end i think you should keep the asynchronous way in OOP. This is surely doable using some not so obivous syntax but i don't know which one.

And for the design of your application, think about how UI works generally, you usually have one thread for the UI, one (or more) for the rest, i guess that the best way to go. The "UI thread" will lock any new command while the current one is processed, he will then print the result and ask for a new command. UI Thread will be callbacked by the API running in the other.

0

As long as you will only do one of each kind of request at a time you could keep a map of callback function to this pointer. I think this is your use case because you are looping on ReadMessage inside of each command function. Perhaps something kind of like this:

#include <stdio.h>
#include <map>

// provided by the library - somehow

const int connection_connected = 0x01;
struct ble_msg_connection_status_evt_t { int flags; } msg = {0xFF};
void ble_evt_connection_status(const struct ble_msg_connection_status_evt_t *msg);
void ble_cmd_connection_get_status(int) { }
bool ReadMessage() { ble_evt_connection_status(&msg); return true; }

// provided by you

struct BlueGiga;

std::map<void*,BlueGiga*> this_finder; // this could be in thread local storage...

struct BlueGiga
{
    bool IsConnected()
    {
        _isConnected = false;

        this_finder.insert(std::make_pair((void*)::ble_evt_connection_status,this));

        ble_cmd_connection_get_status(0);

        _scanning = true;

        while (_scanning)
        {
            if (!ReadMessage())
            {
                return false;
            }
        }

        return _isConnected;
    }

    void ble_evt_connection_status(const struct ble_msg_connection_status_evt_t *msg)
    {
        _isConnected = (msg->flags & connection_connected);
        _scanning = false;
    }

    int _isConnected;
    bool _scanning;
};

int main()
{
    BlueGiga blueGiga;
    return blueGiga.IsConnected();
}

// one for every callback - could use a macro to make it easier

void ble_evt_connection_status(const struct ble_msg_connection_status_evt_t *msg)
{
    BlueGiga* object = this_finder[(void*)::ble_evt_connection_status];
    object->ble_evt_connection_status(msg);
}

/* sample macro

#define DECLARE_CALLBACK(func,param) \
void func(const struct param *msg) \
{ \
    BlueGiga* object = this_finder[(void*)::func]; \
    object->func(msg); \
}

DECLARE_CALLBACK(ble_evt_connection_status,ble_msg_connection_status_evt_t)

*/

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.