Сущность технологии СОМ. Библиотека программиста
Шрифт:
На первый взгляд, отделяемые интерфейсы кажутся лучшей из всех возможностей. Когда интерфейс не используется, то на его служебные данные отводится нуль байт объекта. Когда же интерфейс используется, объект косвенно тратит 4 байта на служебные данные отделяемого интерфейса. Подобное впечатление базируется на нескольких обманчивых предположениях. Во-первых, затраты на работающий отделяемый интерфейс составляют отнюдь не только 4 байта памяти для его vptr. Отделяемому интерфейсу требуются также обратный указатель и счетчик ссылок [1] . Во-вторых, несмотря на возможность использования специального распределителя памяти (custom memory allocator ), отделяемому интерфейсу потребуется по крайней мере 4 дополнительных байта на выравнивание и/или заполнение заголовков динамически выделенной памяти, используемых С-библиотекой для реализации malloc/operator new .
1 Служебные данные счетчика ссылок можно сократить, если разработчик желает ограничить клиентское использование AddRef. Это очень опасная оптимизация, возникшая благодаря растущей популярности интеллектуальных указателей, и результатом ее часто является наличие избыточных (но безвредных) пар AddRef/Release.
К сожалению, дело с отделяемым интерфейсом обстоит еще хуже. Как видно из показанной ранее реализации, если объект получает два запроса QueryInterface на тот же самый отделяемый интерфейс, то будут созданы две копии этого отделяемого интерфейса, так как указатель на первый из них полностью забывается главным объектом, поскольку он был возвращен вызывающему объекту. Это означает, что в этом случае отделяемый интерфейс занимает по крайней мере от 24 до 32 байт, так как в памяти находятся оба vptr отделяемого интерфейса, по одному на каждый запрос QueryInterface. Эта память не будет восстановлена, пока клиент не освободит каждый отделяемый интерфейс. Ситуация, когда два запроса QueryInterface удерживают указатель в течение всего времени жизни объекта, особенно важна, так как именно это и происходит при удаленном обращении к объекту. СОМ-слой, реализующий удаленные вызовы, будет дважды запрашивать объект (с помощью QueryInterface) на предмет одного и того же интерфейса и будет удерживать оба результата в течение всего времени жизни объекта. Это обстоятельство делает отделяемые интерфейсы особенно рискованными для объектов, к которым может осуществляться удаленный доступ.
Узнав обо всех подводных камнях отделяемых интерфейсов, задаешь себе логичный вопрос: "В каких же случаях отделяемые интерфейсы являются подходящими?" Не существует безусловного ответа; в то же время отделяемые интерфейсы очень хороши для поддержки большого числа взаимно исключающих интерфейсов. Рассмотрим случай, в котором в дополнение к трем транспортным интерфейсам, показанным ранее, имеются интерфейсы ITruck (грузовик), IMonsterТruck (грузовик-монстр), IMotorcycle (мотоцикл), IBicycle (велосипед), IUnicycle (уницикл), ISkateboard (скейтборд) и IHelicopter (вертолет), причем все они наследуют IVehicle. Если бы производящий транспортный класс хотел поддерживать любой из этих интерфейсов, но только по одному из них для каждого заданного экземпляра, то для осуществления этого отделяемые интерфейсы были бы прекрасным способом при условии, что главный объект кэшировал бы указатель на первый отделяемый интерфейс. Определение класса главного объекта выглядело бы примерно так:
class GenericVehicle : public IUnknown
{
LONG m_cRef;
IVehicle *m_pTearOff;
// cached ptr to tearoff
// кэшированный указатель на отделяемый интерфейс
GenericVehicle(void) : m_cRef(0), m_pTearOff(0) {}
// IUnknown methods
// методы IUnknown
STDMETHODIMP QueryInterface(REFIID, void **);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release (void);
// define tearoff classes
// определяем классы отделяемых интерфейсов
class XTruck : public ITruck { … };
class XMonsterTruck : public IMonsterTruck { … };
class XBicycle : public IBicycle { … };
:
:
:
};
В этом классе в случае, когда не используется ни один из интерфейсов, объект платит за пустой
STDMETHODIMP GenericVehicle::QueryInterface(REFIID riid ,void **ppv)
{ if (riid == IID_IUnknown) *ppv = static_cast<IUnknown*>(this);
else if (riid == IID_ITruck) { if (m_pTearOff == 0)
// no tearoff yet, make one
// отделяемого интерфейса еще нет, создаем один
m_pTearOff = new XTruck(this);
if (m_pTearOff)
// tearoff exists, let tearoff QI
// отделяемый интерфейс существует, пусть это QI
return m_pTearOff->QueryInterface(riid, ppv);
else
// memory allocation failure
// ошибка выделения памяти
return (*ppv = 0), E_NOINTERFACE;
}
else if (riid == IID_IMonsterTruck)
{
if (in_pTearOff == 0)
// no tearoff yet, make one
// отделяемого интерфейса еще нет, создаем один
m_pTearOff = new XMonsterTruck(this);
if (m_pTearOff)
// tearoff exists, let tearoff QI
// отделяемый интерфейс существует, пусть это QI
return m_pTearOff->QueryInterface(riid, ppv);
else
// memory allocation failure
// ошибка выделения памяти
return (*ppv = 0), E_NOINTERFACE;
}
else …
:
:
:
}
На основе показанной здесь реализации QueryInterface на каждый объект будет приходиться по большей части по одному отделяемому интерфейсу. Это значит, что в случае отсутствия запросов на транспортные интерфейсы объект будет тратить в сумме 12 байт (vptr IUnknown + счетчик ссылок + кэшированный указатель на отделяемый интерфейс). Если транспортный интерфейс запрошен, то объект будет тратить в сумме от 24 до 28 байт (исходные 12 байт + наследующий Vehicle vptr + счетчик ссылок + обратный указатель на главный объект + (необязательно) служебная запись malloc (memory allocation – выделение памяти)).
Если бы в данном случае отделяемые интерфейсы не использовались, то определение класса выглядело бы примерно так:
class GenericVehicle : public ITruck, public IHelicopter, public IBoat, public ICar, public IMonsterTruck, public IBicycle, public IMotorcycle, public ICar, public IPlane, public ISkateboard { LONG m_cRef;
// IUnknown methods – методы IUnknown
:
:
:
};
В результате этот класс создал бы объекты, тратящие всегда 44 байта (десять vptr + счетчик ссылок). Хотя производящий класс может показаться немного запутанным, постоянные интерфейсы СОМ принадлежат к аналогичной категории, так как в настоящее время существует восемь различных постоянных интерфейсов, но объект обычно выставляет только один из них на экземпляр. В то же время разработчик класса не всегда может предсказать, какой из интерфейсов будет запрошен определенным клиентом (и будет ли какой-либо). Кроме того, каждый из восьми интерфейсов требует своего набора поддерживающих элементов данных для корректной реализации методов интерфейса. Если эти элементы данных были созданы как часть отделяемого интерфейса, а не главного объекта, то для каждого объекта будет назначен только один набор элементов данных. Этот тип сценария идеален для отделяемых интерфейсов, но опять же, для большей эффективности, указатель на отделяемый интерфейс следует кэшировать в главном объекте.
Двоичная композиция
Композиция и отделяемые интерфейсы – это две технологии на уровне исходного кода, предназначенные для реализации объектов СОМ на C++. Обе эти технологии требуют, чтобы разработчик объекта имел определения для каждого класса композита или отделяемого интерфейса в исходном коде C++, для возможности обработать подобъект, прежде чем возвратить его посредством QueryInterface. Для ряда ситуаций это очень разумно. В некоторых случаях, однако, было бы удобнее упаковать многократно используемую реализацию одного или большего числа интерфейсов в двоичный компонент, который мог бы обрабатываться через границы DLL, не нуждаясь в исходном коде подкомпонента. Это позволило бы более широкой аудитории повторно использовать подкомпонент, избегая слишком тесной связи с ним, как в случае повторного использования на уровне исходного кода (этот случай описан в главе 1). Однако если компонент повторного использования представляет собой двоичный композит или отделяемый интерфейс, то он должен участвовать в общей идентификации объекта.
Истинная со скидкой для дракона
Любовные романы:
любовно-фантастические романы
рейтинг книги
Герцог и я
1. Бриджертоны
Любовные романы:
исторические любовные романы
рейтинг книги
На границе империй. Том 9. Часть 5
18. Фортуна дама переменчивая
Фантастика:
космическая фантастика
попаданцы
рейтинг книги
Росток
2. Хозяин дубравы
Фантастика:
попаданцы
альтернативная история
фэнтези
рейтинг книги
Демон
2. История одного эволюционера
Фантастика:
рпг
постапокалипсис
рейтинг книги
Огромный. Злой. Зеленый
1. Большой. Зеленый... ОРК
Любовные романы:
любовно-фантастические романы
рейтинг книги
Запечатанный во тьме. Том 1. Тысячи лет кача
1. Хроники Арнея
Фантастика:
уся
эпическая фантастика
фэнтези
рейтинг книги
Тайны ордена
6. Девятый
Фантастика:
боевая фантастика
попаданцы
рейтинг книги
Кодекс Охотника. Книга VI
6. Кодекс Охотника
Фантастика:
фэнтези
попаданцы
аниме
рейтинг книги
Неудержимый. Книга XXI
21. Неудержимый
Фантастика:
попаданцы
аниме
фэнтези
рейтинг книги
Возлюби болезнь свою
Научно-образовательная:
психология
рейтинг книги
