3

Think of your basic GLUT programs. They simply run from a main method and contain callbacks like `glutMouseFunc(MouseButton) where MouseButton is the name of a method.

What I have done is I have encapsulated the main file into a class, so that MouseButton is no longer a static function but has an instance. But doing this gives me a compilation error :

Error 2 error C3867: 'StartHand::MouseButton': function call missing argument list; use '&StartHand::MouseButton' to create a pointer to member c:\users\angeleyes\documents\visual studio 2008\projects\capstone ver 4\starthand.cpp 388 IK Engine

It is not possible to provide a code sample as the class is quite huge.

I have tried using this->MouseButton but that gives the same error. Can't a pointer to an instance function be given for callback?

genpfault
  • 51,148
  • 11
  • 85
  • 139
Prnbs
  • 31
  • 1
  • 2

6 Answers6

5

As the error message says, you must use &StartHand::MouseButton syntax to get a pointer to a member function (ptmf); this is simply mandated as part of the language.

When using a ptmf, the function you are calling, glutMouseFunc in this case, must also expect to get a ptmf as a callback, otherwise using your non-static MouseButton won't work. Instead, a common technique is for callbacks to work with a user-supplied void* context, which can be the instance pointer—but the library doing the callbacks must explicitly allow this parameter. It's also important to make sure you match the ABI expected by the external library (the handle_mouse function below).

Since glut doesn't allow user-supplied context, you have to use another mechanism: associate your objects with glut's current window. It does provide a way to get the "current window", however, and I've used this to associate a void* with the window. Then you simply need to create a trampoline to do the type conversion and call the method.

Machinery:

#include <map>

int glutGetWindow() { return 0; } // make this example compile and run  ##E##

typedef std::pair<void*, void (*)(void*,int,int,int,int)> MouseCallback;
typedef std::map<int, MouseCallback> MouseCallbacks;
MouseCallbacks mouse_callbacks;
extern "C" void handle_mouse(int button, int state, int x, int y) {
  MouseCallbacks::iterator i = mouse_callbacks.find(glutGetWindow());
  if (i != mouse_callbacks.end()) { // should always be true, but possibly not
                                    // if deregistering and events arrive
    i->second.second(i->second.first, button, state, x, y);
  }
}

void set_mousefunc(
  MouseCallback::first_type obj,
  MouseCallback::second_type f
) {
  assert(obj); // preconditions
  assert(f);
  mouse_callbacks[glutGetWindow()] = MouseCallback(obj, f);
  //glutMouseFunc(handle_mouse); // uncomment in non-example  ##E##
  handle_mouse(0, 0, 0, 0); // pretend it's triggered immediately  ##E##
}

void unset_mousefunc() {
  MouseCallbacks::iterator i = mouse_callbacks.find(glutGetWindow());
  if (i != mouse_callbacks.end()) {
    mouse_callbacks.erase(i);
    //glutMouseFunc(0); // uncomment in non-example  ##E##
  }
}

Example:

#include <iostream>

struct Example {
  void MouseButton(int button, int state, int x, int y) {
    std::cout << "callback\n";
  }
  static void MouseButtonCallback(
    void* self, int button, int state, int x, int y
  ) {
    static_cast<Example*>(self)->MouseButton(button, state, x, y);
  }
};

int main() {
  Example obj;
  set_mousefunc(&obj, &Example::MouseButtonCallback);

  return 0;
}

Notice that you don't call glutMouseFunc directly anymore; it is managed as part of [un]set_mousefunc.


Just in case it isn't clear: I've rewritten this answer so it should work for you and so that it avoids the C/C++ linkage issue being debated. It will compile and run as-is (without glut), and it should work with glut with only minor modification: comment or uncomment the 4 lines marked ##E##.

  • 6
    You should not use static functions as callbacks. Technically the library is expecting a C-Function. Static member methods are not C-Functions and you are just getting lucky that your compiler uses the same ABI for both. There is no requirement for it to do so. And just to grind it in, the more expensive compilers that use highly optimized ABI's (and pass parameters in registers rather than on the stack) will break with the following code. – Martin York Jan 14 '10 at 21:43
  • Actually this example is standard C++ and guaranteed to work in C++. The problem is language and compiler interop, and your point is valid in those circumstances. OpenGL is in exactly that situation. Fully answering how to specify compiler-/platform-specific ABIs when declaring the function is outside the scope of this question, however. –  Jan 14 '10 at 22:08
  • I posted a question (link to follow) so that you can discuss the ABI matter more thoroughly. I'm really interested in this, because I had often used static methods to do Roger's "trampoline" trick (hehe I like that term, Roger). http://stackoverflow.com/questions/2068022/in-c-is-it-safe-portable-to-use-static-member-function-pointer-for-c-api-ballb – Emile Cormier Jan 14 '10 at 22:35
  • 1
    I didn't name it. It means any function whose purpose is delegating to another function, and in some cases means a specific assembly construct which is optimized for exactly that. –  Jan 14 '10 at 22:45
  • Martin: I believe you that it causes problems in some compilers, but now I'm curious which ones. Care to share? –  Jan 14 '10 at 23:01
2

No, a pointer to an instance function can not be given to a callback function expecting a function pointer of a certain signature. Their signatures are different. It won't compile.

Generally such APIs allow you to pass in a void* as a "context" parameter. You pass in your object there, and write a wrapper function which takes the context as the callback. The wrapper casts it back to whatever class you were using, and calls the appropriate member function.

Terry Mahaffey
  • 11,775
  • 1
  • 35
  • 44
1

You can't replace a static callback with an instance one. When the caller calls your callback, on what instance whoul it call? In other words, how does the caller pass in the formal 'this' argument?

The solution is to have a static callback stub and pass the instance as argument, which implies the callee must accept an arbitrary pvoid that will pass back when invoking the callback. In the stub, you can then call the non-static method:

class C {
    void f() {...}
    static void F(void* p) {
      C* pC = (C*)p;
      pC->f();
    }
  }

  C* pC = ...;
  someComponent.setCallback(&C::F, pC);
Remus Rusanu
  • 288,378
  • 40
  • 442
  • 569
1

Contrary to what everyone seems to be saying, you most definitely CAN use a non-static member function as a callback method. It requires special syntax designed specifically for getting pointers to non-static members, and special syntax to call that function on a specific instance of a class. See here for a discussion of the needed syntax.

Here is sample code that illustrates how this works:

#include <cstdlib>
#include <string>
#include <iostream>
#include <vector>
#include <sstream>
#include <algorithm>
using namespace std;


class Operational
{
public:
    Operational(int value) : value_(value) {};

    string FormatValue() const ;

private:
    int value_;

};

string Operational::FormatValue() const
{
    stringstream ss;
    ss << "My value is " << value_;
    return ss.str();
}

typedef string(Operational::*FormatFn)() const; // note the funky syntax

Operational make_oper(int val)
{
    return Operational(val);
}

int main()
{
    // build the list of objects with the instance callbacks we want to call
    Operational ops[] = {1, 2, 3, 5, 8, 13};
    size_t numOps = sizeof(ops)/sizeof(ops[0]);

    // now call the instance callbacks
    for( size_t i = 0; i < numOps; ++i )
    {
        // get the function pointer
        FormatFn fn = &Operational::FormatValue;    

        // get a pointer to the instance
        Operational* op = &ops[i];  

        // call the callback on the instance
        string retval = (op->*fn)();

        // display the output
        cout << "The object @ " << hex << (void*)op << " said: '" << retval << "'" << endl;
    }


    return 0;
}

The output of this program when I ran it on my machine was:

The object @ 0017F938 said: 'My value is 1' 
The object @ 0017F93C said: 'My value is 2' 
The object @ 0017F940 said: 'My value is 3' 
The object @ 0017F944 said: 'My value is 5' 
The object @ 0017F948 said: 'My value is 8' 
The object @ 0017F94C said: 'My value is 13'
John Dibling
  • 99,718
  • 31
  • 186
  • 324
  • Presumably the OP doesn't have control over the library to modify it to use ptmfs, so if it doesn't already support them, then they cannot be used. As I have the oldest answer and already state this, I'm not sure what you mean by "contrary to what everyone seems to be saying". –  Jan 14 '10 at 22:02
  • @Roger Pate: Someone is misunderstood - either me, or the OP, or the other respondants. My understanding was that OP was asking "Can member functions be used as callback functions" and everyone else said "No." I may very well have misunderstood both the OP and the respondants. – John Dibling Jan 14 '10 at 22:23
  • @Roger - Your example shows binding using static function. So I don't think your answer is correct. You need to use ::* to get ptr to method of any object instance. – Ketan Jan 15 '10 at 01:25
  • Ketan: What do you mean? ::* is not an operator. How is my answer incorrect? –  Jan 15 '10 at 18:47
  • John: That's what I was trying to comment originally, I never said "no", but "you can use a ptmf directly, if the api which you're using supports that, but since it's most likely that api doesn't support them, here's ..." –  Jan 15 '10 at 18:48
0

The following works in c++ to define a c callback function, useful for example when using glut (glutDisplayFunc, glutKeyboardFunc, glutMouseFunc ...) when you only need a single instance of this class :

MyClass * ptr_global_instance = NULL;

extern "C" void mouse_buttons_callback(int button, int state, int x, int y) {

    // c function call which calls your c++ class method

    ptr_global_instance->mouse_buttons_cb(button, state, x, y);
}

void MyClass::mouse_buttons_cb(int button, int state, int x, int y) {

    // this is actual body of callback - ie.  if (button == GLUT_LEFT_BUTTON) ...
    // implemented as a c++ method
}

void MyClass::setup_glut(int argc, char** argv) { // largely boilerplate glut setup

    glutInit(&argc, argv);

    // ... the usual suspects go here like glutInitWindowSize(900, 800); ...

    setupMouseButtonCallback(); // <-- custom linkage of c++ to cb

    // ... other glut setup calls here
}

void MyClass::setupMouseButtonCallback() {

    // c++ method which registers c function callback

    ::ptr_global_instance = this;
    ::glutMouseFunc(::mouse_buttons_callback);
}

In your MyClass header we add :

void mouse_buttons_cb(int button, int state, int x, int y);

void setupMouseButtonCallback();

This also works using identical logic flows to setup your glut call to glutDisplayFunc(display)

Scott Stensland
  • 26,870
  • 12
  • 93
  • 104
0

You cannot use a non-static member function in this case. Basically the type of the argument expected by glutMouseFunc is

void (*)(int, int, int, int)

while the type of your non-static member function is

void (StartHand::*)(int, int, int, int)

First problem is that types don't really match. Second, in order to be able to call that method, the callback would have to know which object ( i.e. "this" pointer ) your method belongs to ( that's pretty much why the types are different in the first place ). And third, I think you're using the wrong syntax to retrieve the method's pointer. The right syntax should be: &StartHand::MouseButton.

So, you have to either make that method static or use some other static method that would know which StartHand pointer to use to call MouseButton.

Iustin Amihaesei
  • 116
  • 1
  • 1
  • 3