Сущность технологии СОМ. Библиотека программиста
Сущность технологии СОМ. Библиотека программиста читать книгу онлайн
В этой книге СОМ исследуется с точки зрения разработчика C++. Написанная ведущим специалистом по модели компонентных объектов СОМ, она раскрывает сущность СОМ, помогая разработчикам правильно понять не только методы модели программирования СОМ, но и ее основу. Понимание мотивов создания СОМ и ее аспектов, касающихся распределенных систем, чрезвычайно важно для тех разработчиков, которые желают пойти дальше простейших приложений СОМ и стать по-настоящему эффективными СОМ-программистами. Показывая, почему СОМ для распределенных систем (Distributed СОМ) работает именно так, а не иначе, Дон Бокс дает вам возможность применять эту модель творчески и эффективно для ежедневных задач программирования.
Внимание! Книга может содержать контент только для совершеннолетних. Для несовершеннолетних чтение данного контента СТРОГО ЗАПРЕЩЕНО! Если в книге присутствует наличие пропаганды ЛГБТ и другого, запрещенного контента - просьба написать на почту [email protected] для удаления материала
#define IID_PPV_ARG(Type, Expr) IID_##type,
reinterpret_cast<void**>(static_cast<Type **>(Expr))
С помощью этого макроса [1] компилятор будет уверен в том, что выражение, использованное в приведенном ниже вызове QueryInterface, имеет правильный тип и что используется соответствующий уровень изоляции (indirecton):
IPug *pPug = 0; hr = punk->QueryInterface(IID_PPV_ARG(IPug, &pPug));
Этот макрос закрывает брешь, вызванную параметром void**, без каких-либо затрат на этапе выполнения.
Реализация IUnknown
Имея описанные выше образцы клиентского использования, легко видеть, как реализовать методы IUnknown. Примем предложенную выше иерархию типов Dog/Cat. Чтобы определить С++-класс, который реализует интерфейсы IPug и ICat , нужно просто добавить к списку базовых классов самые последние в иерархии наследования версии интерфейсов:
class PugCat : public IPug, public ICat
При использовании наследования компилятор C++ обеспечивает совместимость двоичного представления производного класса с каждым базовым классом. Для класса PugCat это означает, что все объекты PugCat будут содержать указатель vptr, указывающий на таблицу vtbl, совместимую с IPug. Объекты PugCat также будут содержать указатель vptr, указывающий на вторую таблицу vtbl, совместимую с ICat. Рисунок 2.5 показывает, как интерфейсы в качестве базовых классов соотносятся с представлением объектов.
Поскольку все функции-члены в СОМ-определениях интерфейса являются чисто виртуальными, производный класс должен обеспечивать реализацию каждого метода, имеющегося в любом из его интерфейсов. Методы, общие для двух или более интерфейсов (например, QueryInterface, AddRef и т. д.) нужно реализовывать только один раз, так как компилятор и компоновщик инициализируют все таблицы vtbl так, чтобы они указывали на одну реализацию метода. Таков естественный побочный эффект от использования множественного наследования в языке C++.
Следующий код является определением класса, которое создает объекты, поддерживающие интерфейсы IPug и ICat:
class PugCat : public IPug, public ICat
{
LONG mcRef;
protected:
virtual ~PugCat(void);
public: PugCat(void);
// IUnknown methods
// методы IUnknown
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
STDMETHODIMP(ULONG) AddRef(void);
STDMETHODIMP(ULONG) Release(void);
// IAnimal methods
// методы IAnimal
STDMETHODIMP Eat(void);
// IDog methods
// методы IDog
STDMETHODIMP Bark(void);
// IPug methods
// методы IPug
STDMETHODIMP Snore(void);
// ICat methods
// методы ICat
STDMETHODIMP IgnoreMaster(void);
};
Отметим, что в классе должен быть реализован каждый метод, определенный в любом интерфейсе, от которого он наследует, так же, как и каждый метод, определенный в любых производных (implied) базовых интерфейсах (например, IDog, IAnimal ). Для создания стековых фреймов, совместимых с СОМ, необходимо использовать макросы STDMETHODIMP и STDMETHODIMP. При ориентации на платформы Win32, использующие компилятор Microsoft C++, заголовки SDK определяют эти два макроса следующим образом:
#define STDMETHODIMP HRESULT stdcall
#define STDMETHODIMP(type) type stdcall
Заголовочные файлы SDK также определяют макросы STDMETHOD и STDMETHOD , которые можно использовать при определении интерфейсов без IDL-компилятора. В серийно выпускаемом программировании на СОМ эти два макроса не нужны.
Реализация AddRef и Release чрезвычайно прозрачна. Элемент данных mcRef отслеживает, сколько неосвобожденных интерфейсных указателей удерживают объект. Конструктор класса приводит счетчик ссылок в нулевое состояние:
PugCat::PugCat(void) : mcRef(0)
// initialize reference count to zero
// устанавливаем счетчик ссылок в нуль
{ }
Реализация AddRef в классе фиксирует путем увеличения счетчика ссылок, что вызывающий объект продублировал указатель интерфейса. Измененное значение счетчика ссылок возвращается для целей диагностики:
STDMETHODIMP(ULONG) AddRef(void)
{ return ++mcRef; }
Реализация Release фиксирует уничтожение указателя интерфейса простым уменьшением счетчика ссылок, а также производит соответствующее действие, когда счетчик ссылок достигает нуля. Для объектов, находящихся в динамически распределяемой области памяти, это означает вызов оператора delete для уничтожения объекта:
STDMETHODIMP(ULONG) Release(void)
{
LONG res = -mcRef;
if (res == 0) delete this;
return res;
}
Для кэширования обновленного счетчика ссылок необходимо использовать временную переменную, так как нельзя обращаться к элементам данных объекта после того, как объект уже уничтожен.
Заметим, что показанные реализации Addref и Release используют собственные операторы инкремента и декремента (увеличения и уменьшения на единицу). Для простой реализации это весьма разумно, так как СОМ не допускает более одного потока для обращения к объекту до тех пор, пока конструктор не обеспечит явный многопоточный доступ (почему и как конструктор сделает это, подробно описано в главе 5). В случае объектов, доступных в многопоточной среде, для автоматического подсчета ссылок следует использовать подпрограммы Win32 InterlockedIncrement/InterlockedDecrement:
STDMETHODIMP(ULONG) AddRef(void)
{
return InterlockedIncrement(&mcRef);
}
STDMETHODIMP(ULONG) Release(void)
{
LONG res = InterlockedDecrement(&mcRef);
if (res == 0) delete this; return res;
}
Этот код несколько менее эффективен, чем версии, использующие собственные операторы C++. Но, вообще говоря, разумнее использовать менее эффективные варианты InterlockedIncrement / InterlockedDecrement, так как известно, что они надежны во всех ситуациях и освобождают разработчика от необходимости сохранять две версии практически одинакового кода.
Показанные выше реализации AddRef и Release предполагают, что объект может размещаться только в динамически распределяемой области памяти (в «куче») с использованием С++-оператора new. В определении класса деструктор сделан защищенной операцией для обеспечения того, чтобы ни один экземпляр класса не был определен никаким другим способом. Однако иногда желательно иметь объекты, не размещенные в «куче». Для этих объектов вызов delete в последнем вызове Release был бы гибельным. Так как единственной причиной для того, чтобы объект в первую очередь поддерживал счетчик ссылок, была необходимость вызова delete this, допустимо оптимизировать счетчик ссылок для объектов, не содержащихся в динамически распределяемой области памяти: