Сущность технологии СОМ. Библиотека программиста
Шрифт:
Поскольку только что описанное поведение является полезным для большого класса объектов, в СОМ предусмотрена агрегируемая реализация IMarshal, выполняющая в точности то, что было описано. Эта реализация называется маршалером свободной поточной обработки (FreeThreaded Marshaler – FTM) и может быть осуществлена с помощью вызова API-функции CoCreateFreeThreadedMarshaler:
HRESULT CoCreateFreeThreadedMarshaler( [in] IUnknown *pUnkOuter, [out] IUnknown **ppUnkInner);
Класс,
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);
assert (SUCCEEDED(hr));
}
;
;
;
}
Пусть класс Rect является внутрипроцессным и помечен как ThreadingModel = «Both». Разработчик данного Rect– объекта всегда будет выполняться в апартаменте потока, вызывающего CoCreateInstance (CLSID_Rect). Это означает, что два вызова CoCreateInstance (CLSID_Point) будут также выполняться в апартаменте клиента. Правила же СОМ гласят, что элементы данных m_pPtTopLeft и m_pPtBottomRight могут быть доступны только из того апартамента, который выполняет вызовы CoCreateInstance.