0

Consider a library function that takes a callback function (pointer) as argument. If a regular function pointer is used, the function can be used with regular C and C++ functions, but no class methods can be used for that:

typedef void(*CALLBACK_T)(int);

void testCallback(CALLBACK_T cb){
    cb(5);
}

void intOut(int i){
    std::cout << i << std::endl;
}

class Test{
public:
    void intOut2(int i){
        std::cout << "Test: " << i << std::endl;
    }
};

void test(void){
    testCallback(&intOut);
    Test t;
    testCallback(&t.intOut2); //won't work
}

I could use std::function to encapsulate class members, but this would break C compatibility.

The only option I can think of is to create an additional context pointer to the callback handler:

typedef void(*CALLBACK2_T)(int, void*);

void testCallback2(CALLBACK2_T cb, void* context){
    cb(5, context);
}

but this would require to keep track of the context information as well (the callback isn't directly invoked within the library function).

Do you have a better idea how a callback can be used with C functions and C++ class members at the same time?

muffel
  • 6,043
  • 4
  • 42
  • 77
  • I think this is widely used this way you proposed. I can't see anything wrong with it. – πάντα ῥεῖ Apr 17 '14 at 11:24
  • @πάνταῥεῖ I still can't use capturing lambda expressions if I only introduce a context pointer, as they won't cast to the function pointer. This would be possible if I used `std::function` – muffel Apr 17 '14 at 11:27
  • I'd say it's complicated without using some kind of wrapper because of member functions taking the additional object parameter on which the method is invoked.. – perencia Apr 17 '14 at 11:28
  • See [this question](http://stackoverflow.com/questions/1000663/using-a-c-class-member-function-as-a-c-callback-function?rq=1). You can use a `static` class function, which -- assuming you can pass in some context when defining the callback [i.e. a class instance] -- can reflect the call to a class member function. – TripeHound Apr 17 '14 at 11:33
  • "but this would require to keep track of the context information as well" - Um. an object `this` *is* its context information, and without it a non-static member function pointer is useless. What you want cannot be done. You can template your callback and make it universal for both (one a fn-pointer, the other a contrived `bind` [**See it live**](https://ideone.com/ZsCdFk), but that is hardly ideal to what you're trying to do. – WhozCraig Apr 17 '14 at 12:32

3 Answers3

1

Imho there is no alternative.

If you do not want to keep track of the context in your library, there is no way to get the 'this' pointer to the callback function. You are forcing the consumer of your library to go for a global variable, which is very limiting.

Bgie
  • 463
  • 3
  • 10
  • This is incorrect. You can use a thunk. C# delegates and pinvoke are probably the most well known demonstration that your statement is incorrect. – David Heffernan Apr 17 '14 at 12:52
  • I was aware of the 'thunk' possibility, but this is not possible on all platforms that support C/C++. Self modifying code is not only 'dirty work', but, for example on certain microcontrollers, might be impossible. – Bgie Apr 18 '14 at 13:59
1

You have very little room to manoeuvre here. Overwhelmingly the most commonly used approach is an extra context pointer.

One plausible alternative is to create a thunk at runtime. That is you allocate some memory and mark it as executable. The memory then contains the instructions needed to call the member function. The subject of the call, the object instance, is located at a fixed offset relative to the thunk. This is pretty dirty work, and not exactly portable. I do not recommend it.

David Heffernan
  • 572,264
  • 40
  • 974
  • 1,389
0

The C equivalent to Test::intOut2() is :

intOut2(Test *this, int i)

So &t.intOut2 doesn't have the right type to be a CALLBACK_T.

It should work by specifying intOut2 as static :

class Test{
public:
     static void intOut2(int i){
        std::cout << "Test: " << i << std::endl;
    }
};
hyves
  • 11
  • 2