web-dev-qa-db-ja.com

C ++関数ポインターをC関数ポインターに変換

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メンバー関数を目的のポインターに変換できません。

誰か助けてもらえますか?

23
RRR

私がそれを正しく思い出せば、関数構文への「通常の」Cポインターを介してアクセスできるのはクラスの静的メソッドだけです。静的にしてみてください。クラスのメソッドへのポインターには、純粋なCメソッドには意味のない「オブジェクト」(this)などの追加情報が必要です。

FAQ表示 ここ は、問題の適切な説明と可能な(醜い)解決策を示しています。

11
Ferenc Deak

非静的メンバー関数ポインターを通常の関数ポインターとして渡すことはできません。それらは同じものではなく、おそらく同じサイズでもありません。

ただし、(通常は)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コードをインターフェースする問題。

22
Tristan Brindle

関数ポインターを非静的メンバー関数に渡すことはできません。実行できることは、インスタンスパラメータを使用して呼び出しを行う静的関数またはグローバル関数を作成することです。

これは、関数ラッパーとラッパーを呼び出すコールバック関数の2つのメンバーを持つヘルパークラスを使用する便利な例です。

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を使用して)呼び出し可能で非静的なメンバー関数も格納でき、Callback::callback関数を使用してcポインターに変換できます。例えば:

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

@Snpsの答えは素晴らしいです。私は常にパラメーターなしで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);
5

@Snpsの答えは完璧です!しかし、@ DXMが言及したように、それは1つのコールバックしか保持できません。少し改善しましたが、同じタイプの多くのコールバックを保持できるようになりました。それは少し奇妙ですが、完璧に動作します:

 #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);
2
Evgeniy Alexeev

私はアイデアを持っています(完全に標準に準拠しているわけではなく、extern "C" 不足している):

class MainWindow;

static MainWindow* instance;

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

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

MainWindowの複数のインスタンスがインスタンス化されると、問題が発生します。

1
user1095108

短い答えは: std :: mem_fn を使用して、メンバー関数ポインターを通常のC関数ポインターに変換できます。

それは与えられた質問への答えですが、質問者はCコードがMainWindow *なしでMainWindowのインスタンスメソッドを呼び出すことができることを期待しているため、この質問は混乱した前提を持っているようです。

Mem_fnを使用してMainWindow::on_btn_clickedをC関数ポインターにキャストする場合でも、最初の引数としてMainWindow*を受け取る関数です。

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はオブジェクトについて何も知らないため、それが唯一の方法です。

1
stands2reason