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.