2016-10-30 1 views
10

J'ai une API C tierce qui attend une fonction de rappel __stdcall.
Mon code dispose d'une fonction de rappel __cdecl fournie en externe.Modification de la convention d'appel

Je ne peux pas passer mon pointeur de fonction à l'API C car ils sont considérés comme des types différents.
Contourner le système de types et utiliser reinterpret_cast<> entraîne naturellement une erreur d'exécution.

Voici un exemple de here:

// C-API 
// the stdcall function pointer type: 
typedef CTMuint(__stdcall *CTMwritefn)(const void *aBuf, CTMuint aCount, void *aUserData); 

// A function needing the callback: 
CTMEXPORT void __stdcall ctmSaveCustom(CTMcontext aContext, CTMwritefn aWriteFn, void *aUserData, int *newvertexindex); 
                  ^^^^^^^^^^^^^^^^^^^ 

////////////////////////////////////////////////////////////////////////////// 

// C++ 
CTMuint __cdecl my_func(const void *aBuf, CTMuint aCount, void *aUserData); 

// I want to call here: 
ctmSaveCustom(context, my_func, &my_data, nullptr); 
//      ^^^^^^^ 

est-il un moyen de convertir en toute sécurité et/ou envelopper une fonction avec une convention d'appel dans une autre?

J'ai trouvé un moyen de le faire en passant un castel-captureless-lambda qui appelle un deuxième capturant lambda. Le premier est passé en tant que rappel, le second via le void* user_data. Cela fonctionne et est de type sécurisé. Mais il est assez compliqué pour quelque chose qui semble si simple.

+1

Pouvez-vous pas seulement créer une enveloppe cdecl qui transmettra l'appel à votre rappel réel? – krzaq

+0

@krzaq: Comment feriez-vous cela? Ce n'est pas aussi évident que cela semble ... Je vais ajouter un exemple. –

+0

Pouvez-vous - en dehors du pointeur de fonction - transmettre également des "données utilisateur" (généralement un "void *")? Sinon, une fonction wrapper va être un peu compliqué en ce qui concerne la sécurité des threads/plusieurs rappels. –

Répondre

11

Vous pouvez faire un wrapper pour la traduction entre les différentes conventions d'appel:

template<typename Func, Func* callback> 
auto make_callback() 
{ 
    return &detail::callback_maker<Func, callback>::call; 
} 

avec callback_maker défini comme

template<typename T, T*> 
struct callback_maker; 

template<typename R, typename... Params, R(*Func)(Params...)> 
struct callback_maker<R(Params...), Func> 
{ 
    static R __stdcall call(Params... ps) 
    { 
     return Func(std::forward<Params>(ps)...); 
    } 
}; 

Ce programme vise à être une solution assez générale, vous permettant de spécifier la fonction prototype. Vous pouvez l'utiliser comme suit:

// external_api(&not_stdcall_func); // error 
external_api(make_callback<void(int,int), &not_stdcall_func>()); 

demo


Si le pointeur doit être déterminé lors de l'exécution, vous pouvez garder le rappel dans les données de l'utilisateur. Vous devez gérer correctement la durée de vie de cette fonctionnalité, mais il est probable que vous en ayez déjà besoin. Encore une fois, tenter une solution générique. Faire un rappel et lui dire quel argument est le pointeur de données utilisateur:

template<typename Callback, size_t N> 
auto make_callback() 
{ 
    using callback_maker = detail::callback_maker<Callback, N>; 
    return &callback_maker::call; 
} 

Avec callback_maker défini comme

template<typename T, size_t N> 
struct callback_maker; 

template<typename R, typename... Params, size_t N> 
struct callback_maker<R(*)(Params...), N> 
{ 
    using function_type = R(Params...); 

    static R __stdcall call(Params... ps) 
    { 
     void const* userData = get_nth_element<N>(ps...); 
     auto p = static_cast<pair<function_type*, void*> const*>(userData); 
     return p->first(ps...); 
    } 
}; 

et get_nth_element comme

template<size_t N, typename First, typename... Ts> 
decltype(auto) get_nth_element_impl(false_type, First&& f, Ts&&...); 

template<size_t N, typename First, typename... Ts> 
decltype(auto) get_nth_element_impl(true_type, First&&, Ts&&... ts) 
{ 
    return get_nth_element_impl<N-1>(integral_constant<bool, (N > 1)>{}, forward<Ts>(ts)...); 
} 

template<size_t N, typename First, typename... Ts> 
decltype(auto) get_nth_element_impl(false_type, First&& f, Ts&&...) 
{ 
    return forward<First>(f); 
} 

