Лучший способ вызвать функцию-член C++ обратным вызовом C?

Учитывая типичный класс:

struct Whatever
{
    void Doit();
};

Whatever w;

Каков наилучший способ вызвать функцию-член обратным вызовом на основе C void *, таким как pthread_create () или обработчиком сигнала?

pthread_t pid;

pthread_create(&pid, 0, ... &w.Doit() ... );

person keraba    schedule 26.09.2008    source источник


Ответы (9)


Большинство обратных вызовов C позволяют указать аргумент, например.

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine)(void*), void *arg);

Таким образом, вы могли бы

void myclass_doit(void* x)
{
  MyClass* c = reinterpret_cast<MyClass*>(x);
  c->doit();
}

pthread_create(..., &myclass_doit, (void*)(&obj));
person Ian G    schedule 26.09.2008
comment
просто обратите внимание, что myclass_doit должен иметь связь c (т.е. extern C). - person Greg Rogers; 26.09.2008
comment
Нет, это не так. На него явно ссылаются из файла C++. - person keraba; 26.09.2008
comment
Для этого требуется отдельная оболочка для каждого обратного вызова. Да, это работает, но создает огромный беспорядок накладных расходов на управление кодом. - person Catskul; 15.09.2009

Наиболее краткое решение — определить в заголовочном файле, общем для всего вашего кода:

template <typename T, void (T::*M)()>
void* thunk(
    void* p)
{
    T* pt = static_cast<T*>(p);

    (pt->*M)();

    return 0;
}

Вы, вероятно, захотите определить 4 версии: по одной, где преобразователь возвращает void и void*, и по одной, где функция-член возвращает void и void*. Таким образом, компилятор может выбрать лучший вариант в зависимости от обстоятельств (и на самом деле он будет жаловаться, если все не совпадает).

Тогда все, что вам нужно вводить каждый раз, когда вы сталкиваетесь с одной из этих ситуаций, это:

pthread_create(&pid, 0, &thunk‹Что угодно, &Что угодно::doit>, &w);

Это будет работать даже в том случае, если метод является закрытым, если на него ссылаются из кода класса. (Если нет, я должен задаться вопросом, почему код ссылается на закрытый метод.)

person keraba    schedule 26.09.2008

Используйте оболочку C-функции следующим образом:

struct Whatever
{
    void Doit();
};

extern "C" static int DoItcallback (void * arg)
{
  Whatever * w = (Whatever *) arg;
  w->DoIt();
  return something;
}

Работает только в том случае, если вы можете каким-то образом передать указатель на класс. Большинство механизмов обратного вызова допускают это.

Афаик, это единственный способ сделать это. Вы не можете напрямую вызвать метод из C без большого количества взлома.

person Nils Pipenbrinck    schedule 26.09.2008
comment
Для этого требуется отдельная оболочка для каждого обратного вызова. Да, это работает, но создает огромный беспорядок накладных расходов на управление кодом. - person Catskul; 15.09.2009

Является ли функция-член частной? Если нет, используйте стандартную идиому:

void* pthread_foo_caller(void* arg) {
    Foo* foo = static_cast<Foo*>(arg);
    foo->bar();
    return NULL;
}

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

class Foo {
 public:
  static pthread_foo_caller(void* arg);
  ...
};

void* Foo::pthread_foo_caller(void* arg) {
    Foo* foo = static_cast<Foo*>(arg);
    foo->private_bar();
    return NULL;
}
person hazzen    schedule 26.09.2008

Функция-член ДОЛЖНА быть статической. Нестатические имеют подразумеваемый аргумент "this". Передайте указатель на ваш экземпляр Whatever как void*, чтобы статический элемент мог получить доступ к экземпляру.

person Corey Trager    schedule 26.09.2008

Вот простой способ сделать это, не забудьте правильно управлять временем жизни вашего объекта «MemberFunction».

#include 

class MyClass
{
public:
    void DoStuff()
    {
        printf("Doing Stuff!");
    }
};

struct MemberFunction
{
    virtual ~MemberFunction(){}
    virtual void Invoke() = 0;
};

void InvokeMember(void *ptr)
{
    static_cast(ptr)->Invoke();
}

template 
struct MemberFunctionOnT : MemberFunction
{
    typedef void (T::*function_t)();
public:
    MemberFunctionOnT(T* obj, function_t fun)
    {
        m_obj = obj;
        m_fun = fun;
    }

    void Invoke()
    {
        (m_obj->*m_fun)();
    }
private:
    T *m_obj;
    function_t m_fun;
};

template 

MemberFunction* NewMemberFunction(T *obj, void (T::*fun)())
{ 
    return new MemberFunctionOnT(obj, fun); 
}

//simulate a C-style function offering callback functionality.
void i_will_call_you_later(void (*fun)(void*), void *arg)
{
    fun(arg);
}

int main()
{
    //Sample usage.
    MyClass foo;

    MemberFunction *arg = NewMemberFunction(&foo, &MyClass::DoStuff);
    i_will_call_you_later(&InvokeMember, arg);
    return 0;
}
person Torbjörn Gyllebring    schedule 26.09.2008

Одна вещь, о которой вы должны знать, это то, что если вы пишете такой код:

try {
    CallIntoCFunctionThatCallsMeBack((void *)this, fCallTheDoItFunction);
} catch (MyException &err)
{
   stderr << "badness.";
}

void fCallTheDoItFunction(void *cookie)
{
    MyClass* c = reinterpret_cast<MyClass*>(cookie);
    if (c->IsInvalid())
        throw MyException;
    c->DoIt();
}

Вы можете столкнуться с серьезными проблемами в зависимости от вашего компилятора. Оказывается, некоторые компиляторы во время оптимизации видят единственный вызов C в блоке try/catch и радостно восклицают: «Я вызываю функцию C, которая, поскольку это старый добрый C, не может генерировать ошибки! Я уберу все остатки try/catch, так как он никогда не будет достигнут.

Глупый компилятор.

Не вызывайте C, который перезванивает вам, и ожидайте, что сможете его поймать.

person plinth    schedule 26.09.2008
comment
Ну, если ваш код вызывается кодом C (или другого языка), вам нужно убедиться, что никакие исключения в любом случае не возвращаются обратно в C/другой, поэтому этот совет тавтологичен. :-П - person Chris Jester-Young; 27.09.2008

См. эту ссылку.

По сути, это невозможно напрямую, потому что: «Указатели на нестатические члены отличаются от обычных указателей функций C, поскольку им требуется передать this-указатель объекта класса. Таким образом, обычные указатели функций и [указатели на] нестатические функции-члены имеют разные и несовместимые подписи"

person Seb Rose    schedule 26.09.2008

Хотя я не использовал его из C, для выполнения обратных вызовов я настоятельно рекомендую посмотреть libsigc++. Это было именно то, что мне нужно было несколько раз при выполнении обратных вызовов C++.

person bmdhacks    schedule 26.09.2008
comment
Разве libsigc++ не определяет функциональные объекты (которые нельзя вызывать из C)? Просто проверка. В остальном да, согласен, полезно. - person keraba; 26.09.2008