C ++: статическая функция-обертка, которая направляет функцию-член? - PullRequest
3 голосов
/ 10 ноября 2009

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

Мне нужно предоставить некоторые статические функции для использования в качестве функции обратного вызова в C lib. Однако я хочу, чтобы фактическая реализация была нестатической, поэтому я могу использовать виртуальные функции и повторно использовать код в базовом классе. Такие как:

class Callbacks {
  static void MyCallBack() { impl->MyCallBackImpl(); }
  ...

class CallbackImplBase {
   virtual void MyCallBackImpl() = 0;

Однако я пытаюсь решить эту проблему (синглтон, компоновка, позволяющая Callbacks содержаться в классе разработчика и т. Д.), Я захожу в тупик (impl обычно заканчивается указанием на базовый класс, а не на производный).

Интересно, возможно ли это вообще или я застрял в создании каких-то вспомогательных функций вместо использования наследования?

Ответы [ 4 ]

8 голосов
/ 10 ноября 2009

Задача 1:

Хотя это может выглядеть и работать на вашей установке, это не гарантированно работает, так как C ++ ABI не определен. Таким образом, технически вы не можете использовать статические функции-члены C ++ в качестве указателей на функции, которые будут использоваться кодом C.

Задача 2:

Все вызовы C (которые я знаю) позволяют вам передавать пользовательские данные обратно как пустоту *. Вы можете использовать это как указатель на ваш объект, который имеет виртуальный метод. НО Необходимо убедиться, что вы используете dynamic_cast <> () для базового класса (класса с виртуальным методом, использованным в обратном вызове), прежде чем он будет преобразован в пустоту *, иначе указатель на другом конце может не может быть правильно истолковано (особенно если задействовано множественное наследование).

Задача 3:

Исключения: C не предназначен для работы с исключениями (особенно старыми библиотеками C с обратными вызовами). Поэтому не ожидайте, что исключения, которые избегают вашего обратного вызова, предоставят звонящему что-то значимое (они с большей вероятностью приведут к завершению приложения).

Решение:

Что вам нужно сделать, так это использовать функцию extern "C" в качестве обратного вызова, который вызывает виртуальный метод для объекта известного типа и отбрасывает все исключения.

Пример для подпрограмм C pthread

#include <iostream>

extern "C" void* start_thread(void* data);

class Work
{
    public:
    virtual ~Work() {}
    virtual void doWork() = 0;
};

/*
 * To be used as a callback for C code this MUST be declared as
 * with extern "C" linkage to make sure the calling code can
 * correctly call it
 */
void* start_thread(void* data)
{
    /*
     * Use reinterpret_cast<>() because the only thing you know
     * that you can do is cast back to a Work* pointer.
     *
     */
    Work*  work = reinterpret_cast<Work*>(data);
    try
    {
        work->doWork();
    }
    catch(...)
    {
        // Never let an exception escape a callback.
        // As you are being called back from C code this would probably result
        // in program termination as the C ABI does not know how to cope with
        // exceptions and thus would not be able to unwind the call stack.
        //
        // An exception is if the C code had been built with a C++ compiler
        // But if like pthread this is an existing C lib you are unlikely to get
        // the results you expect.
    }
    return NULL;
}

class PrintWork: public Work
{
    public:
    virtual void doWork()
    {
        std::cout << "Hi \n";
    }
};

int main()
{
    pthread_t   thread;
    PrintWork   printer;
    /*
     * Use dynamic_cast<>() here because you must make sure that
     * the underlying routine receives a Work* pointer
     * 
     * As it is working with a void* there is no way for the compiler
     * to do this intrinsically so you must do it manually at this end
     */
    int check = pthread_create(&thread,NULL,start_thread,dynamic_cast<Work*>(&printer));
    if (check == 0)
    {
        void*   result;
        pthread_join(thread,&result);
    }
}
2 голосов
/ 10 ноября 2009

Это возможно. Возможно, есть проблема в том, как вы инициализируете конкретную реализацию?

На самом деле, я помню одну библиотеку, которая делает что-то очень похожее на это. Возможно, вам будет полезно взглянуть на libxml ++ исходный код. Он построен поверх libxml , который является библиотекой Си.

libxml ++ использует структуру статических функций для обработки обратных вызовов. Для настройки дизайн позволяет пользователю предоставлять (через виртуальные функции) свои собственные реализации, в которые затем перенаправляются обратные вызовы. Я думаю, это в значительной степени ваша ситуация.

1 голос
/ 10 ноября 2009

Что-то вроде ниже. Синглтон находится в классе Callback, член Instance вернет статически размещенную ссылку на класс CallbackImpl. Это одиночный код, потому что ссылка будет инициализирована только один раз при первом вызове функции. Кроме того, это должна быть ссылка или указатель, иначе виртуальная функция не будет работать.

class CallbackImplBase
{
public:
   virtual void MyCallBackImpl() = 0;
};

class CallbackImpl : public CallbackImplBase
{
public:
    void MyCallBackImpl()
    {
        std::cout << "MyCallBackImpl" << std::endl;
    }
};

class Callback
{
public:
    static CallbackImplBase & Instance()
    {
        static CallbackImpl instance;
        return instance;
    }

    static void MyCallBack()
    {
        Instance().MyCallBackImpl();
    }
};

extern "C" void MyCallBack()
{
    Callback::MyCallBack();
}
0 голосов
/ 10 ноября 2009

Определен ли какой-либо из параметров, переданных в функцию обратного вызова, пользователем? Есть ли способ, которым вы можете присоединить определенное пользователем значение к данным, переданным в эти обратные вызовы? Я помню, когда я реализовал библиотеку-оболочку для окон Win32, я использовал SetWindowLong () , чтобы прикрепить указатель this к дескриптору окна, который впоследствии можно было получить в функции обратного вызова. По сути, вам нужно упаковать указатель this куда-нибудь, чтобы вы могли получить его при срабатывании обратного вызова.

struct CALLBACKDATA
{
  int field0;
  int field1;
  int field2;
};

struct MYCALLBACKDATA : public CALLBACKDATA
{
  Callback* ptr;
};


registerCallback( Callback::StaticCallbackFunc, &myCallbackData, ... );

void Callback::StaticCallbackFunc( CALLBACKDATA* pData )
{
  MYCALLBACKDATA* pMyData = (MYCALLBACKDATA*)pData;
  Callback* pCallback = pMyData->ptr;

  pCallback->virtualFunctionCall();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...