C++ COM дизайн. Композиция против множественного наследования

Я пытаюсь встроить элемент управления браузера в мое приложение (IWebBrowser2). Мне нужно реализовать IDispatch, IDocHostShowUI, IDocHostUIHandler и т. Д., Чтобы сделать эту работу. Я делаю это в чистом C++/Win32 API. Я не использую ATL, MFC или любой другой фреймворк.

У меня есть основной класс, называемый TWebf, который создает окно Win32 для вставки элемента управления браузера и выполняет все вызовы OLE, необходимые для его работы. Он также используется для управления браузером с помощью таких методов, как Refresh(), Back(), Forward() и т. Д.

Прямо сейчас это реализуется с композицией. В TWebf есть классы, реализующие все различные интерфейсы (IDispatch, IDocHostShowUI...) в качестве членов (выделенных в стек). Первое, что делает TWebf в своем конструкторе, это дает всем этим членам указатель обратно на себя (dispatch.webf = this; так далее). QueryInterface, AddRef и Release реализованы как вызовы этих методов в TWebf для всех реализаций интерфейса (путем вызова return webf->QueryInterface(riid, ppv); например)

Мне не нравится эта циклическая зависимость между TWebf и классами, реализующими интерфейсы. TWebf имеет члена TDispatch, который имеет члена TWebf, который имеет...

Таким образом, я думал о решении этого с множественным наследованием вместо этого. Это также упростит QueryInterface, чтобы всегда иметь возможность просто вернуть this,

UMLish набросок того, что я хочу, будет выглядеть примерно так: (нажмите для увеличения)

http://img267.imageshack.us/img267/9918/lsactivedesktopuml.png

Как видно из uml, я хочу предоставить минимальные реализации всех интерфейсов, поэтому мне нужно только переопределить эти методы в интерфейсах, которые я на самом деле хочу сделать что-то существенное в TWebf.

Возможна ли моя "реализация множественного наследования"? Это хорошая идея? Это лучшее решение?

РЕДАКТИРОВАТЬ:

Для дальнейшего обсуждения, вот текущая реализация QueryInterface в TWebf

HRESULT STDMETHODCALLTYPE TWebf::QueryInterface(REFIID riid, void **ppv)
{
    *ppv = NULL;

    if (riid == IID_IUnknown) {
        *ppv = this;
    } else if (riid == IID_IOleClientSite) {
        *ppv = &clientsite;
    } else if (riid == IID_IOleWindow || riid == IID_IOleInPlaceSite) {
        *ppv = &site;
    } else if (riid == IID_IOleInPlaceUIWindow || riid == IID_IOleInPlaceFrame) {
        *ppv = &frame;
    } else if (riid == IID_IDispatch) {
        *ppv = &dispatch;
    } else if (riid == IID_IDocHostUIHandler) {
        *ppv = &uihandler;
    }

    if (*ppv != NULL) {
        AddRef();
        return S_OK;
    }

    return E_NOINTERFACE;
}

РЕДАКТИРОВАТЬ 2:

Я попытался реализовать это только для пары интерфейсов. Наличие наследования TWebf от IUnknown и TOleClientSite, кажется, работает нормально, но когда я добавил TDispatch в список наследования, он перестал работать.

Отдельно от warning C4584: 'TWebf' : base-class 'IUnknown' is already a base-class of 'TDispatch' предупреждение, что я также получаю ошибки во время выполнения. Ошибка во время выполнения: "Место чтения нарушения доступа 0x00000000"

Ошибка времени выполнения возникает в строке, касающейся IOleClientSite, а не IDispatch по какой-то причине. Я не знаю, почему это происходит, или это действительно связано с множественным наследованием или нет. Есть какие-нибудь подсказки?

РЕДАКТИРОВАТЬ 3:

Плохая реализация QueryInterface, кажется, была причиной исключения во время выполнения. Как правильно заметил Mark Ransom, этот указатель должен быть приведен до того, как он назначен на *ppv, и при запросе IUnknown требуется особая осторожность. Прочитайте Почему именно мне нужен явный прирост при реализации QueryInterface в объекте с множественным наследованием для превосходного объяснения этого.

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

3 ответа

Решение

Многократное наследование является очень распространенным способом создания COM-интерфейсов, так что да, это возможно.

Однако QueryInterface все еще должен приводить указатель для каждого интерфейса. Одним интересным свойством множественного наследования является то, что указатель может корректироваться для каждого типа класса - указатель на IDispatch не будет иметь того же значения, что и указатель на IDocHostUIHandler, даже если они оба указывают на один и тот же объект. Также убедитесь, что QueryInterface для IUnknown всегда возвращает один и тот же указатель; поскольку все интерфейсы являются производными от IUnknown, вы получите неоднозначное приведение, если попытаетесь просто привести его непосредственно к нему, но это также означает, что вы можете использовать любой интерфейс в качестве IUnknown, просто выберите первый в родительском списке.

Было бы значительно легче оставаться с композицией. MI имеет много подводных камней, таких как виртуальное наследование, и сильно страдает от ремонтопригодности. Если вам нужно передать это составным классам как элемент данных, вы сделали это неправильно. Что вы должны сделать, это передать это в вызовы методов, если им нужен доступ к другим предоставленным методам. Поскольку вы управляете всеми вызовами методов для составного объекта, не должно быть проблем с вставкой дополнительного указателя. Это делает жизнь НАМНОГО, намного проще для технического обслуживания и других операций.

Множественное наследование имеет пару ограничений

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

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

Другие вопросы по тегам