2009-01-31 8 views
4

J'ai une fonction dans une bibliothèque externe que je ne peux pas changer avec la signature suivante:Le passage d'une fonction membre non statique qualifié comme un pointeur de fonction

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

Je veux passer dans une fonction membre comme le rappel , car mon rappel a besoin de modifier les variables d'instance.

Il est évident que ce n'est pas possible avec un simple:

registerResizeCallback(&Window::Resize); 

donc je ne suis pas vraiment sûr de savoir comment résoudre le problème.

+1

Rien n'est impossible en C.Votre problème m'a intrigué, j'ai donc écrit un blogspot sur une façon hackish de le résoudre: http://nothingintoinsight.blogspot.com/2009/02/how-to-hack-closures-in-your-c-code-ou. html – user51568

Répondre

6

Vérifiez "[33.2] How do I pass a pointer-to-member-function to a signal handler, X event callback, system call that starts a thread/task, etc?" au C++ FAQ Lite:

Ne pas.

Parce qu'une fonction de membre n'a pas de sens sans objet pour l'invoquer, vous ne pouvez pas le faire directement

...

En tant que patch pour le logiciel existant, utilisez un haut niveau (non -member) fonctionne comme un wrapper qui prend un objet obtenu grâce à une autre technique.

+0

Je suppose que je vais juste utiliser le motif singleton. – ICR

9

Comme Igor Oks indicates, vous ne pouvez pas faire cela. Le reste de cette question n'est pas tellement une réponse à votre problème, mais une discussion sur la façon dont quelque chose comme ceci devrait fonctionner avec une API de rappel correctement conçue (il semble que celle que vous utilisez ne l'est pas).

La plupart des interfaces de rappel bien conçues vous permettent de fournir un "void *" ou un autre moyen d'obtenir un contexte dans le rappel. Une manière courante d'utiliser ceci avec C++ est de passer un pointeur d'objet dans le paramètre de contexte void *, puis la fonction de rappel peut le renvoyer dans un pointeur d'objet et appeler la méthode membre pour effectuer le travail réel. C'est dommage que l'API de rappel que vous utilisez ne fournisse pas de données de contexte.

Strictement parlant, le rappel doit être extern "C", mais l'utilisation de méthodes membres statiques pour les rappels est courante et je pense que dans la pratique il n'y a jamais de problème. (Cela suppose que l'API de rappel est une interface C, qui est de loin la plus courante).

Un exemple:

// callback API declaration's 

extern "C" { 
    typedef unsigned int callback_handle_t; 

    typedef void (*callback_fcn_t)(void* context, int data1, int data2); 

    callback_handle_t RegisterCallback(callback_fcn_t, void* context); 
    void UnregisterCallback(callback_handle_t); 
} 

// ---------------------------------- 

// prototype for wrapper function that will receive the callback and 
// transform it into a method call 

extern "C" 
static void doWorkWrapper(void* context, int data1, int data2); 


// the class that does the real work 

class worker { 
public: 
    worker() { 
     hCallback = RegisterCallback(doWorkWrapper, this); 
    } 

    ~worker() { 
     UnregisterCallback(hCallback); 
    } 

    void doWork(int data1, int data2) { 
     // ... 
    }; 

private: 
    callback_handle_t hCallback; 
}; 

// the wrapper that transforms the callback into a method call 
extern "C" 
static void doWorkWrapper(void* context, int data1, int data2) 
{ 
    worker* pWorker = static_cast<worker*>(context); 

    pWorker->doWork(data1, data2); 
} 
1

Expansion sur les conseils de Michael Burr, vous devrez savoir comment une fonction non membre peut accéder à l'instance de l'objet que vous modifiez. Une façon courante consiste à tirer profit de la portée des GLOBALS statiques dans C:

// Top of your .c module: 
static Window *gMyWindow; 

// The declaration 
extern "C" { 
    void* my_callback(int, int); 
} 

// Later, set it just before handing off the callback 
void somefunc() { 
    ... 
    gMyWindow = &windowObjectRef; 
    registerResizeCallback(my_callback); 
    windowObjectRef.SomeOtherWindowCallCausingCallbackInvoke(); 
    ... 
} 

// The callback in the same .c module as the global 
void my_callback(int x, int y) { 
    Window *object = gMyWindow; 
    object->Resize(x, y); 
} 

Je n'ai pas compilé/exécuter le code ci-dessus, donc il peut y avoir quelques réglages dans les détails, mais nous espérons que le concept est clair: la callback doit être un pont entre C et C++, et la question est comment obtenir votre objet "dans" le rappel pour être l'instance pour l'appel de la fonction membre. Il peut y avoir d'autres raisons dans votre environnement pourquoi l'exemple global ci-dessus ne fonctionnera pas, alors votre tâche serait de trouver quel autre mécanisme que globals vous permettrait de passer l'objet dans le rappel en fonction de votre situation .

Questions connexes