// получаем локальный интерфейсный указатель из GIT
HRESULT Localize(Itf **ppItf) const {
assert(g_pGIT != 0 && m_dwCookie != 0);
return g_pGIT->GetInteгfaceFromGlobal(m_dwCookie, *piid, (void**)ppItf);
}
// convenience methods
// методы для удобства
bool IsOK(void) const { return m_dwCookie != 0; }
DWORD GetCookie(void) const { return m_dwCookie; }
};
#define GIP(Itf) GlobalInterfacePointer<Itf, &IID_##Itf>
Имея данное определение класса и макрос, класс SafeRect теперь вместо исходных DWORD сохраняет GlobalInterfacePointers:
class SafeRect : public IRect {
LONG m_cRef:
// СОM reference count
// счетчик ссылок СОМ
IUnknown *m_pUnkFTM;
// cache for FTM lazy aggregate
// кэш дпя отложенного агрегирования FTM
GIP(IPoint) m_gipTopLeft;
// GIT cookie – top/left
// «закладка» GIT для верхнего/левого элемента
GIP(IPoint) m_gipBottomRight;
// GIT cookie – bottom/right
// «закладка» GIT для нижнего/правого элемента
:
:
:
}
Для инициализации элемента GlobalInterfacePointer разработчик (который выполняется в апартаменте объекта) просто регистрирует обрабатываемые указатели, вызывая метод Globalize на каждый GlobalInterfacePointer:
SafeRect::SafeRect(void) : m_cRef (0), m_pUnkFTM(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 = m_gipTopLeft.Globalize(pPoint);
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_Iроint, (void**) &рРоint);
assert(SUCCEEDED(hr));
// register interface pointer in GIT
// регистрируем интерфейсный указатель в GIT
hr = m_gipBottomRight.Globalize(pPoint);
assert (SUCCEEDED (hr));
pPoint->Release();
// reference is now held in GIT
// теперь ссылка хранится в GIT
}
Те методы, которым нужен доступ к глобализованным указателям, могут импортировать локальную копию посредством метода Localize из GlobalInterfaсePointer:
STDMETHODIMP SafeRect::get_Top(long *pVal) {
IPoint *pPoint = 0;
// local imported pointer
// локальный импортированный указатель
HRESULT hr = m_gipTopLeft.Localize(&pPoint);
if (SUCCEEDED(hr)){
long x;
hr = pPoint->get_Coords(&x, pVal);
pPoint->Release(); }
return hr;
}
Отметим, что в силу применения маршалера свободной поточной обработки (FreeThreaded Marshaler) исходный интерфейсный указатель не может быть кэширован, а должен импортироваться при каждом вызове метода, чтобы предотвратить попытку доступа из неверного апартамента.
Предыдущий фрагмент кода может быть автоматизирован еще больше. Поскольку большинство вызовов методов в классе GlobalInterfacePointer должны будут локализовать временный указатель в самом вызове метода, то приводимый ниже класс автоматизирует импорт временного указателя и его последующее освобождение, что очень напоминает интеллектуальный указатель (smart pointer):
template <class Itf, const IID* piid> class LocalInterfacePointer {
Itf *m_pItf;
// temp imported pointer
// временный импортированный указатель
// prevent misuse
// предотвращаем неверное использование
LocalInterfacePointer(const LocalInterfacePointer&);
operator = (const LocalInterfacePointer&);
public:
LocalInterfacePointer(const GlobalInterfacePointer<Itf, piid>& rhs, HRESULT& hr) { hr = rhs.Loca1ize(&m_pItf) ; }
LocalInterfacePointer(DWORD dwCookie, HRESULT& hr) { assert(g_pGIT != 0);
hr = g_pGIT->GetInterfaceFromGlobal(dwCookie, *piid, (void**)&m_pItf); }
~LocalInterfacePointer(void) { if (m_pItf) m_pItf->Release(); }
class SafeItf : public Itf { STDMETHOD_(ULONG, AddRef) (void) = 0;
// hide
// скрытый STDMETHOD_(ULONG, Release)(void) = 0;
// hide
// скрытый
};
SafeItf *GetInterface(void) const { return (SafeItf*) m_pItf; }
SafeItf *operator ->(void) const { assert(m_pItf != 0);
return GetInterface();
}
};
#def1ne LIP(Itf) LocalInterfacePointer<Itf, &IID_##Itf>
С получением этого второго класса C++ обработка импортированных указателей становится намного проще:
STDMETHODIMP SafeRect::get_Area(long *pn) {
long top, left, bottom, right;
HRESULT hr, hr2;
// import pointers
// импортируем указатели
LIP(IPoint) lipTopLeft(m_gipTopLeft, hr);
LIP(IPoint) lipBottomRight(m_gipBottomRight, hr2);
assert(SUCCEEDED(hr) && SUCCEEDED(hr2));
// use temp tocal pointers
// используем временные локальные указатели
hr = lipTopLeft->GetCoords(&left, &top);
hr2 = lipBottomRight->GetCoords(&right, &bottom);
assert(SUCCEEDED(hr) && SUCCEEDED(hr2));
*pn = (right – left) * (bottom – top); return S_OK;
// LocalInterfacePointer auto-releases temp ptrs.
// LocalInterfacePointer сам освобождает
// временные указатели
}
Макросы GIP и LIP делают совместное использование GIT и FTM намного менее громоздким. До появления GIT использование FTM в классе с интерфейсными указателями было значительно более трудным, чем теперь обеспечивает любой из кодов, приведенных в данном разделе.
Где мы находимся?
В данной главе была описана абстракция апартаментов как логическое группирование объектов, которые подчиняются правилам параллелизма и реентерабельности. Процессы имеют один или более апартаментов. Потоки выполняются в ровно одном апартаменте, а для реализации межапартаментных связей СОМ поддерживает маршалинг объектных ссылок через границы апартаментов. Заместитель является локальным представителем объекта, постоянно находящимся в другом апартаменте. Стандартные заместители для передачи запросов методов с удаленного объекта используют ORPC. Специальные заместители имеют полную свободу для обеспечения корректной семантики. Апартамент является фундаментальной абстракцией, которая используется во всей архитектуре удаленного доступа модели СОМ.
Глава 6. Приложения
int process_id == fork();
if (process_id == 0)
exec(«…/bin/serverd»);
В предыдущей главе были представлены основы апартаментов COM и проиллюстрирована COM-архитектура удаленного доступа с изрядным количеством деталей. Были исследованы правила управления ссылками на объекты COM в условиях многопоточной среды, а также методика реализации классов и объектов COM, работающих в потоках. В этой главе будут рассматриваться проблемы, возникающие при управлении процессами и приложениями при использовании COM. Основное внимание будет сосредоточено на том, как апартаменты соотносятся с локализацией ошибок, доверительными отношениями и контекстом защиты.
Подводные камни внутрипроцессной активации
Итак, серверы COM были ранее представлены как внутрипроцессные модули кода, загружаемые в активизирующий их процесс с целью создания объектов и выполнения их методов. Для значительного класса объектов это является разумной стратегией развертывания. Эта стратегия, однако, не лишена недостатков. Одним из подводных камней при запуске объекта в клиентском процессе является отсутствие изоляции ошибок. Если объект вызывает нарушение условий доступа или другую фатальную ошибку во время исполнения, то клиентский процесс завершится вместе с объектом. Более того, если программа клиента вызовет какую-либо ошибку, то все объекты, созданные в его адресном пространстве, будут немедленно уничтожены без предупреждения. Эта проблема также относится к тем клиентам, которые решат осуществить нормальный выход, например, когда конечный пользователь закрывает одно из приложений. Когда клиентский процесс завершается, любые объекты, созданные в адресном пространстве клиента, будут уничтожены, даже если внешние клиенты вне процесса хранят легальные импортированные ссылки. Очевидно, что если клиентский процесс прекратится, то при активизации внутри процесса жизнь объекта может быть прервана преждевременно.