C ++ COM дизайн.Композиция против множественного наследования - PullRequest
4 голосов
/ 01 сентября 2010

Я пытаюсь встроить элемент управления браузера в свое приложение (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://img827.imageshack.us/img827/3269/lsactivedesktopumlsmall.png

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

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

EDIT:

Для дальнейшего обсуждения, вот текущая реализация 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 ]

2 голосов
/ 01 сентября 2010

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

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

0 голосов
/ 01 сентября 2010

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

0 голосов
/ 01 сентября 2010

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

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

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

...