Страницы

21 мая 2012 г.

Написание callback-функций в C++ и COM

       Это быстрое описание того, как адаптировать Ваши указатели функции C-стиля, если Вы кодируете на C++. Эта статья написана для того, чтобы предоставить быстрое описание людям, пытающимся выяснять, как работают функции обратного вызова на всевозможных языках. Щелкните здесь для C#/.NET версии. Или щелкните здесь, если Вы хотите сделать делегат C#-стиля функций обратного вызова в C++.

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


Есть по крайней мере два варианта:
  • Добавить статический метод объекта и передать указатель на него (это будет идентично C-функции), а так же  указатель "this" (чтобы указать на объект который необходимо вызвать). После этого можно вызвать эту функцию из другой функции в созданном объекте.
  • Сделать интерфейс (чистый виртуальный класс).
Второй пункт мы опишем здесь.

Функции обратного вызова унаследованные от интерфейса обратного вызова.

Например, требуется передать указатель на функцию следующего вида:

void FooCallback(int a, int b, int c);

Сначала, необходимо сделать объект который содержит вышеописанную функцию:

class IFooCallback
{
public:
    virtual void FooCallback(int a, int b, int c);
};

Теперь создадим класс, содержащий функцию обратного вызова, унаследованный от нового объекта.
class NeedsToBeCalled : public IFooCallback
{
public:
    void FooCallback(int a, int b, int c);
    {
        do_something();
    }
    void MyFunction()
    {
        // Now pass 'this' as the callback object
        SetCallback(this);
    }
};

Реализация внутренних функций обратного вызова.

Это "официальный"  способ сделать это в Java. Вместо того, чтобы отдельно реализовывать интерфейс обратного вызова и класс, реализована часть объекта в классе (в приватной части) который реализует интерфейс.

class NeedsToBeCalled
{
private:
    class InternalCallback : public IFooCallback
    {
        void FooCallback(int a, int b, int c);
        {
            do_something();
        }
    } myCallback;
public:
    void MyFunction()
    {
        // Now pass &myCallback as the callback object
        SetCallback(&myCallback);
    }
};

Это хороший способ, т.к. он позволяет обращаться к множеству методов того же объекта; можно сделать следующее:

class GetsCalledSeveralTimes
{
private:
    class FirstInternalCallback : public IFooCallback
    {
        void FooCallback(int a, int b, int c);
        {
            do_something();
        }
    } firstCallback;
    class SecondInternalCallback : public IFooCallback
    {
        void FooCallback(int a, int b, int c);
        {
            do_something_else();
        }
    } secondCallback;
public:
    void MyFunction()
    {
        // Now we have a choice of objects to set as the callback:
        SetCallback(&firstCallback);
        // Or:
        SetCallback(&secondCallback);
    }
};

Вышеупомянутый пример показывает. что можно можно вызвать одну и туже callback-функцию по-разному в одном и том же объекте. Это более громоздко в плане написания, но не требует никакой дополнительной предварительной обработки.

Функции обратного вызова в COM.

Если требуется сделать это в COM, необходимо заставить класс быть похожим на COM-класс, т.е. на IUnknown, а так же соответствующий интерфейс обратного вызова. Это означает, что требуется три дополнительных метода: QueryInterface, AddRef и Release.

Для примера приведем реализацию интерфейса  ISampleGrabberCB от DirectShow / WDM video capture API.

private:
    //! COM reference count
    ULONG _ref_count;
public:
    //! Return a ptr to a different COM interface to this object
    HRESULT APIENTRY QueryInterface( REFIID iid, void** ppvObject )
    {
        // Match the interface and return the proper pointer
        if (iid == IID_IUnknown) {
            *ppvObject = dynamic_cast(this);
        } else if (iid == IID_ISampleGrabberCB) {
            *ppvObject = dynamic_cast(this);
        } else {
            // It didn't match an interface
            *ppvObject = NULL;
            return E_NOINTERFACE;
        }
        // Increment refcount on the pointer we're about to return
        this->AddRef();
        // Return success
        return S_OK;
    }
    //! Increment ref count
    ULONG APIENTRY AddRef()
    {
        return (++_ref_count );
    }
    //! Decrement ref count
    ULONG APIENTRY Release()
    {
        return (--_ref_count );
    }

Есть вероятно более простой способ реализации. Можно было бы переделать QueryInterface(), чтобы он делал нечто похожее, но объект выше довольно прост и не делает ничего необычного с COM (в частности, все его интерфейсы указывают на один и тот же объект):
HRESULT APIENTRY QueryInterface(REFIID iid, void** ppvObject)
{
    *ppvObject = this;
    this->AddRef();
    return S_OK;
}

Данная статья является переводом.
Статья-исходник находиться тут

Комментариев нет:

Отправить комментарий