Хорошие методы для поддержания классов COM, которые реализуют несколько интерфейсов управляемыми - PullRequest
2 голосов
/ 30 марта 2011

COM-объекты, которые реализуют множество интерфейсов, могут в конечном итоге страдать от анти-паттерна объекта God или в конечном итоге оказаться утомительными кодом пересылки:

class MyCOMClass
    , public CUnknown
    , public IFoo
    , public IBar
    , public IPersistStream
    , public IYetAnotherInterface,
    , public IAndAnotherInterfaceToo
// etc etc etc

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

Существуют ли какие-либо более легкие методы для разделения обработкидругие интерфейсы с другими внутренними объектами без необходимости использования склонной к ошибкам агрегации COM или нарушения требований к симметрии COM для QueryInterface?

Моя первоначальная попытка найти решение, похоже, работает, но выглядит как хак:

Вместо реализации IFoo в MyCOMClass, реализуйте IFoo в облегченном не-COM C ++ классе, который делегирует обратно предоставленному IUnknown.Когда вызывается QueryInterface (__uuidof (IFoo)), возвращайте FooHandler и предоставьте ему IUnknown MyCOMClass в качестве делегата IUnknown.

class FooHandler : public IFoo
{
public:
        SetDelegateUnknown(IUnknown* unk) { m_DelegateUnknown=unk; }
        IUnknown* GetDelegateUnknown() { return m_DelegateUnknown; }
    HRESULT STDMETHODCALLTYPE QueryInterface(const IID &riid,void **ppvObject) { return GetDelegateUnknown()->QueryInterface(riid, ppvObject); }
    virtual ULONG STDMETHODCALLTYPE AddRef(void) { return GetDelegateUnknown()->AddRef(); }
    virtual ULONG STDMETHODCALLTYPE Release( void) { return GetDelegateUnknown()->Release(); }

        // all the other iFoo methods are implemented here
private:
    IUnknown*   m_DelegateUnknown;  
};

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

Спасибо за любые предложения.

1 Ответ

1 голос
/ 30 марта 2011

Предполагая, что вам не нужно, чтобы объекты-обработчики были отдельными экземплярами C ++ от общего объекта, тогда может сработать следующее ...

Если я правильно помню свой COM ... - почему бы не простооставьте FooHandler как частично абстрактный базовый класс - оставьте часть IUnknown невыполненной.Пусть MyCOMClass наследует все необходимые обработчики;а затем реализовать IUnknown только в этом наиболее производном классе.Предоставленный вами AddRef / Release / QI будет использоваться для всех базовых классов.(Как правило, можно просто переслать AddRef / Release в класс CUnknown или некоторую базу, которая выполняет ведение подсчета, но, скорее всего, потребуется реализовать QI вручную, поскольку это единственное место, где вы полностью знаете, какой набор интерфейсов вы хотите предоставить.)

Другими словами, продолжайте делать то, что вы делаете;но вам не нужно выполнять часть делегирования вручную: компилятор на самом деле делает то же самое для вас за кулисами, так как множественное наследование интерфейсов (в частности, классов с виртуальными методами) работает в C ++.Волшебная часть заключается в том, что методы, объявленные в самом производном интерфейсе по умолчанию, переопределяют все методы с тем же именем и сигнатурой параметра в базовых классах;поэтому любой базовый класс, который вызывает AddRef или QI в своем собственном IUnknown, в конечном итоге вызовет версию, указанную вами в наиболее производном классе.

Код, вероятно, выглядит примерно так:

class MyCOMClass
    , public CUnknown // Assume this handles refcounting (and has a virtual dtor!)
    , public CFooHandler // Implements IFoo
    , public CBarHandler // Implements IBar
{
    ... add any interfaces that MyCOMClass itself is implementing...

    // Actual impl of IUnknown...
    STDMETHOD_(ULONG, AddRef)(); { return CUnknown::AddRef(); }
    STDMETHOD_(ULONG, Release)(); { return CUnknown::Release(); }

    STDMETHOD(QueryInterface)(IN REFIID riid, OUT void** ppv)
    {
        *ppv = NULL;

        // IUnknown can require extra casting to pick out a specific IUnknown instance
        // otherwise compiler will complain about an ambiguous cast. Any IUnknown will do,
        // we know they're all the same implementation, so even casting to CFooHandler then IUnknown is fine here.
        // Here am assuming that CUnknown implements IUnknown
        if(riid == __uuidof(IUnknown))
            *ppv = static_cast<IUnknown*>(static_cast<CUnknown*>(this));
        else if(riid == __uuidof(IFoo))
            *ppv = static_cast<IFoo*>(this);
        else if(riid == __uuidof(IBar))
            *ppv = static_cast<IBar*>(this);
        else
            return E_NOINTERFACE;

        // Usually you call AddRef on the interface you are returning; but
        // we know we're using the same ref count for the whole object, so this
        // is appropriate for this specific implementation strategy.
        AddRef();
    }

Если вы хотите реализовать обработчики для отдельных объектов, то вам нужно выполнить делегирование, которое вы предлагаете, - по сути, это форма агрегации.Но вам не нужно реализовывать интерфейсы на MyCOMClass и писать множество серверов пересылки: все, что MyCOMClass должен сделать, это реализовать QI таким образом, чтобы возвращаемое значение - будь то тот же самый объект или какой-то отдельный объект - было должным образом приведено кзапрашиваемый интерфейс.Но если вам не нужны отдельные объекты, описанная выше техника должна работать нормально.

...