Сущность технологии СОМ. Библиотека программиста
Сущность технологии СОМ. Библиотека программиста читать книгу онлайн
В этой книге СОМ исследуется с точки зрения разработчика C++. Написанная ведущим специалистом по модели компонентных объектов СОМ, она раскрывает сущность СОМ, помогая разработчикам правильно понять не только методы модели программирования СОМ, но и ее основу. Понимание мотивов создания СОМ и ее аспектов, касающихся распределенных систем, чрезвычайно важно для тех разработчиков, которые желают пойти дальше простейших приложений СОМ и стать по-настоящему эффективными СОМ-программистами. Показывая, почему СОМ для распределенных систем (Distributed СОМ) работает именно так, а не иначе, Дон Бокс дает вам возможность применять эту модель творчески и эффективно для ежедневных задач программирования.
Внимание! Книга может содержать контент только для совершеннолетних. Для несовершеннолетних чтение данного контента СТРОГО ЗАПРЕЩЕНО! Если в книге присутствует наличие пропаганды ЛГБТ и другого, запрещенного контента - просьба написать на почту [email protected] для удаления материала
if (m_pTonsOfMemory == 0) m_pTonsOfMemory = new char[4096 * 4096];
*ppv = static_cast<IBoat*>(this);
}
else if
…
}
не существует способа определить момент, когда больше нет внешних указателей интерфейса IBoat, так как вызов Release, который клиент делает через интерфейс IBoat, неотличим от вызова Release, сделанного через любой другой интерфейс объекта. В обычной ситуации именно это и нужно, но в данном случае вызовы AddRef и Release через интерфейсы IBoat необходимо рассматривать иначе. Если бы интерфейс IBoat был реализован с использованием композиции, то он имел бы свои собственные уникальные реализации AddRef и Release, в которых он мог бы поддерживать свой собственный счетчик ссылок, отличный от счетчика главного объекта:
class CarBoatPlane : public ICar, public IPlane
{
LONG m_cRef;
char *m_pTonsOfMemory;
CarBoatPlane (void) : m_cRef(0),
m_pTonsOfMemory (0) {}
public:
// IUnknown methods – методы IUnknown
STDMETHODIMP QueryInterface(REFIID, void**);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
// IVehicle methods – методы IVehicle
STDMETHODIMP GetMaxSpeed(long *pMax);
// ICar methods – методы ICar
STDMETHODIMP Brake(void);
// IPlane methods – методы IPlane
STDMETHODIMP TakeOff(void);
// define nested class that implements IBoat
// определяем вложенный класс, реализующий IBoat
struct XBoat : public IBoat {
// get back pointer to main object
// получаем обратный указатель на главный объект
inline CarBoatPlane* This();
LONG m_cBoatRef;
// per-interface ref count
// счетчик ссылок на каждый интерфейс
XBoat(void) : m_cBoatRef(0) {}
STDMETHODIMP QueryInterface(REFIID, void**);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);
STDMETHODIMP GetMaxSpeed(long *pval);
STDMETHODIMP Sink(void);
};
XBoat m_xBoat; };
Реализация AddRef и Release из IBoat могут теперь следить за числом ссылок типа IBoat и высвободить ресурсы, когда они больше не нужны:
STDMETHODIMP_(ULONG) CarBoatPlane::XBoat::AddRef()
{
ULONG res = InterlockedIncrement(&m_cBoatRef);
if (res == 1)
{
// first AddRef – первый AddRef
// allocate resource and forward AddRef to object
// размещаем ресурсы и пересылаем AddRef на объект
This()->m_pTonsOfMemory = new char[4096*4096];
This()->AddRef(); }
return res; }
STDMETHODIMP_(ULONG) CarBoatPlane::XBoat::Release()
{
ULONG res = InterlockedDecrement(&m_cBoatRef);
if (res == 0) {
// last Release – последний Release
// free resource and forward Release to object
// освобождаем ресурсы и пересылаем Release на объект
delete [] This()->m_pTonsOfMemory;
This()->Release();
} return res; }
Чтобы эта методика работала, все пользующиеся интерфейсными указателями должны придерживаться требований спецификации СОМ: функция Release должна вызываться через указатель, посредством которого вызывается соответствующая функция AddRef. Поэтому правильной концовкой QueryInterface будет следующая:
((IUnknown*)(*ppv))->AddRef();
// use exact ptr
// используем точный указатель return S_OK;
вместо такого:
AddRef();
// just call this->AddRef
// только вызов
this->AddRef return S_OK;
Первый вариант гарантирует, что если клиент пишет следующий правильный код
IBoat *pBoat = 0;
HRESULT hr = pUnk->QueryInterface(IID_IBoat, (void**)&pBoat);
if (SUCCEEDED(hr))
{ hr = pBoat->Sink(); pBoat->Release(); }
то для AddRef и для Release обязательно будет использовано одно и то же значение указателя.
Можно осуществлять композицию в контексте управляемой таблицами реализации QueryInterface. При наличии семейства макросов препроцессора, показанного в предыдущей главе, достаточно всего одного дополнительного макроса, чтобы определить, что вместо базового класса используется элемент данных, и второго макроса, чтобы реализовать методы IUnknown в композите:
class CarBoatPlane : public ICar, public IPlane
{ public: struct XBoat : public IBoat
{
// composite QI/AddRef/Release/This()
// композит из QI/AddRef/Release/This()
IMPLEMENT_COMPOSITE_UNKNOWN(CarBoatPlane, XBoat, m_xBoat) STDMETHODIMP GetMaxSpeed(long *pval);
STDMETHODIMP Sink(void);
};
XBoat m_xBoat;
// IVehicle methods
// методы IVehicle
STDMETHODIMP GetMaxSpeed(long *pMax);
// ICar methods
// методы ICar
STDMETHODIMP Brake(void);
// IPlane methods
// методы IPlane
STDMETHODIMP TakeOff(void);
// standard heap-based QI/AddRef/Release
// стандартные расположенные в «куче» QI/AddRef/Release
IMPLEMENT_UNKNOWN(CarBoatPlane)
BEGIN_INTERFACE_TABLE(CarBoatPlane)
IMPLEMENTS_INTERFACE_AS(IVehicle, ICar)
IMPLEMENTS_INTERFACE(ICar)
IMPLEMENTS_INTERFACE(IPlane)
// macro that calculates offset of data member
// макрос, вычисляющий смещение элемента данных
IMPLEMENTS_INTERFACE_WITH_COMPOSITE(IBoat, XBoat, m_xBoat)
END_INTERFACE_TABLE() };
В приведенном выше определении класса опущены только определения методов объекта вне QueryInterfасе, AddRef и Release. Два новых макроса, использованных в определении класса, определяются следующим образом:
// inttable.h
// (book-specific header file)
// (заголовочный файл, специфический для данной книги)
#define COMPOSITE_OFFSET(ClassName, BaseName,
MemberType, MemberName)
(DWORD(static_cast<BaseName*>(
reinterpret_cast<MemberType*>(0x10000000 +
offsetof(ClassName, MemberName)))) – 0х10000000)
#define IMPLEMENTS_INTERFACE_WITH_COMPOSITE(Req,
MemberType, MemberName)
{ &IID_##Req,ENTRY_IS_OFFSET, COMPOSITE_OFFSET(_IT,
Req, MemberType, MemberName) },
// impunk.h
// (book-specific header file)
// (заголовочный файл, специфический для данной книги)
#define IMPLEMENT_COMPOSITE_UNKNOWN(OuterClassName,
InnerClassName, DataMemberName)
OuterClassName *This()
{ return (OuterClassName*)((char*)this –
offsetof(OuterClassName, DataMemberName)); }
STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
{ return This()->QueryInterface(riid, ppv); }
STDMETHODIMP_(ULONG) AddRef(void)
{ return This()->AddRef(); }
STDMETHODIMP_(ULONG) Release(void)
{ return This()->Release(); }
Эти макросы препроцессора просто дублируют фактические реализации QueryInterface, AddRef и Release , использованные в композиции.
Динамическая композиция
Если для реализации интерфейса в классе C++ используется множественное наследование или композиция, то в каждом объекте этого класса будут содержаться служебные данные (overhead) указателя vptr размером в четыре байта на каждый поддерживаемый интерфейс (принимая, что sizeof (void*) == 4). Если число интерфейсов, экспортируемых объектом, невелико, то эти служебные данные не играют важной роли, особенно в свете преимуществ, предоставляемых программной моделью СОМ. Если, однако, число поддерживаемых интерфейсов велико, то размер служебных данных vptr может вырасти до такой степени, что часть объекта, не связанная с СОМ, будет казаться маленькой по сравнению с ними. При использовании каждого из этих интерфейсов все время без служебных данных не обойтись. Если же, однако, эти интерфейсы не будут использоваться никогда или использоваться в течение короткого времени, то можно воспользоваться лазейкой в Спецификации СОМ и оптимизировать vptr некоторых неиспользуемых объектов.