5

I have an old .dll with plain C interface which takes callbacks to invoke when some work is done. The callback it takes is of type void (f*)(char* arg).

I'm looking for a trick to pass a C++ function object there so that the callback is invoked with "this" pointer stored somewhere, something like bind, but simple bind doesn't work

To illustrate this: C interface:

typedef void (f*)(char* param) Callback;
void registerCallback(Callback c);

Usage in C++:

class A
{
    void func1()
    {
         registerCallback(std::bind(&A::func2, _1, this)); // obviously doens't work
    }

    void func2(char* param)
    { ... }
};
YSC
  • 38,212
  • 9
  • 96
  • 149
Dmitry
  • 317
  • 3
  • 10
  • 4
    Your C API should take `userdata`/`cookie`/`context` argument somewhere. If it doesn't the authors should suffer from capital punishment. – milleniumbug Feb 29 '16 at 15:27
  • 2
    `std::bind` returns a value of unspecified type which isn't convertible to the function pointer. For general strategy see https://blogs.msdn.microsoft.com/oldnewthing/20140127-00/?p=1963/ Lambdas won't work either because only captureless lambda can decay to function pointer. – milleniumbug Feb 29 '16 at 15:33
  • 1
    How is `char* param` used by the registerCallback? – SergeyA Feb 29 '16 at 15:35
  • @SergeyA it's an "out" parameter: the response is placed there – Dmitry Feb 29 '16 at 15:39
  • 1
    @Dmitry, than the only choice you have is to employ questionable practice of 'thunks' from ATL. No other way, sorry. – SergeyA Feb 29 '16 at 15:52
  • What is the point of '(char* param)' if you can not use it to pass 'this'? Stupid interface:( – Martin James Feb 29 '16 at 16:23
  • 1
    Anyone who issues a library that does not allow a 'user' context parameter to be passed around to the callback should be executed, preferably before they reproduce, or become teachers:( – Martin James Feb 29 '16 at 16:26
  • @MartinJames Unfortunately, nobody prevents them from becoming C library or POSIX designers. Lessons were not learned from `qsort` and `bsearch`. Neither were they learned from the file tree walking function `ftw`, when the NEW file tree walking function `nftw` was designed. Still no callback context! (Well, maybe you can over-allocate the `FTW` buffer structure and stick some stuff beyond its end). – Kaz Feb 29 '16 at 17:14

4 Answers4

3

The only way that to make this work with an instance of a class and a member function of the class, that I am aware of, is:

  1. Store a pointer to an object in a global variable.
  2. Register a non-member function.
  3. Call the member function from the non-member function using the global variable.
class A
{
    void func1();

    void func2(char* param)
    { ... }
};

// Global pointer
A* aPtr = NULL;

// Non-member function.
// extern "C" is probably needed if the older DLL is expecting
// an unmangled C function pointer.
extern "C" void globalFunc2(char* param)
{
   if ( aPtr == NULL )
   {
      // Deal with error
   }
   else
   {
      aPtr->func2(param);
   }
}

void A::func1()
{
    aPtr = this;
    registerCallback(globalFunc2);
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • Specifying `extern "C"` for `globalFunc2()` may be necessary depending on how the callback is declared. You may also want to add a warning about not using such an implementation when running multiple threads, especially if it's possible for the library to call the callback function from another thread. – Andrew Henle Feb 29 '16 at 16:34
  • @AndrewHenle, using `extern "C"` is probably required. I am leaving out the multiple threads issue since I don't fully understand the ramifications. – R Sahu Feb 29 '16 at 16:41
  • Why extern "C" is required here? From my understanding, it only needed when one searches for a function in a dll by name via some "GetProcAddress" – Dmitry Feb 29 '16 at 16:49
  • for multithreading usage the pointer should be stored in thread-local storage – Andriy Tylychko Feb 29 '16 at 17:20
1

The way I see it, the core of the problem is letting the caller of the callback function (the entity registerCallback is registering with) know which A object's func2 to call.

As far as I can understand from your problem, you basically have a bunch of A objects and you only want a number of these A objects to execute their respective func2s when the callback event occurs. However, the caller of the callback function does not know who to call when the callback event occurs. Since you mentioned that it's an old .dll, I assume we cannot just go in and change how registerCallback works, but we need to store the A objects that are registered for callback.

Therefore,

class Informer {
public:
  static void InformAllMembers(char* param) {
    for(auto& a : m_Members) { //Inform all As registered with me
      a->func2(param);
    }
  }
  static void Register(A* a) {
    m_Members.push_back(a);
  }
private:
  static std::vector<A*> m_Members;
};
std::vector<A*> Informer::m_Members;
...

registerCallback(Informer::InformAllMembers);

A a;
Informer::Register(&a);

NOTE: You will have to handle cases where some of the registered A objects are destroyed and unregister them from the Informer.

Nard
  • 1,006
  • 7
  • 8
0

This is simply an alternative to @RSahu's solution using static members in A class. IMHO, is is functionnally the same as global variables, but at least you get a namespace containment in you class:

class A
{
    static A* current_obj;

public:    
    void func1()
    {
         current_obj = this;
         registerCallback(func3);
    }

private:
    void func2(char* param)
    { ... }
    static void func3(char *param) {
        if (NULL == current_obj) {
            // error ...
        }
        current_obj->func2(param);
    }
...
};

And as demonstrated above, the registered functions can be private to the class, because the only function that need to be called externally is here func1.

But it suffers the same problem: you can register only one object at the same time.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
0

Maybe I'm not understanding the problem but why not just build a simple wrapper to hold the "this" pointer. So, the application will use this callback (or lambda)

void RegisterCallback_withthis(char* arg, void* thisptr)

Only a single callback function can be registered with the DLL at one time. So, a single global thisptr is good.

static void* thisptr;
void RegisterCallback(char* arg)
{
     RegisterCallback_withthis(argc, thisptr);
}

The application will have to setup the thisptr while registering the callback.

thisptr = this;
Mochan
  • 84
  • 1
  • 5