template<size_t N, typename... Ts> 
decltype(auto) get_nth_element(Ts&&... ts) 
{ 
    return get_nth_element_impl<N>(integral_constant<bool, (N > 0)>{}, forward<Ts>(ts)...); 
} 

Maintenant, sur le site d'appel

using callback_t = CTMuint(*)(const void *aBuf, CTMuint aCount, void *aUserData); 
auto runtime_ptr = &not_stdcall_func; 

pair<callback_t, void*> data; 
data.first = runtime_ptr; 
data.second = nullptr; // actual user data you wanted 

auto callback = make_callback<callback_t, 2>(); 

ctmSaveCustom({}, callback, &data, nullptr); 

demo


Selon la suggestion de Andrey Turkin, vous pouvez remplacer le pointeur de données d'utilisateur dans la liste des paramètres. Avec forward_as_tuple, il évite le besoin de get_nth_element.La fonction d'appel amélioré:

static R __stdcall call(Params... ps) 
{ 
    auto params_tuple = forward_as_tuple(ps...); 
    void const* userData = get<N>(params_tuple); 
    auto p = static_cast<pair<function_type*, void*> const*>(userData); 
    get<N>(params_tuple) = p->second; 
    return apply(p->first, move(params_tuple)); 
} 

et est ici une implémentation simpliste de 17 de apply C++:

template<typename Func, typename T, size_t... Is> 
decltype(auto) apply_impl(Func f, T&& t, index_sequence<Is...>) 
{ 
    return f(get<Is>(t)...); 
} 

template<typename Func, typename... Ts> 
decltype(auto) apply(Func f, tuple<Ts...>&& tup) 
{ 
    return apply_impl(f, move(tup), index_sequence_for<Ts...>{}); 
} 

demo

+1

Je n'ai jamais vu cette façon de décomposer (refléter) les types de fonction (pointeur). Très cool en utilisant la spécialisation de modèle. –

+0

Comme discuté dans les commentaires pour la réponse de @ StoryTeller, et ainsi attendu, j'obtiens l'erreur suivante: 'erreur C2975: 'callback': argument de modèle invalide pour 'make_callback', expression de la constante de compilation attendue' –

+0

@AdiShavit a ajouté ma prise le rappel d'exécution. Peut-être que vous trouverez cela utile. De toute façon, c'était une question amusante – krzaq

4

Dans le cas de visual C++ (à partir de VC11), state- moins lambdas implémente un opérateur de conversion pour utiliser des pointeurs de toutes les conventions d'appel.

Alors this, peut tout aussi bien

#include <iostream> 
using namespace std; 

int __cdecl foo() 
{ 
    return 2; 
} 

void bar (int (__stdcall *pFunc)()) 
{ 
    cout << pFunc()*2; 
} 

int main() { 

    bar([](){ return foo(); }); 

    return 0; 
} 
+0

En fait, bien que le principe soit vrai, cela ne peut pas fonctionner comme vous le montre puisque dans mon cas 'foo()' n'est pas une déclaration de fonction (qui peut être utilisée dans un lambda sans capture), mais un pointeur de fonction en tant que paramètre à la lambda rendant capture pleine et donc non diffusable. –

+1

@AdiShavit, si vous (et le compilateur) n'avez pas accès à la définition de la fonction complète, la prémisse entière est impossible. Même la réponse de ** krzaq ** ne fonctionnera pas si vous obtenez le pointeur au moment de l'exécution. – StoryTeller

+0

@AdiShavit ce qu'il a dit. Notez que j'utilise le pointeur de fonction comme un paramètre de modèle - il doit être connu à la compilation (bien, lien?) Heure – krzaq

3

me Répondre, en espérant que quelqu'un a une solution plus simple. L'approche est la même que celle décrite pour here.

Nous allons utiliser les éléments suivants:

  1. lambdas Captureless peuvent être automatiquement CAST pour fonction des pointeurs avec une convention d'appel souhaitée.
  2. La fonction C-API fournit un moyen void* user_data de transmettre des données au rappel.

Nous passerons la C-API deux labmdas:

  1. On est coulé captureless à la convention d'appel correcte;
  2. L'autre capture le rappel fn-ptr et est passé comme user_data au lambda sans appel à appeler. Il capture les deux le rappel d'origine et l'original user_data pour un usage interne.

Voici le code:

// This is a lambda that calls the (cdecl) callback via capture list 
// However, you can't convert a non-captureless lambda to a function pointer 
auto callback_trampoline = [&callback, &user_data](const void *aBuf, CTMuint aCount) -> CTMuint 
{ 
    return callback(aBuf, aCount, user_data); 
}; 

