web-dev-qa-db-fra.com

Convertir le pointeur de fonction C ++ en pointeur de fonction c

Je développe une application C++ à l'aide d'une bibliothèque C. Je dois envoyer un pointeur pour fonctionner à la bibliothèque C.

Voici ma classe:

 class MainWindow : public QMainWindow {  
     Q_OBJECT  
     public:  
     explicit MainWindow(QWidget *parent = 0);  
     private:  
     Ui::MainWindow *ui;
     void f(int*);

 private slots:
     void on_btn_clicked(); 
};

Voici ma fonction on_btn_clicked:

void MainWindow::on_btn_clicked()
{
    void (MainWindow::* ptfptr) (int*) = &MainWindow::f;

    c_library_function(static_cast<void()(int*)>(ptfptr), NULL);

}

La fonction C devrait obtenir un pointeur vers une telle fonction: void f (int *). Mais le code ci-dessus ne fonctionne pas, je ne parviens pas à convertir ma fonction membre f au pointeur souhaité.

Quelqu'un peut-il m'aider?

23
RRR

Si je me souviens bien, seules les méthodes statiques d'une classe sont accessibles via le pointeur C "normal" vers la syntaxe de la fonction. Essayez donc de le rendre statique. Le pointeur vers une méthode d'une classe a besoin d'informations supplémentaires, telles que "l'objet" (this) qui n'a aucune signification pour une méthode C pure.

Le FAQ montré ici a une bonne explication et une solution (laide) possible pour votre problème.

11
Ferenc Deak

Vous ne pouvez pas passer un pointeur de fonction membre non statique comme un pointeur de fonction ordinaire. Ce n'est pas la même chose, et probablement même pas la même taille.

Vous pouvez cependant (généralement) passer un pointeur vers une fonction membre statique via C. Habituellement, lors de l'enregistrement d'un rappel dans une API C, vous pouvez également passer un pointeur "données utilisateur" qui est renvoyé à votre fonction enregistrée. Vous pouvez donc faire quelque chose comme:

class MyClass
{

    void non_static_func(/* args */);

public:
    static void static_func(MyClass *ptr, /* other args */) {
        ptr->non_static_func(/* other args */);
    }
};

Enregistrez ensuite votre rappel comme

c_library_function(MyClass::static_func, this);

c'est-à-dire passer le pointeur d'instance à la méthode statique et l'utiliser comme fonction de transfert.

À strictement parler, pour une portabilité totale, vous devez utiliser une fonction gratuite déclarée extern "C" plutôt qu'un membre statique pour faire votre transfert (déclaré comme friend si nécessaire), mais pratiquement je n'ai jamais eu de problème en utilisant cette méthode pour interfacer le code C++ avec le code GObject, qui est le rappel C -lourd.

22
Tristan Brindle

Vous ne pouvez pas passer un pointeur de fonction à une fonction membre non statique. Ce que vous pouvez faire est de créer une fonction statique ou globale qui effectue l'appel avec un paramètre d'instance.

Voici un exemple que je trouve utile qui utilise une classe d'assistance avec deux membres: un wrapper de fonction et une fonction de rappel qui appelle le wrapper.

template <typename T>
struct Callback;

template <typename Ret, typename... Params>
struct Callback<Ret(Params...)> {
    template <typename... Args>
    static Ret callback(Args... args) { return func(args...); }
    static std::function<Ret(Params...)> func;
};

// Initialize the static member.
template <typename Ret, typename... Params>
std::function<Ret(Params...)> Callback<Ret(Params...)>::func;

En utilisant ceci, vous pouvez stocker toutes les fonctions membres appelables, même non statiques (en utilisant std::bind) et convertissez-le en pointeur c à l'aide du Callback::callback fonction. Par exemple:

struct Foo {
    void print(int* x) { // Some member function.
        std::cout << *x << std::endl;
    }
};

int main() {
    Foo foo; // Create instance of Foo.

    // Store member function and the instance using std::bind.
    Callback<void(int*)>::func = std::bind(&Foo::print, foo, std::placeholders::_1);

    // Convert callback-function to c-pointer.
    void (*c_func)(int*) = static_cast<decltype(c_func)>(Callback<void(int*)>::callback);

    // Use in any way you wish.
    std::unique_ptr<int> iptr{new int(5)};
    c_func(iptr.get());
}
15
Snps

