Сущность технологии СОМ. Библиотека программиста
Сущность технологии СОМ. Библиотека программиста читать книгу онлайн
В этой книге СОМ исследуется с точки зрения разработчика C++. Написанная ведущим специалистом по модели компонентных объектов СОМ, она раскрывает сущность СОМ, помогая разработчикам правильно понять не только методы модели программирования СОМ, но и ее основу. Понимание мотивов создания СОМ и ее аспектов, касающихся распределенных систем, чрезвычайно важно для тех разработчиков, которые желают пойти дальше простейших приложений СОМ и стать по-настоящему эффективными СОМ-программистами. Показывая, почему СОМ для распределенных систем (Distributed СОМ) работает именно так, а не иначе, Дон Бокс дает вам возможность применять эту модель творчески и эффективно для ежедневных задач программирования.
Внимание! Книга может содержать контент только для совершеннолетних. Для несовершеннолетних чтение данного контента СТРОГО ЗАПРЕЩЕНО! Если в книге присутствует наличие пропаганды ЛГБТ и другого, запрещенного контента - просьба написать на почту [email protected] для удаления материала
Маршалер свободной поточной обработки (FreeThreaded Marshaler)
Если в классе установлена опция ThreadingModel="Both" , то она показывает, что экземпляры класса, а также объект класса могут безопасно находиться в любых апартаментах: STA или МТА. В то же время, согласно правилам СОМ, любой данный экземпляр будет находиться только в одном апартаменте. Если бы разработчик объекта прошел все этапы проверки того, что объект может благополучно находиться в МТА, то в этом случае объекту вообще не нужно было бы заботиться об апартаментах. Одновременный доступ к подобному объекту мог бы быть не только для нескольких потоков внутри МТА, но также от потоков вне МТА (например, от потоков, выполняемых в STA). В то же время клиенты не могут знать, что такой доступ является безопасным для отдельно взятого объекта, поэтому любое совместное использование интерфейсного указателя в нескольких апартаментах должно быть установлено с использованием явной технологии маршалинга. Это означает, что доступ к внутрипроцессному объекту будет осуществляться через ORPC-вызовы, если только вызывающий объект не выполняется в том же самом апартаменте, где был создан объект.
В отличие от клиентов, объекты знают о своих отношениях с апартаментами, о своем параллелизме и реентерабельности. Объекты, удовлетворяющиеся ORPC-запросами при доступе из нескольких апартаментов одного и того же процесса, ведут себя так по умолчанию. А объект, которого не устраивает доступ ORPC, имеет возможность обойти это путем реализации специального маршалинга. Довольно просто использовать специальный маршалинг для обхода администратора заглушек и преобразования исходного указателя на объект в маршалированную объектную ссылку. При использовании этой технологии реализация специального заместителя могла бы просто считывать исходный указатель из маршалированной объектной ссылки и передавать его вызывающему объекту в импортирующем апартаменте. Клиентские потоки по-прежнему передавали бы интерфейсный указатель через границу апартамента с помощью явного или неявного вызова CoMarshalInterface / CoUnmarshalInterface. Однако объект мог бы договориться со специальным заместителем о том, чтобы просто передать исходный указатель нужному объекту. Хотя данная технология безупречно работает для внутрипроцессного маршалинга, она, к сожалению, не приводит к успеху в случае межпроцессного маршалинга. Но, к счастью, реализация объекта может просто обратиться к стандартному маршалеру за другим контекстом маршалинга, отличным от MSHCTX_INPROC.
Поскольку только что описанное поведение является полезным для большого класса объектов, в СОМ предусмотрена агрегируемая реализация IMarshal, выполняющая в точности то, что было описано. Эта реализация называется маршалером свободной поточной обработки (FreeThreaded Marshaler – FTM) и может быть осуществлена с помощью вызова API-функции CoCreateFreeThreadedMarshaler:
HRESULT CoCreateFreeThreadedMarshaler( [in] IUnknown *pUnkOuter, [out] IUnknown **ppUnkInner);
Класс, который желает использовать FTM, просто агрегирует экземпляр либо во время инициализации, либо по требованию при первом запросе QueryInterface об интерфейсе IMarshal . Следующий класс заранее обрабатывает FTM во время построения.
class Point : public IPoint {
LONG m_cRef; IUnknown *m_pUnkFTM;
long m_x; long m_y; Point(void) : m_cRef(0), m_x(0), m_y(0) {
HRESULT hr = CoCreateFreeThreadedMarshaler(this,&m_pUnkFTM);
assert(SUCCEEDED(hr)) ;
}
virtual ~Point(void) { m_pUnkFTM->Release(); }
};
Соответствующая реализация QueryInterface просто запросила бы интерфейс IMarshal из FTM:
STDMETHODIMP Point::QueryInterface(REFIID riid, void **ppv)
{ if (riid == IID_IUnknown || riid == IID_IPoint)
*ppv = static_cast<IPoint*>(this);
else if (riid == IID_IMarshal) return m_pUnkFTM->QueryInterface(riid, ppv);
else return (*ppv = 0), E_NOINTERFACE;
((IUnknown* )*ppv)->AddRef();
return S_OK;
}
Поскольку используется FTM, не понадобится никаких заместителей, как бы ни маршалировались через внутрипроцессные границы апартамента ссылки на объекты Point . Это применимо к явным вызовам CoMarshalInterface / CoUnmarshalInterface, а также в случаях, когда ссылки на объекты Point передаются как параметры метода на внутрипроцессные заместители объектов, не являющихся объектами Point.
FTM занимает не менее 16 байт памяти. Поскольку многие внутрипроцессные объекты никогда не используются за пределами своего апартамента, то предварительное выделение памяти для FTM не является лучшим использованием имеющихся ресурсов. В высшей степени вероятно, что объект уже имеет некий примитив для синхронизации потоков. В таком случае FTM может быть отложенно агрегирован (lazy-aggregated) при первом же запросе QueryInterface о IMarshal. Для того чтобы добиться этого, рассмотрим такое определение класса:
class LazyPoint : public IPoint {
LONG m_cRef; IUnknown *m_pUnkFTM;
long m_x;
long m_y;
LazyPoint (void) : m_cRef (0) .m_pUnkFTM(0),m_x(0), m_y(0) {}
virtual ~LazyPoint(void) {
if (m_pUnkFTM) m_pUnkFTM->Release();
}
void Lock(void);
// acquire object-specific lock
// запрашиваем блокировку, специфическую для объектов
void Unlock(void);
// release object-specific lock
// освобождаем блокировку, специфическую для объектов
:
:
:
};
Основываясь на таком определении класса, следующая реализация QueryInterface осуществит корректное агрегирование FTM по требованию:
STDMETHODIMP Point::QueryInterface(REFIID riid, void **ppv) {
if (riid == IID_IUnknown || riid == IID_IPoint)
*ppv = static_cast<IPoint*>(this);
else if (riid == IID_IMarshal) {
this->Lock();
HRESULT hr = E_NOINTERFACE;
*ppv = 0;
if (m_pUnkFTM == 0)
// acquire FTM first time through
// получаем первый FTM
CoCreateFreeThreadedMarshaler(this, &m_pUnkFTM);
if (m_pUnkFTM != 0)
// by here, FTM is acquired
// здесь получен FTM
hr = m_pUnkFTM->QueryInterface(riid, ppv);
this->Unlock();
return hr;
} else return (*ppv = 0), E_NOINTERFACE;
((IUnknown *)*ppv)->AddRef(); return S_OK; }
Недостатком данного подхода является то, что все запросы QueryInterface на IMarshal будут сериализованы (преобразованы в последовательную форму); тем не менее, если IMarshal вообще не будет запрошен, то будет запрошено меньше ресурсов.
Теперь, когда мы убедились в относительной простоте использования FTM, интересно обсудить случаи, в которых FTM не годится. Конечно, те объекты, которые могут существовать только в однопотоковых апартаментах, не должны использовать FTM, так как маловероятно, что они будут ожидать одновременного обращения к ним. В то же время объекты, способные работать в апартаментах МТА, отнюдь не обязаны использовать FTM. Рассмотрим следующий класс, который использует для выполнения своих операций другие СОМ-объекты:
class Rect : public IRect { LONG m_cRef; IPoint *m_pPtTopLeft; IPoint *m_pPtBottomRight; Rect(void) : m_cRef(0) {
HRESULT hr = CoCreateInstance(CLSID_Point, 0, CLSCTX_INPROC, IID_Ipoint, (void**) &m_pPtTopLeft);
assert(SUCCEEDED (hr)); hr = CoCreateInstance(CLSID_Point, 0, CLSCTX_INPROC, IID_Ipoint, (void**)&m_pPtBottomRight);