Расширение интерфейсов MIDL и дизайн COM-объектов - PullRequest
2 голосов
/ 03 марта 2011

Я читал о различных шаблонах проектирования COM, подробно описанных в Поваренной книге программиста COM , а также о некоторых связанных SO-потоках, в частности , обсуждающих композицию и множественное наследование .Возможно, из-за того, что я слишком плохо знаком с C ++ и COM, возможно, мне не хватает замечаний, высказанных в различных источниках, поэтому вот мой вопрос, выраженный в одном предложении:

Могу ли я расширить интерфейс, сгенерированный MIDL дляВнутреннее использование DLL и, если да, то как правильно обрабатывать проблему алмаза / параллельную иерархию с учетом ограничений MIDL / COM?

Грязные детали ...

Надеемся, что поможет другим определитьгде мое замешательство может быть, вот мои предположения:

1) COM не поддерживает виртуальное наследование, а допускает только множественное наследование только через интерфейсы.

2) Несмотря на то, что COM не может его видеть, для меня не должно быть незаконным использование неподдерживаемого наследования C ++, если я не ожидаю, что оно будет открыто предоставлено COM.

3) Поскольку MIDL допускает только одиночное наследование интерфейсов, если у меня параллельная иерархия, мне нужно объединить их для кокласса.

4) MIDL, по-видимому, не объявляет сам кокласс, поэтому мне нужно было бы написать .h файл, объявляющий фактический класс, и я могу расширять его по мере необходимости, понимая, что потребители COM не могут его использовать (и этоХОРОШО).

То, что я хочу сделать, - это иметь базовый объект (я еще не решил, будет ли он абстрактным или нет, хотя я думаю, что это будет сейчас), который обрабатывает большинство деталей реализации и делегирует некоторые конкретныефункциональность для подклассов.Клиент обычно использует подклассы.Итак,

project.idl

import "oaidl.idl"
import "ocidl.idl"

[
  object,
  uuid(...),
  dual,
  oleautomation
]
interface IBase : IDispatch {
   //stuff I want to show to COM
};

[
  object,
  uuid(...),
  dual,
  oleautomation
]
interface IChild1 : IBase {
   //stuff (in addition to base) I want to show to COM
};

[
  object,
  uuid(...),
  dual,
  oleautomation
]
interface IChild2 : IBase {
   //stuff (in addition to base) I want to show to COM
};

[
   uuid(...),
   version(...),
]
library myproject {
   importlib("stdole32.tlb");
   interface IBase;
   interface IChild1;
   interface IChild2;
   [
      uuid(...),
   ]
   coclass Base {
      [default]interface IBase;
      interface IOle*; //include other IOle* interfaces required for the functionality
   };
   [
      uuid(...),
   ]
   coclass Child1 {
      [default]interface IChild1;
      interface IOle*; //those are delegated to the base members
   };
   [
      uuid(...),
   ]
   coclass Child2 {
      [default]interface IChild2;
      interface IOle*; //those are delegated to the base members
   };
};

base.h

#include base_h.h //interfaces generated by MIDL

// I assume I need to re-include IOle* because IBase has no relationship
// and C++ wouldn't know that I want the object base to also have those
// interfaces...
class base : public IBase,
             public IOle* {
    //handle all IUnknown, IDispatch and other IOle* stuff here
    //as well as the common implementations as appropriate
};

child1.h

#include base.h

//I'm not sure if I need to re-include the IOle* interfaces...
//I also assume that by inheriting base, child1 also inherits its interface
class Child1 : public Base,
               public IChild1 {
  //specific details only, let base handle everything else.
};

child2.h

#include base.h

//I'm not sure if I need to re-include the IOle* interfaces...
class Child2 : public Base,
               public IChild2 {
  //specific details only, let base handle everything else.
};

Концептуально создание нового дочернего * объекта всегда будет означать создание базового объекта, поскольку base будетТребуется обработать детали реализации, поэтому я подумал, что база также должна позаботиться о подсчете QueryInterface & Reference, но я запутался в следующих моментах:

1) компилятор жалуется на неоднозначность членов из-запараллельной иерархии;IUnknown повторно реализован несколько раз из моего пользовательского интерфейса и из дополнительных интерфейсов IOle *.Документация предполагает, что ожидается, что на самом деле необходима только одна реализация для каждого объекта, но я не понимаю, как бы я решил проблемы с компилятором, и я чувствую, что выполнение приведения типов как-то неправильно?Мне также интересно, если я должен виртуально унаследовать все интерфейсы, которые кажутся действительными для C ++, хотя у COM такого понимания нет, но это не должно волновать (?).

