Использование функции-члена класса C ++ (не может быть статической) в качестве функции обратного вызова C - PullRequest
6 голосов
/ 03 февраля 2011

У меня есть функция библиотеки C, которая ожидает указатель на функцию для обратного вызова, и я хочу передать функцию-член C ++.Функция C ++ изменяет переменную-член, поэтому я не могу использовать статическую свободную функцию (как предложено в нескольких похожих постах).Моя попытка (показанная ниже) терпит неудачу с ошибкой компилятора.

Этот пост наиболее близок к тому, что мне нужно:

Использование функции-члена класса C ++ в качестве функции обратного вызова C

Как я могу сделать это без статических функций?Спасибо!


test.h

#ifndef TEST_H_
#define TEST_H_

#ifdef __cplusplus
extern "C" {
#endif

typedef void (*handler_t)(int foo, void *bar);

void set_handler(handler_t h);

#ifdef __cplusplus
}
#endif

#endif

test.c

#include "test.h"
#include <stdlib.h>

static handler_t handler_ = NULL;
void set_handler(handler_t h) {
        handler_ = h;
}

void handle_event(int foo, void *bar) {
        if (handler_ != NULL) handler_(foo, bar);
}

test.cpp

#include "test.h"
#include <iostream>
using namespace std;

class Foo {
public:
        Foo() : ctr_(0) {};

        // handler needs to access non-static variable, so it can't be static
        void handler(int foo, void *bar) { ++ctr_;  }

private:
        int ctr_;
};

int main(int argc, char **argv) {
        // error: can't convert to "void (*)(int, void*)"
        set_handler(&Foo::handler);

        cout << "done" << endl;
        return 0;
}

GCC barf

$ gcc test.cpp test.c 
test.cpp: In function ‘int main(int, char**)’: 
test.cpp:18: error: cannot convert ‘void (Foo::*)(int, void*)’ to ‘void (*)(int, void*)’ for argument ‘1’ to ‘void set_handler(void (*)(int, void*))’

Ответы [ 6 ]

8 голосов
/ 03 февраля 2011

Это невозможно, по крайней мере, с этой handler_t подписью.

В то время как вы можете создать бесплатную функцию в вашем .cpp для переноса вызова члена, вам нужен указатель на экземпляр Foo:

void my_wrap(int foo, void* bar) {
    Foo* some_foo_instance = ...;
    some_foo_instance->handler(foo, bar);
}

int main(int argc, char **argv) {
    set_handler(&my_wrap);
}

Вам нужен некоторый void *, чтобы передать экземпляр Foo в качестве атрибута обработчика:

// Header
typedef void (*handler_t)(int foo, void *bar, void* arg1);
void set_handler(handler_t h, void* arg1);

// Impl.
void set_handler(handler_t h, void* arg1) {
        handler_ = h;
        handler_arg1_ = arg1;
}

// cpp
void my_wrap(int foo, void* bar, void* arg1) {
    Foo* some_foo_instance = static_cast<Foo*>(arg1);
    some_foo_instance->handler(foo, bar);
}

// main
int main(int argc, char **argv) {
    Foo some_concrete_instance;
    set_handler(&my_wrap, static_cast<void*>(&some_concrete_instance));
}
3 голосов
/ 03 февраля 2011

Большой вопрос в том, сколько раз вам нужно вызывать set_handler несколько раз, чтобы вызывать методы для различных объектов. Если этот ответ один, вы можете сделать что-то вроде этого:

#include <boost/function.hpp>

class HandlerContext
{
    static boost::function<void (int, void*)> s_func

    static void forward(int foo, void* bar)
    {
         s_func(foo, bar);
    }

public:
    static void set(boost::function<int, void*> const& f)
    {
        s_func = f;
        set_handler(&HandlerContext::forward);
    }
};

Если ответ «более одного раза», вы можете иметь несколько функций пересылки, которые получают свои функциональные объекты из массива. В этом случае вам потребуется предварительно назначить слоты, потому что используемая функция будет указывать, какой обратный вызов сделать.

1 голос
/ 03 февраля 2011

Вот уродливый хак, который я изобрел некоторое время назад , чтобы решить эту проблему:

#include <boost/function.hpp>
#include <boost/bind.hpp>

using ::boost::function;
using ::boost::bind;

typedef int (*callback_t)(const char *, int);

typedef function<int(const char *, int)> MyFTWFunction;

template <MyFTWFunction *callback>
class callback_binder {
 public:
   static int callbackThunk(const char *s, int i) {
      return (*callback)(s, i);
   }
};

extern void register_callback(callback_t f);

int random_func(const char *s, int i)
{
   if (s && *s) {
      return i;
   } else {
      return -1;
   }
}

MyFTWFunction myfunc;

class FooClass {
 public:
   virtual int callme(const char *s, int x) { return 0; };
};

int main(int argc, const char *argv[])
{
   FooClass foo;
   myfunc = bind(&FooClass::callme, &foo, _1, _2);
   register_callback(&callback_binder<&myfunc>::callbackThunk);
   return 0;
}

Возможно, это можно исправить, чтобы использовать вещи из TR1 и удалить зависимость от Boost.

И, конечно же, myfunc - это глобальная переменная. Это должна быть глобальная переменная. У вас должна быть одна глобальная переменная для каждого возможного объекта, к которому вы хотите обратиться. OTOH, вы можете иметь столько глобалов, сколько захотите.

Основная проблема здесь заключается в том, что абсолютно невозможно делать то, что вы хотите в данных ограничениях. Указатель на объект, в который вы хотите перезвонить, должен прийти откуда-то. В некоторых языках (например, в Python) вы можете создать функцию «на лету», которая имеет собственную копию указателя объекта. Это не может быть сделано в C ++. Все функции должны существовать полностью во время компиляции. Вы не можете создавать новые экземпляры функций во время выполнения.

С C ++ 0x вы можете сортировать функции во время выполнения с помощью лямбда-функций. Но эти функции имеют неопределенный тип, и нет абсолютно никакого способа, которым вы могли бы когда-либо передать их функции C и заставить это работать. Лямбда-выражения предназначены для предоставления в качестве параметров шаблона, и их довольно сложно использовать для чего-либо еще, поскольку их адрес не может быть получен, и даже если бы вы могли, вы не могли бы знать, на какой тип указывает указатель.

Я настоятельно рекомендую не использовать его. Маленькие void * большинство интерфейсов обратного вызова позволяют вам указать, что передача вам вместе с данными предназначена для хранения какого-либо указателя объекта. Если возможно, вы должны сделать это вместо этого.

1 голос
/ 03 февраля 2011

Это предложение:

У меня есть функция библиотеки C

Это означает, что вы можете НЕ передать ей любой объект C ++.
Если используемая вами библиотека является библиотекой C, она не знает о C ++, поэтому она не может использовать все, что является C ++, она может использовать только материал C.

You MUST заставляет ее вызыватьСвободная функция в вашем коде.
Теперь ваша свободная функция может затем вызывать метод для объекта (поэтому обратные вызовы C имеют параметр void * (так что вы можете передавать контекст обратному вызову)).

1 голос
/ 03 февраля 2011

Предположим, вы создали функцию отображения:

Foo *inst = // some instance of Foo you're keeping around...

void wrapper(int foo, void *bar){
    inst->handler(foo, bar);
}

Затем используйте wrapper в качестве обратного вызова. Семантика экземпляра в обратном вызове довольно странная, поэтому я не уверен, как вы будете уверены, что связываетесь с правильным экземпляром - если это одиночный код, возможно, это не имеет значения.

0 голосов
/ 03 февраля 2011

Если у вас есть контроль над определением обработчика, я рекомендую использовать Усилить функциональные объекты вместо указателей на функции.

Если вы ДОЛЖНЫ использовать указатели на функции, определите handler_t с дополнительным void *, значение которого передается вместе с обработчиком, следите за ошибками Мартина Йорка, связанных в комментарии . Тогда у вас есть что-то вроде этого:

typedef void (*handler_t)(int foo, void *bar, void *data);

static handler_t handler_ = NULL;
static void* handler_data_ = NULL;
void set_handler(handler_t h, void *d = NULL) {
    handler_ = h;
    handler_data = d;
}

void handle_event(int foo, void *bar) {
    if (handler_ != NULL) handler_(foo, bar, handler_data_);
}

void foo_handler(int foo, void *bar, void *data) {
    Foo *fooObj = static_cast<Foo*>(data);
    fooObj->handler(foo, bar);
}

// in main
    set_handler(foo_handler, &some_foo_object);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...