using trampoline_type = decltype(callback_trampoline); 

// so we create a capture-less wrapper which will get the lambda as the user data! 
// this CAN be cast to a function pointer! 
auto callback_wrapper_dispatcher = [](const void *aBuf, CTMuint aCount, void *aUserData) -> CTMuint 
{ 
    auto& lambda = *reinterpret_cast<trampoline_type*>(aUserData); 
    return lambda(aBuf, aCount); 
}; 

ctmSaveCustom(context_, callback_wrapper_dispatcher, &callback_trampoline, nullptr); 

C'est de type sûr et fonctionne comme prévu.

Il serait cool de faire de cela un outil générique similaire à ce qui est suggéré dans la réponse de @krzaq.

MISE À JOUR:
est ici une formulation plus simple avec une seule captureless lambda, mais le même concept:

auto payload = std::tie(callback, user_data); 
using payload_type = decltype(payload); 
auto dispatcher = [](const void *aBuf, CTMuint aCount, void *aUserData)->CTMuint 
{ 
    // payload_type is visible to the captureless lamda 
    auto& payload = *reinterpret_cast<payload_type*>(aUserData); 
    return std::get<0>(payload)(aBuf, aCount, std::get<1>(payload)); 
}; 
ctmSaveCustom(context_, dispatcher, &payload, nullptr); 
+0

@krzaq: voir la formulation mise à jour avec un seul lambda, essentiellement ce que vous avez suggéré mais non générique. –

4

Si le rappel est pas connu au moment de la compilation vous avez des options suivantes:

  • Utilisez une fonction de wrapper unique et passez le rappel de la cible dans user_data. Pro - raisonnablement facile à utiliser Con - a besoin de pour son propre usage; requiert des signatures de fonction très similaires
  • Utilise la classe wrapper, alloue l'instance de la classe et passe this dans user_data.Pro - plus polyvalent car il peut capturer certaines données dans chaque instance (par exemple, il peut stocker user_data pour le rappel de cible ou transmettre des données supplémentaires au rappel cible); con - nécessité de gérer la durée de vie de l'instance wrapper
  • Construire des thunk séparées pour chaque rappel de cible distincte. Pro - ne nécessite pas d'utiliser user_data; Con - assez bas niveau et joli nonportable (dans les deux compilateur dans OS); pourrait être difficile à faire; difficile à faire en C++ sans recourir à l'assemblage.

La première option ressemblerait à quelque chose comme ça (déchirant sans vergogne au large @krzaq):

template<typename T> struct callback_maker; 
template<typename R, typename... Params> struct callback_maker<R(Params...)> { 
    static R __stdcall call_with_userdata_as_last_parameter(Params... ps, void* userData) { 
     R(__cdecl *Func)(Params...) = reinterpret_cast<R(__cdecl *)(Params...)>(userData); 
     return Func(std::forward<Params>(ps)...); 
    } 
}; 
template<typename Func> constexpr auto make_callback() { 
    return &callback_maker<Func>::call_with_userdata_as_last_parameter; 
} 

... 
extern void external_api(void(__stdcall*)(int,int,void*), void* userdata); 
extern void __cdecl not_stdcall_func(int,int); 
external_api(make_callback<void(int,int)>(), &not_stdcall_func); 

Probablement pas utile pour vous car vous avez besoin userData pour les deux callbacks.

La seconde option:

template<typename T> struct CallbackWrapper; 
template<typename R, typename... Params> struct CallbackWrapper<R(Params...)> { 
    using stdcall_callback_t = R(__stdcall*)(Params..., void*); 
    using cdecl_callback_t = R(__cdecl*)(Params..., void*); 
    using MyType = CallbackWrapper<R(Params...)>; 
    CallbackWrapper(cdecl_callback_t target, void* target_userdata) : _target(target), _target_userdata(target_userdata) {} 
    stdcall_callback_t callback() const { return &MyType::callback_function; } 
private: 
    static R __stdcall callback_function(Params... ps, void* userData) { 
     auto This = reinterpret_cast<MyType*>(userData); 
     return This->_target(std::forward<Params>(ps)..., This->_target_userdata); 
    } 
    cdecl_callback_t _target; 
    void* _target_userdata; 
}; 

... 
extern void external_api(void(__stdcall*)(int,int,void*), void* userdata); 
extern void __cdecl not_stdcall_func(int,int, void*); 

void * userdata_for_not_stdcall_func = nullptr; 
CallbackWrapper<void(int, int)> wrapper(&not_stdcall_func, userdata_for_not_stdcall_func); 
external_api(wrapper.callback(), &wrapper); 
// make sure wrapper is alive for as long as external_api is using the callback!