La réponse @Snps est excellente. Je l'ai étendu avec une fonction maker qui crée un rappel, car j'utilise toujours des rappels void sans paramètres:

typedef void (*voidCCallback)();
template<typename T>
voidCCallback makeCCallback(void (T::*method)(),T* r){
  Callback<void()>::func = std::bind(method, r);
  void (*c_function_pointer)() = static_cast<decltype(c_function_pointer)>(Callback<void()>::callback);
  return c_function_pointer;
}

À partir de là, vous pouvez créer votre rappel C simple depuis la classe ou n'importe où et faire appeler le membre:

voidCCallback callback = makeCCallback(&Foo::print, this);
plainOldCFunction(callback);
5
Thomas Fankhauser

La réponse @Snps est parfaite! Mais comme @DXM l'a mentionné, il ne peut contenir qu'un seul rappel. Je l'ai un peu amélioré, maintenant il peut conserver de nombreux rappels du même type. C'est un peu étrange, mais fonctionne parfaitement:

 #include <type_traits>

template<typename T>
struct ActualType {
    typedef T type;
};
template<typename T>
struct ActualType<T*> {
    typedef typename ActualType<T>::type type;
};

template<typename T, unsigned int n,typename CallerType>
struct Callback;

template<typename Ret, typename ... Params, unsigned int n,typename CallerType>
struct Callback<Ret(Params...), n,CallerType> {
    typedef Ret (*ret_cb)(Params...);
    template<typename ... Args>
    static Ret callback(Args ... args) {
        func(args...);
    }

    static ret_cb getCallback(std::function<Ret(Params...)> fn) {
        func = fn;
        return static_cast<ret_cb>(Callback<Ret(Params...), n,CallerType>::callback);
    }

    static std::function<Ret(Params...)> func;

};

template<typename Ret, typename ... Params, unsigned int n,typename CallerType>
std::function<Ret(Params...)> Callback<Ret(Params...), n,CallerType>::func;

#define GETCB(ptrtype,callertype) Callback<ActualType<ptrtype>::type,__COUNTER__,callertype>::getCallback

Maintenant, vous pouvez simplement faire quelque chose comme ceci:

typedef void (cb_type)(uint8_t, uint8_t);
class testfunc {
public:
    void test(int x) {
        std::cout << "in testfunc.test " <<x<< std::endl;
    }

    void test1(int x) {
        std::cout << "in testfunc.test1 " <<x<< std::endl;
    }

};

cb_type* f = GETCB(cb_type, testfunc)(std::bind(&testfunc::test, tf, std::placeholders::_2));

cb_type* f1 = GETCB(cb_type, testfunc)(
                std::bind(&testfunc::test1, tf, std::placeholders::_2));


f(5, 4);
f1(5, 7);
2
Evgeniy Alexeev

J'ai une idée (pas entièrement conforme aux normes, comme extern "C" est manquant):

class MainWindow;

static MainWindow* instance;

class MainWindow
{
public:
  MainWindow()
  {
    instance = this;

    registerCallback([](int* arg){instance->...});
  }
};

Vous aurez des problèmes si plusieurs instances de MainWindow sont instanciées.

1
user1095108

La réponse courte est: vous pouvez convertir un pointeur de fonction membre en un pointeur de fonction C ordinaire en utilisant std :: mem_fn .

C'est la réponse à la question telle qu'elle est donnée, mais cette question semble avoir une prémisse confuse, car le demandeur s'attend à ce que le code C puisse appeler une méthode d'instance de MainWindow sans avoir de MainWindow *, ce qui est tout simplement impossible.

Si vous utilisez mem_fn pour caster MainWindow::on_btn_clicked à un pointeur de fonction C, alors vous avez toujours une fonction qui prend un MainWindow* comme premier argument.

void (*window_callback)(MainWindow*,int*) = std::mem_fn(&MainWindow::on_btn_clicked);

C'est la réponse à la question telle qu'elle est donnée, mais elle ne correspond pas à l'interface. Vous devrez écrire une fonction C pour encapsuler l'appel à une instance spécifique (après tout, votre code API C ne sait rien sur MainWindow ou sur une instance spécifique de celui-ci):

void window_button_click_wrapper(int* arg)
{
    MainWindow::inst()->on_btn_clicked(arg);
}

Ceci est considéré comme un anti-pattern OO, mais comme l'API C ne sait rien de votre objet, c'est le seul moyen.

1
stands2reason