I want to handover a class member as a callback function in C-style to an external DLL.
What I got so far:
void MyCallback(double a, unsigned char b, unsigned char* c)
{
// do something
}
The DLL provides a definition:
extern "C" BOOL CALLBACK ExternalDLL_SetCallback(void(*pCallbackFunc)(double a, unsigned char b, unsigned char* c));
ExternalDLL_SetCallback(&MyCallback); // this works
Because I need access to class members of MyClass inside the callback function, I want to have MyCallback become a class member.
I tried to use std::bind() to capture this and pass it over with the callback:
using namespace std::placeholders;
typedef void (*cbf_t)(double, unsigned char, unsigned char*);
class MyClass {
public:
void MyCallback(double a, unsigned char b, unsigned char* c) { this->doClassStuff(a); }; // callback function as a class member
void doClassStuff(double a); // example function
void setCallback() {
m_cbfunc = std::bind(&MyClass::MyCallback, this, _1, _2, _3);
ExternalDLL_SetCallback((cbf_t)&m_cbfunc);
};
std::function<void(double,unsigned char,unsigned char)> m_cbfunc;
}
This actually compiles and runs, but the program terminates as soon as a callback is actually called. Unfortunately, the program can only be executed on a remote machine where I have no options to use the debugger.
I also tried to use a lambda function to do the capture magic:
m_cbfunc = [this](double a, unsigned char b, unsigned char* c) { this->doClassStuff(a); }; // Lambda callback function
Most likely, in either case the C-style conversion (cbf_t) is the source of evil here. static_cast<cbf_t>(std::bind(...)) or static_cast<cbf_t>(std::function(...)) are marked by the compiler, so probably it doesn't even make sense to cast the result to a function pointer.
Is there a simple way to get around this pitfall without descending too far into assembler-style implementation?
In practice, I will be having a bunch of MyClass objects setting their callbacks and being called occasionally from the external DLL. If the callback is a member function, each call is automatically assigned to the right MyClass object.
But, as I can only get a static callback function so far, I don't see a good way to keep track of which object had registered the callback when it's coming in.
Can you give me advice on how this could be implemented in an easy way?
Update:
I understood that lambdas with capture cannot be casted into a function pointer. It works only for plain lambdas. In my case: cbf_t func = +[](double a, unsigned char b, unsigned char* c) { std::cout << a << b << c; }; where the "+" replaces an explicit conversion to function pointer type.
Now, I got the idea to create a map<std::function<void(double,unsigned char, unsigned char*)>, MyClass*> where I map generically created lambdas to my MyClass objects and than have the lambdas delegate the call to their respective MyClass object. But again, there is a show-stopper. I can easily get the address of the lambda of course when I create it, but inside the lambda body, I seem to have no chance to get it's actual address to compare it to the keys of my map. The only ways to get the address of the lambda are by introducing a [&fn] capture or adding a (this auto self, ...) function parameter to the lambda (only C++23) or using the y_combinator (which essentially does the same). But in either way, I'll be unable to pass them to my API DLL.
Of course I could define a bunch of Callback functions like this
#include <vector>
#include <map>
#include <iostream>
typedef void (*cbf_t) (double, unsigned char, unsigned char*);
class MyClass
{
public:
MyClass(unsigned int x) { m_x = x; };
void doClassStuff(double a) { std::cout << a << "from MyClass obj" << m_x; };
void setCallback(cbf_t &func) { /*ExternalDLL_SetCallback(func);*/ };
inline static std::map<cbf_t, MyClass*> myclass_map;
protected:
unsigned int m_x;
};
void MyCallback0(double a, unsigned char b, unsigned char *c) { MyClass::myclass_map[&MyCallback0]->doClassStuff(a); };
void MyCallback1(double a, unsigned char b, unsigned char *c) { MyClass::myclass_map[&MyCallback1]->doClassStuff(a); };
void MyCallback2(double a, unsigned char b, unsigned char *c) { MyClass::myclass_map[&MyCallback2]->doClassStuff(a); };
std::vector<cbf_t> cbs;
int main()
{
cbs.push_back(&MyCallback0);
cbs.push_back(&MyCallback1);
cbs.push_back(&MyCallback2);
for (auto& el : cbs)
{
MyClass *mc = new MyClass(std::distance(&cbs.front(), &el));
mc->setCallback(el);
MyClass::myclass_map[el] = mc;
}
}
But in the end, I have to create the callback functions in a dynamic way at runtime and I haven't found a good approach how to achieve that without running into the restrictions that lambda functions have. But obviously, there must be an easy way.
3 Answers 3
std::function<void(double,unsigned char,unsigned char)> m_cbfunc;
//...
ExternalDLL_SetCallback((cbf_t)&m_cbfunc);
This actually compiles and runs, but the program terminates as soon as a callback is actually called.
Not surprising. You've passed to ExternalDLL_SetCallback an address that is not an address of an object of cbf_t type. Anything can happen with code like this, crash is likely to happen.
Because I need access to class members of MyClass inside the callback function, I want to have MyCallback become a class member.
No you don't. An argument to ExternalDLL_SetCallback must be of the declared type which is not a member function type.
So what do you really need to do? Depends on your specific use case.
If all calls to the callback in your program are supposed to operate on a single MyClass object, you can store a global struct and simply use it:
struct CallbackContext {
MyClass* classObject = nullptr;
} g_theContext;
void MyCallback(double a, unsigned char b, unsigned char* c)
{
g_theContext.classObject->doClassStuff(a);
//...
}
//...
// set callback
assert(g_theContext.classObject == nullptr); //once
g_theContext.classObject = this;
ExternalDLL_SetCallback(&MyCallback);
If the library supports multiple callbacks and you want to register a number of callbacks with different MyClass objects that, too, can be arranged:
struct CallbackContext {
std::list<MyClass*> processors;
} g_theContext;
void MyCallback(double a, unsigned char b, unsigned char* c)
{
for(MyClass* obj : processors){
obj->doClassStuff(a);
//...
}
}
//...
// set callback
if(g_theContext.processors.empty()){
ExternalDLL_SetCallback(&MyCallback); //once
}
g_theContext.processors.push_back(this);
Since the library itself is a global object, you're essentially enhancing it with MyClass* storage for this specific callback, but do it on your side of the code.
(Notice that this is a single-thread code; if callbacks can be registered and|or called from multiple threads, you'll need thread safety measures for the shared object g_theContext.)
I will be having a bunch of MyClass objects setting their callbacks and being called occasionally from the external DLL. If the callback is a member function, each call is automatically assigned to the right MyClass object.
This may be requesting more than the library is willing to give, unfortunately. Given the interface, the library clearly has one callback or callback list and isn't supposed to differentiate whether to call this particular callback on that particular event.
If you want to add this functionality, the main question you need to ask yourself is, "what exactly differentiates one callback-triggering event from another"? For example, if an event can be identified by the c callback argument, you can add the corresponding logic to the CallbackContext:
struct CallbackContext {
std::map<std::string, MyClass*> processors;
} g_theContext;
void MyCallback(double a, unsigned char b, unsigned char* c)
{
if(auto p = g_theContext.processors.find(c); p != g_theContext.processors.end()){
p->doClassStuff(a);
}
}
If, instead, you can foresee which class you want to process the callback just as you initiate the event and the program is single-threaded, you can add that logic, too: have one pointer in g_theContext and set it to the proper value before calling the DLL.
But in the worst case, the problem is unsolveable. Suppose it's some kind of a network-interaction DLL. Suppose three different instances of your class "registered", through whatever means, three callbacks. Suppose three events have now happened. Their a, b and c values have no correlation with which object "wants" to process them. They all happen in some previously-unseen DLL-generated threads. They all are called from the same address of the DLL code. In that case, how exactly can the code decide which object event#2 "belongs" to?
3 Comments
MyCallbackA, MyCallbackB, MyCallbackC and so on (their amount is determined by the library header and thus finite). Each of those will check a different context on call (or a different part of the same context) and thus can potentially call doClassStuff for a different MyClass object. You can save yourself some typing with templates (template<int N> void callback(){g_ctx[N]->doClassStuff()}).std::bind() and capturing lambdas are not compatible with C-style function pointers, so they will not work for this task.
Your callback requires a MyClass* object pointer in order to access the members of MyClass, so you need a way to pass your object's this pointer into MyCallback().
The signature of ExternalDLL_SetCallback() that you have shown does not allow for this. However, if the DLL provides some other way to pass a user-defined value into MyCallback(), then you can pass your object's this pointer that way, eg:
class MyClass {
public:
static void MyCallback(double a, unsigned char b, unsigned char* c) {
MyClass *pThis = (MyClass*) ExternalDLL_GetCallbackData();
pThis->doClassStuff(a);
};
void doClassStuff(double a); // example function
void setCallback() {
ExternalDLL_SetCallback(&MyClass::MyCallback);
ExternalDLL_SetCallbackData(this);
};
};
But, if that is not the case, then you are stuck using either global/thread-local variables or manually-crafted thunks to pass the required MyClass* pointer around.
2 Comments
The "correct" answer to this sort of problem is to change the callback interface so that some (usually pointer-sized) user-provided parameter is also passed to the callback. That pattern allows associating user state with the callback.
If that is not possible, the next alternative is to use something like libffi closures to generate the callback at run-time. You create a closure that associates your needed data with the call, then provide that closure address as the callback. When the library invokes the closure, you unpack the bundled data and invoke the "real" call on the object.
Comments
Explore related questions
See similar questions with these tags.
ExternalDLL_SetCallback.cbf_t, the library seems to not be very mature. Sooner or later they will add avoid*argument to the API which users can use to supply user defined data that will be included in the callback.static_castand if the compiler refuses the cast then it is likely illegal, and will result in a crash when used