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

je développe une application C++ en utilisant une bibliothèque C. Je dois envoyer un pointeur pour fonctionner à la bibliothèque C.

C'est 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(); 
};

C'est 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 doit recevoir un pointeur vers une telle fonction : void f(int*). Mais le code ci-dessus ne fonctionne pas, Je ne peux pas réussir à convertir ma fonction de membre f au pointeur désiré.

Can quelqu'un s'il vous plaît aider?

16
demandé sur logoff 2013-11-06 13:19:00

7 réponses

si je m'en souviens correctement, seules les méthodes statiques d'une classe peuvent être accessibles via le pointeur C" normal " pour la syntaxe de fonction. Alors essayez de le rendre statique. Le pointeur vers une méthode d'une classe nécessite des informations supplémentaires, comme "l'objet" (ceci) qui n'a pas de sens pour une méthode C pure.

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

8
répondu fritzone 2015-06-03 15:00:09

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

vous pouvez cependant (habituellement) passer un pointeur à une fonction membre statique Via C. Habituellement lors de l'enregistrement d'un rappel dans une API C, vous pouvez aussi passer un pointeur" user data " qui est passé à votre fonction enregistrée. Donc vous pouvez 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 */);
    }
};

puis enregistrez votre callback comme

c_library_function(MyClass::static_func, this);

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

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

21
répondu Tristan Brindle 2013-11-06 09:35:06

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

voici un exemple que je trouve utile qui utilise une classe helper avec deux membres: une fonction wrapper et une fonction callback 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 n'importe quelles fonctions de membre appelables, même non-statiques (en utilisant std::bind ) et convertissez en pointeur c en utilisant la fonction Callback::callback . 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());
}
11
répondu Snps 2015-04-22 22:24:25

@Snp réponse est grande. Je l'ai étendu avec une fonction maker qui crée un callback, comme j'utilise toujours void callbacks 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 appel C simple à partir de la classe ou n'importe où ailleurs et faire appeler le membre:

voidCCallback callback = makeCCallback(&Foo::print, this);
plainOldCFunction(callback);
4
répondu Thomas Fankhauser 2014-03-18 17:33:40

j'ai une idée (pas entièrement conforme à la norme, car 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
répondu user1095108 2013-11-06 15:08:12

la réponse courte est: vous pouvez convertir un pointeur de fonction membre à 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 l'asker attend du code C qu'il 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 lancer MainWindow::on_btn_clicked vers un C pointeur de fonction, alors vous êtes 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, mais il ne correspond pas à l'interface. Vous devriez écrire une fonction C pour envelopper l'appel à une instance spécifique (après tout, votre code API C ne sait rien sur MainWindow ou n'importe quelle 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-modèle OO, mais puisque L'API C sait rien sur votre objet, c'est le seul moyen.

1
répondu stands2reason 2015-12-03 21:27:36

@Snps réponse 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 garder beaucoup de callbacks 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 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);
1
répondu Evgeniy Alexeev 2016-10-28 04:57:56