Сущность технологии СОМ. Библиотека программиста - Дональд Бокс
Шрифт:
Интервал:
Закладка:
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.
Похоже на то, что по меньшей мере один из методов Rect использует в своей работе два интерфейсных указателя в качестве элементов данных:
STDMETHODIMP Rect::get_Area(long *pn) {
long top, left, bottom, right;
HRESULT hr = m_pPtTopLeft->GetCoords(&left, &top);
assert(SUCCEEDED(hr));
hr = m_pPtBottomRight->GetCoords(&right, &bottom);
assert (SUCCEEDED (hr));
*pn = (right – left) * (bottom – top);
return S_OK;
}
Если бы класс Rect должен был использовать FTM, тогда можно было бы вызывать этот метод из апартаментов, отличных от того апартамента, который осуществлял начальные вызовы CoCreateInstance. К сожалению, это заставило бы метод get_Area нарушить правила СОМ, поскольку два элемента данных – интерфейсные указатели – являются легальными только в исходном апартаменте. Если бы класс Point также использовал FTM, то формально это не было бы проблемой. Тем не менее, в общем случае клиенты (такие, так класс Rect), не должны делать допущений относительно этой специфической исключительно для реализаций детали. Фактически, если объекты Point не используют FTM и окажутся созданными в другом апартаменте из-за несовместимости с ThreadingModel, то в этом случае объект Rect содержал бы указатели на заместители. Известно, что заместители четко следуют правилам СОМ и послушно возвращают RPC_E_WRONG_THREAD в тех случаях, когда к ним обращаются из недопустимого апартамента.
Это оставляет разработчику Rect выбор между двумя возможностями. Одна из них – не использовать FTM и просто принять к сведению, что когда клиенты передают объектные ссылки Rect между апартаментами, то для обращения к экземплярам класса Rect будет использоваться ORPC. Это действительно является простейшим решением, так как оно не добавляет никакого дополнительного кода и будет работать, не требуя умственных усилий. Другая возможность – не содержать исходные интерфейсные указатели как элементы данных, а вместо этого держать в качестве элементов данных некую маршалированную форму интерфейсного указателя. Именно для этого и предназначена глобальная интерфейсная таблица (Global Interface Table – GIT). Для реализации данного подхода в классе Rect следовало бы иметь в качестве элементов данных не исходные интерфейсные указатели, а «закладку» (cookies) DWORD:
class SafeRect : public IRect {
LONG m_cRef;
// СОМ reference count
// счетчик ссылок СОМ IUnknown *m_pUnkFTM;
// cache for FTM lazy aggregate
// кэш для отложенного агрегирования FTM
DWORD m_dwTopLeft;
// GIT cookie for top/left
// закладка GIT для верхнего/левого
DWORD m_dwBottomRight;
// GIT cookie for bottom/right
// закладка GIT для нижнего/правого
Разработчик по-прежнему создает два экземпляра Point, но вместо хранения исходных указателей регистрирует интерфейсные указатели с помощью глобальной таблицы GIT:
SafeRect::SafeRect(void) : m_cRef(0), m_pUnkFTM(0) {
// assume ptr to GIT is initialized elsewhere
// допустим, что указатель на GIT инициализирован
// где-нибудь в другом месте
extern IGIobalInterfaceTable *g_pGIT;
assert(g_pGIT != 0);
IPoint *pPoint = 0;
// create instance of class Point
// создаем экземпляр класса Point HRESULT
hr = CoCreateInstance(CLSID_Point, 0, CLSCTX_INPROC, IID_Ipoint, (void**)&pPoint);
assert (SUCCEEDED (hr));
// register interface pointer in GIT
// регистрируем интерфейсный указатель в GIT
hr = g_pGIT->RegisterInterfaceInGlobal(pPoint, IID_Ipoint, &m_dwTopLeft);
assert(SUCCEEDED(hr));
pPoint->Release();
// reference is now held in GIT
// ссылка теперь содержится в GIT
// create instance of class Point
// создаем экземпляр класса Point
hr = CoCreateInstance(CLSID_Point, 0, CLSCTX_INPROC, IID_Ipoint, (void**)&pPoint);
assert(SUCCEEDED(hr));
// register interface pointer in GIT
// регистрируем интерфейсный указатель в GIT
hr = g_pGIT->RegisterInterfaceInGlobal(pPoint, IID_Ipoint, &m_dwBottomRight);
assert(SUCCEEDED(hr)); pPoint->Release();
// reference is now held in GIT
// ссылка теперь содержится в GIT
}
Отметим, что все то время, пока интерфейсный указатель зарегистрирован в GIT, пользователь интерфейсного указателя не должен хранить никаких дополнительных ссылок.
Поскольку класс был преобразован для использования GIT вместо исходных интерфейсных указателей, он должен демаршалировать новый заместитель в каждом вызове метода, которому требуется доступ к зарегистрированным интерфейсам:
STDMETHODIMP SafeRect::get_Area(long *pn) {
extern IGlobalInterfaceTable *g_pGIT; assert(g_pGIT != 0);
// unmarshal the two interface pointers from the GIT
// демаршалируем дВа интерфейсных указателя из GIT
IPoint *ptl = 0, *pbr = 0;
HRESULT hr = g_pGIT->GetInterfaceFromGlobal(m_dwPtTopLeft, IID_Ipoint, (void**)&ptl);
assert (SUCCEEDED(hr));
hr = g_pGIT->GetInterfaceFromGlobal(m_dwPtBottomRight, IID_Ipoint, (void**)&pbr);
// use temp ptrs to implement method
// дпя реализации метода используем временные указатели
long top, left, bottom, right;
hr = ptl->GetCoords(&left, &top);
assert (SUCCEEDED(hr));
hr = pbr->GetCoords(&right, &bottom);
assert (SUCCEEDED (hr));
*pn = (right – left) * (bottom – top);
// release temp ptrs. // освобождаем временные указатели
ptl->Release();
pbr->Release();
return S_OK;
}
Поскольку реализация SafeRect использует FTM, то нецелесообразно пытаться сохранить немаршалированные интерфейсные указатели между вызовами метода, так как неизвестно, произойдет ли следующий вызов метода в том же самом апартаменте.