28

我正在使用 C 库开发 C++ 应用程序。我必须向 C 库发送一个指向函数的指针。

这是我的课:

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

这是我的 on_btn_clicked 函数:

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

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

}

C 函数应该获得指向此类函数的指针:void f(int*)。但是上面的代码不起作用,我无法成功将我的 f 成员函数转换为所需的指针。

有人可以帮忙吗?

4

7 回答 7

25

您不能将非静态成员函数指针作为普通函数指针传递。它们不是一回事,甚至可能大小都不一样。

但是,您可以(通常)通过 C 传递一个指向静态成员函数的指针。通常在 C API 中注册回调时,您还可以传递一个“用户数据”指针,该指针将传递回您的注册函数。因此,您可以执行以下操作:

class MyClass
{

    void non_static_func(/* args */);

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

然后将您的回调注册为

c_library_function(MyClass::static_func, this);

即将实例指针传递给静态方法,并将其用作转发函数。

严格来说,对于完全可移植性,您需要使用声明的自由函数extern "C"而不是静态成员来进行转发(如果需要,声明为friend),但实际上,使用这种方法将 C++ 代码与 GObject 代码接口我从来没有遇到任何问题,这是 C 回调重的。

于 2013-11-06T09:28:20.220 回答
18

您不能将函数指针传递给非静态成员函数。您可以做的是创建一个使用实例参数进行调用的静态或全局函数。

这是一个我觉得很有用的例子,它使用一个有两个成员的帮助类:一个函数包装器和一个调用包装器的回调函数。

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;

使用它,您可以存储任何可调用的,甚至是非静态成员函数(使用std::bind)并使用该函数转换为 c 指针Callback::callback。例如:

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());
}
于 2013-11-06T10:36:53.627 回答
12

如果我没记错的话,只有类的静态方法可以通过“普通”C 指针访问函数语法。所以尽量让它静态。指向类的方法的指针需要额外的信息,例如对于纯 C 方法没有意义的“对象”(this)。

此处显示的常见问题解答对您的问题有很好的解释和可能的(丑陋的)解决方案。

于 2013-11-06T09:24:56.583 回答
7

@Snps 答案很棒。我使用创建回调的 maker 函数对其进行了扩展,因为我总是使用void不带参数的回调:

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;
}

从那时起,您可以在类中或其他任何地方创建纯 C 回调,并调用该成员:

voidCCallback callback = makeCCallback(&Foo::print, this);
plainOldCFunction(callback);
于 2014-03-18T17:33:40.923 回答
2

简短的回答是:您可以使用std::mem_fn将成员函数指针转换为普通的 C 函数指针。

这是给定问题的答案,但这个问题似乎有一个令人困惑的前提,因为提问者希望 C 代码能够在没有 MainWindow* 的情况下调用 MainWindow 的实例方法,这根本不可能。

如果您使用 mem_fn 强制转换MainWindow::on_btn_clicked为 C 函数指针,那么您仍然是一个以 aMainWindow*作为其第一个参数的函数。

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

这是给定问题的答案,但与界面不匹配。您必须编写一个 C 函数来包装对特定实例的调用(毕竟,您的 C API 代码对 MainWindow 或其任何特定实例一无所知):

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

这被认为是一种 OO 反模式,但由于 C API 对您的对象一无所知,因此这是唯一的方法。

于 2015-12-03T15:36:08.313 回答
2

@Snps 答案很完美!但正如@DXM 提到的,它只能容纳一个回调。我对其进行了一些改进,现在它可以保留许多相同类型的回调。这有点奇怪,但效果很好:

 #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

现在你可以做这样的事情:

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);
于 2016-09-16T05:09:33.593 回答
0

我有一个想法(不完全符合标准,因为extern "C"缺少):

class MainWindow;

static MainWindow* instance;

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

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

如果实例化多个实例,您将遇到问题MainWindow

于 2013-11-06T09:52:06.767 回答