Comment passer un object std :: function à une fonction prenant un pointeur de fonction?

J’essaie d’interagir avec une bibliothèque écrite en c , qui utilise ce modèle familier:

 void some_c_handler(void(*func)(void*), void* data); 

Maintenant, je veux écrire un wrapper C++ pour cette fonction qui ressemble à ceci:

 void my_new_cpp_handler(std::function&& func) { void (*p)() = foo(func); void* data = bar(func); some_c_handler(p, data); } 

some_c_handler et my_new_cpp_handler résolvent le même problème; ils assument une fonction quelconque avec un État. Mais ce dernier est préféré dans la mesure où il extrait une grande partie des détails de la mise en oeuvre de l’utilisateur et permet simplement de passer un object lambda.

Ainsi, my_new_cpp_handler doit prendre le paramètre func qui lui est atsortingbué, le convertir en pointeur de fonction et transmettre son état aux data .

Je ne connais pas suffisamment la norme ou l’implémentation de std::function pour savoir s’il s’agit même d’une requête raisonnable. foo et bar peuvent-ils exister?

En d’autres termes, ce que je veux, c’est pouvoir passer d’une fonction avec état à un gestionnaire de callback c sans avoir à instancier manuellement mon propre type de struct pour le transmettre. De toute évidence, std::function a déjà fait cela pour nous, alors j’aimerais bien pouvoir séparer le pointeur de la fonction de l’état et le transmettre au gestionnaire c -style. Est-ce possible?

Est-ce possible?

Non.

Vous pouvez envelopper un rappel de style C dans un std::function<>... , et le compilateur émettra du code pour extraire les data et le handler et les appeler. Cependant, le code exact à utiliser dépend du compilateur, de l’ ABI et de la bibliothèque C ++ standard utilisée. Vous ne pouvez pas reconstruire ce code comme par magie avec le wrapper std::function .

De plus, un std::function... (ou un lambda) arbitraire peut envelopper un code autre qu’un appel au gestionnaire de style C. Il devrait être évident qu’appliquer le code “reconstitué de manière magique” pour extraire le gestionnaire de style C d’une telle instance de std::function... ne peut pas réussir.

PS

En d’autres termes, ce que je veux, c’est pouvoir passer d’une fonction avec état à un gestionnaire de callback sans avoir à instancier manuellement mon propre type de structure pour le transmettre. De toute évidence, std::function a déjà fait cela pour nous

Alors, pourquoi ne pas utiliser ce que std::function a déjà fait pour vous:

 void my_new_cpp_handler(std::function&& func) { func(); // calls some_c_handler(p, data) IFF that is what "func" encodes. } 

Si vous examinez attentivement la documentation de cette bibliothèque, vous constaterez que le

 void some_c_handler(void(*func)(void*), void* data); 

Invoque func en lui passant l’argument data .

Il s’agit d’un modèle de conception très courant pour les bibliothèques C utilisant une fonction de rappel. En plus de la fonction de rappel, ils prennent également un pointeur opaque supplémentaire qui n’est pas interprété par la bibliothèque, mais qui est transmis aveuglément à la func . En d’autres termes, la bibliothèque C appelle

  func(data); 

Vous pouvez l’utiliser à partir de code C ++ pour passer un pointeur ordinaire à n’importe quelle classe.

Cela inclut std::function aussi.

L’astuce est que dans la plupart des situations, il sera nécessaire d’utiliser de new :

 auto *pointer=new std::function< function_type >... 

Le résultat final est un pointeur qui peut être passé dans la bibliothèque C avec un pointeur sur une “fonction trampoline”:

 some_c_handler(&cpp_trampoline, reinterpret_cast(pointer)); 

Et le trampoline recastile le pointeur opaque:

 void cpp_trampoline(void *pointer) { auto real_pointer=reinterpret_cast*>(pointer); // At this point, you have a pointer to the std::function here. // Do with it as you wish. 

Le seul détail dont vous aurez besoin ici pour résoudre ce problème consiste à déterminer la scope correcte du pointeur de fonction alloué dynamicment, afin d’éviter les memory leaks.

Vous pouvez créer une fonction wrapper dont le but est d’exécuter simplement le rappel std::function .

 void some_c_handler(void(*)(void*), void*) {} void std_function_caller(void* fn) { (*static_cast*>(fn))(); }; auto make_std_function_caller(std::function& fn) { return std::make_pair(std_function_caller, static_cast(&fn)); } void my_new_cpp_handler(std::function&& func) { const auto p = make_std_function_caller(func); some_c_handler(p.first, p.second); } 

Selon ce lien, l’object std::function ne possède aucun membre accessible pouvant fournir un access brut au pointeur. Vous devriez probablement définir une struct contenant un pointeur sur le pointeur de la fonction et l’object, ainsi qu’un wrapper de constructeur qui stocke l’adresse du pointeur dans la structure avant la construction de votre std::struct , de manière à affecter l’adresse stockée dans le pointeur. il pointe vers le paramètre de votre gestionnaire C.