2) Однако, если я объявляю все унаследованные интерфейсы как виртуальные в файлах .h, компилятор затем жалуется, что унаследованные члены не допускаются, когда я пытаюсь реализовать QueryInterface в Base class.cpp.Я погуглил эту ошибку, но не ясно, что она пытается мне сказать.

РЕДАКТИРОВАТЬ: Я ответил на свой вопрос. У Intel была документация для этой ошибки, которую я изначально не щелкал по ссылке, предполагая, что она может не относиться к Visual Studio.Хотелось бы, чтобы я это сделал, но теперь я понимаю, почему я получаю эту ошибку, поскольку я пытался выполнить всю реализацию в Base ::, а не IUnknown :: или IDispatch ::.Теперь возникает новый вопрос, который может прояснить мой первоначальный и главный вопрос - как отложить реализацию от IUnknown (и других) до Base и работать только с Base, если это вообще возможно?Кажется, что если я просто использую IUnknown :: xxx, он больше не сможет получить доступ к закрытым членам Базы, что, как мне кажется, вполне разумно, так что это может быть не тем, что я хочу.Я попытался объявить все другие интерфейсы, кроме собственного базового, как виртуальные, но на самом деле это не так.(Опять же, возможно, моя неопытность не видела очевидного решения.)

3) QueryInterface Base не может привести base к потомку, что является разумной жалобой, поэтому я предполагаю, что мне все равно придется переопределить QI для всех дочерних элементов, но я могу делегировать обратно в QI базы, как только я определю, что запрошенные интерфейсы не являются ребенок. Как ни странно, компилятор настаивает на том, что дочерний * класс является абстрактным из-за отсутствия элементов для IUnknown & IDispatch, но разве база уже не реализована, и, следовательно, дочерний элемент также должен иметь эти члены?

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

Спасибо!

1 Ответ

6 голосов
/ 15 марта 2011

У вас здесь правильная идея, все, чего вам не хватает, - это связать какие-то слабые стороны в самом производном классе.Как разработчик COM, вы ожидаете, что все значения AddRef / Release / QI для объекта класса будут одинаковыми;но компилятор, ориентированный на C ++, этого не знает, поэтому рассматривает их как потенциально отдельные.Здесь у вас есть два вывода: базовое и любое из добавленных вами интерфейсов.

Установить компилятор прямо здесь довольно просто: в самом производном классе переопределите все методы IUnknown инаправьте их в соответствующий базовый класс - например.

class ChildX: public Base,
              public IChildA
              ... more COM interfaces if needed ...
{
    ...

    // Direct IUnknown methods to Base which does the refcounting for us...
    STDMETHODIMP_(ULONG) AddRef() { return Base::AddRef(); } 
    STDMETHODIMP_(ULONG) Release() { return Base::Release(); } 
    ... suggest implementing QI explicitly here.
}

Это в основном говорит о том, что все методы с именем AddRef, независимо от того, как они оказались в ChildX, получат эту конкретную реализацию.

Егопроще всего на самом деле реализовать QI здесь, и только делегировать AddRef / Release в Base.(Технически, Base может приводиться к Child с использованием static_cast, но вам нужно поместить код в функцию после того, как Child был полностью определен; однако делать это не рекомендуется, так как у базового класса редко бывает хорошая причина знать оклассы, производные от него.)

Другие вещи, на которые нужно обратить внимание: убедитесь, что в Base объявлен виртуальный dtor - даже если он просто пуст, чтобы когда Base делал «delete this», когда ссылка переходит к0, он будет вызывать dtors в производных классах, и любые выделенные ими ресурсы будут очищены соответствующим образом.Кроме того, убедитесь, что счетчик ссылок правильный и поточно-ориентированный, если это необходимо;сверьтесь с любым хорошим введением в книгу COM (например, «Внутри распределенного COM», который, несмотря на название, начинается с простого COM), чтобы увидеть, как другие люди делают это.

Это очень распространенная идиома в COMи многие фреймворки используют либо макросы #define, либо более производный шаблонный класс для добавления в AddRef / Release / QI (как это делает MFC) в самом производном классе, а затем делегируют их хорошо известному базовому классу, который обрабатывает многоведения хозяйства.

...