Сообщений 6    Оценка 380        Оценить  
Система Orphus

Управление контекстами в COM

Автор: Тимофей Казаков (TK)
The RSDN Group

Источник: RSDN Magazine #6-2003
Опубликовано: 14.08.2004
Исправлено: 10.12.2016
Версия текста: 1.0
Активация COM+-компонентов
Создание и модификация контекста
Интерфейс IObjContext
SetProperty
DoCallback
Интерфейс IPolicyMaker
Интерфейс IPolicy
Использование политик
Контексты и COM+
Итог

Выход Windows 2000 дал новый толчок развитию COM – появилась возможность асинхронного вызова методов, как результат развития MTS появился COM+. И если раньше такие возможности, как транзакционная работа с ресурсами и ролевая безопасность предоставлялись в MTS средствами сторонних разработчиков (что требовало использования такой, на первый взгляд странной, функций как SafeRef), то в COM+ все эти возможности уже были интегрированы и стали предоставляться на уровне контекста.

Что такое контекст? Мы можем рассматривать контекст, как набор свойств, ассоциированных с одним или несколькими COM-объектами, и которые могут предоставлять дополнительные службы для этих объектов. У каждого COM-объекта есть ассоциированный с ним контекст, и каждый контекст ассоциирован со своим апартаментом. При этом в рамках одного апартамента может находиться несколько контекстов, в каждом из которых может существовать произвольное число COM-объектов.

В какой момент создается контекст объекта? Начнем с того, что с каждым апартаментом связан свой “контекст по умолчанию (default context)”, и изначально все объекты активируются именно в этом контексте. Кроме контекста по умолчанию можно самостоятельно создавать новые контексты и формировать их свойства, или положиться на COM – как это происходит для COM+-компонентов.

Активация COM+-компонентов

При создании COM+-компонент всегда ассоциируется с контекстом. Для этого может быть создан и инициализирован новый контекст либо использован текущий. COM+ использует в этих целях специальную регистрационную базу данных COM+. Информация о конфигурации создаваемого COM-объекта, извлеченная из этой базы, сравнивается с текущим контекстом. Если текущий контекст удовлетворяет всем требованиям создаваемого COM+-компонента, то этот компонент будет активирован в текущем контексте. В противном случае будет создан новый контекст, где уже и произойдет активация. При этом в создаваемый контекст может быть передана часть свойств из вызывающего контекста – это может быть id транзакции, activity id, либо произвольная пользовательская информация (Рисунок 1).


Рисунок 1 Передача свойств контекста.

Создание и модификация контекста

Контекст как таковой не является какой-то размытой сущностью – это такой же COM-объект, как и все остальные. Это значит, что его можно создать с использованием CoCreateIntstance и, используя стандартные интерфейсы, манипулировать его свойствами. Простейший пример создания контекста и помещения в него COM объекта выглядит следующим образом:

      class DECLSPEC_UUID("00000335-0000-0000-C000-000000000046") StdObjectContext;
  CComPtr<IDemoObject> spDemoObject;
  CComPtr<IDemoObject> spProxyObject;
  CComPtr<IObjContext> spContext;
  CComPtr<IMyProperty> spProperty;

  //  Создадим экземпляр DemoObject и “погрузим” его в контекст
  spDemoObject = new CComObject<CDemoObject>();
  spProperty = new CComObject<СMyProperty >();
  spContext.CoCreateInstance(__uuidof(StdObjectContext));
  spContext->SetProperty(__uuidof(IMyProperty), 0, spProperty);
  spContext->Freeze();

  //  Контекст готов к использованию. //  Ассоциируем spDemoObject с созданным контекстом
  CoCreateObjectInContext(spRealObject, spContext, __uuidof(IDemoObject), reinterpret_cast<void**>(&spProxyObject));

  //  Вызов MySampleMethod будет сделан через инфраструктуру контекста
  spProxyObject->MySampleMethod();

ПРИМЕЧАНИЕ

Похоже, что название функции CoCreateObjectInContext выбрано не совсем удачно. В реальности никакого создания объекта не происходит. В качестве параметров функция принимает ссылку на объект, ссылку на контекст, с которым должен быть ассоциирован исходный объект, и возвращает proxy для работы с реальным объектом уже в рамках контекста.

Интерфейс IObjContext

Основной интерфейс из тех, что поддерживает реализация кокласса StdObjectContext – это интерфейс IObjContext (Таблица 1).

Метод Описание
SetProperty Добавить в контекст новое свойство.
RemoveProperty Удалить свойство из контекста.
GetProperty Получить свойство из контекста.
EnumContextProps Перечислить свойства контекста, используя стандартный IEnumXXXX интерфейс.
Freeze Зафиксировать состояние контекста. Фиксирование состояние контекста завершает его инициализацию. После этого контекст переходит в рабочее состояние и предотвращает изменение своих свойств.
DoCallback Выполнить Callback-метод в данном контексте.
SetContextMarshaler Используется для внутренних целей и не представляет практического интереса.
GetContextMarshaler Используется для внутренних целей и не представляет практического интереса.
SetContextFlags Установить флаг контекста.
ClearContextFlags Очистить флаг контекста.
GetContextFlags Получить флаг контекста.

Рассмотрим некоторые из методов подробнее.

SetProperty

Метод SetProperty(REFGUID rpolicyId, CPFLAGS flags, IUnknown *pUnk) используется для добавления в контекст новых свойств. Параметр rpolicyId уникально идентифицирует свойство, параметр flags задает, какими параметрами будет обладать данное свойство, а параметр pUnk – это и есть само свойство. И если с rpolicyId и pUnk все более-менее понятно, то параметр CPFLAGS заслуживает особого внимания.

Параметр CPFLAGS определяет некоторые аспекты поведения добавляемого свойства, и может принимать следующие значения:

Значение Описание
CPFLAG_NONE Значение по “умолчанию”.
CPFLAG_PROPAGATE Флаг CPFLAG_PROPAGATE используется в том случае, если свойство должно распространяться и на другие контексты. Предположим, мы добавляем в контекст свойство с флагом CPFLAG_PROPAGATE. После этого мы вызываем CoCreateInstance, создающий новый COM+-объект. Что произойдет в данной ситуации? Все свойства, у которых указан данный флаг, будут переданы (для этого используется стандартный механизм маршалинга) в контекст вновь созданного COM+ объекта.
CPFLAG_ENVOY Флаг CPFLAG_ENVOY используется для создания свойств “агентов”. При передаче ссылки на объект в другой контекст envoy-свойство проследует вместе с ней, и даст возможность добавить свои сервисы, функционирующие сразу после proxy.
CPFLAG_MONITORSTUB Флаг CPFLAG_MONITORSTUB означает, что свойство будет получать уведомления о создании/удалении заглушки (stub) для COM-объекта.
CPFLAG_MONITORPROXY Флага CPFLAG_MONITORPROXY означает, что свойство будет получать уведомления о создании/удалении proxy для COM-объекта.

DoCallback

Метод DoCallback(PFNCTXCALLBACK pfnCallback, void *pParam, REFIID riid, unsigned int iMethod) позволяет выполнить в рамках контекста обратный вызов. Параметр pfnCallback – это указатель на callback-функцию, принимающую один параметр pParam. Параметры riid и iMethod служат чисто информационным целям. Их наличие связано с тем, что для обработки вызова свойствам контекста нужно знать, какой метод и через какой интерфейс вызывается. На практике метод DoCallback используется следующим образом:

HRESULT __stdcall ContextCallback(void * pvData)
{
  return S_OK;
}

spContext->DoCallback(ContextCallback, NULL, __uuidof(IUnknown), 3);

Интерфейс IPolicyMaker

За функциональность контекста отвечают его свойства. Часть свойств нового контекста может быть добавлена как при создании COM+-объекта (исходя из атрибутов зарегистрированного COM+ класса), так и в результате наследования свойств исходного контекста (например, как это происходит в случае транзакций или доменов синхронизации). И, конечно, остается возможность добавить необходимые свойства в контекст в том случае, если мы создаем его самостоятельно.

Для интеграции с инфраструктурой контекстов каждое свойство должно реализовать интерфейс IPolicyMaker. COM использует этот интерфейс для уведомления свойства о событиях, происходящих в контексте – это могут быть уведомления о создании/разрушении серверной заглушки (stub), а также запросы на создание новых “политик” (политики и их использование рассматривается чуть ниже).

Метод Описание
AddClientPoliciesToSet Добавить в контекст набор “клиентских” политик.
AddEnvoyPoliciesToSet Добавить в контекст набор “агентских” политик.
AddServerPoliciesToSet Добавить в контекст набор “серверных” политик.
Freeze Зафиксировать текущее состояние.
CreateStub Уведомление о создании stub.
DestroyStub Уведомление о разрушении stub.
CreateProxy Уведомление о создании proxy.
DestroyProxy Уведомление о разрушении proxy.

В интерфейсе IPolicyMaker наибольший интерес представляют методы AddXXXPoliciesToSet, которые позволяют добавлять в контекст различные группы политик. Но к рассмотрению методов AddXXXPoliciesToSet мы вернемся чуть ниже, а пока посмотрим, что собой представляет объект “политики”, и какие функции он может в себе нести.

Интерфейс IPolicy

Как уже упоминалось – практически все дополнительные сервисы предоставляются контекстом через его свойства. Для этого свойства предоставляют в контекст набор специальных объектов – “политик”. Объект “политика” – это обычный COM-объект, реализующий интерфейс IPolicy (Таблица 4). IPolicy позволяет контролировать процесс вызова целевого объекта, добавляя на каждом из этапов обработки (вызов/возврат из метода, переключение контекста) свои дополнительные сервисы.

Метод Описание
Call Уведомление о начале вызова метода. Вызывается до того, как вызов покинет исходный контекст.
Enter Уведомление о входе в контекст. Вызывается сразу после входа в контекст, но до того, как будет вызван реальный метод целевого объекта.
Leave Уведомление о выходе из контекста. Вызывается сразу после вызова метода целевого объекта, перед выходом из серверного контекста.
Return Уведомление о возврате из метода. Вызывается при возврате в исходный контекст.
CallGetSizeCallFillBuffer Данная пара методов является полным аналогом метода Call, за тем исключением, что их использование позволяет передать вместе с вызовом дополнительную информацию.
EnterWithBuffer Метод EnterWithBuffer может использоваться как замена использованию метода Enter в тех случаях, когда необходимо обработать информацию, переданную методами CallFillBuffer и CallGetSize.
LeaveGetSizeLeaveFillBuffer Методы LeaveGetSize и LeaveFillBuffer представляют собой серверный эквивалент для CallGetSize и CallFillBuffer.
ReturnWithBuffer Метод ReturnWithBuffer служит для получения информации, сохраненной методами LeaveGetSize и LeaveFillBuffer.
AddRefPolicyReleasePolicy Использование методов AddRefPolicy и ReleasePolicy позволяет отследить моменты, когда политика реально используется.

Каждая политика может обрабатывать один из аспектов вызова. Какой именно – определяется на этапе создания политик в методах AddXXXPoliciesToSet.

      //  Методы AddServerPoliciesToSet, AddClientPoliciesToSet и 
      //  AddEnvoyPoliciesToSet имеют одинаковый вид. Мы рассмотрим только один.
HRESULT AddServerPoliciesToSet(IPolicySet *pPS, 
    IContext *pClientContext, 
    IContext *pServerContext)
{
  //  Все политики добавляются в специальный объект – IPolicySet. 
  pPS->AddPolicy(CONTEXTEVENT_ENTERWITHBUFFER, 
    __uuidof(ComputerNamePolicy), 
    CComPtr<IPolicy>(new CComObject<CComputerNamePolicy>()));

  pPS->AddPolicy(CONTEXTEVENT_ENTERWITHBUFFER, 
    __uuidof(TransactionIDPolicy), 
    CComPtr<IPolicy>(new CComObject<CTransactionIDPolicy>()));

  pPS->AddPolicy(CONTEXTEVENT_ENTERWITHBUFFER, 
    __uuidof(DatabaseNamePolicy), 
    CComPtr<IPolicy>(new CComObject<CDatabaseNamePolicy>()));

  pPS->AddPolicy(CONTEXTEVENT_ENTER, 
    __uuidof(SynchronizationPolicy), 
    CComPtr<IPolicy>(new CComObject<CSynchronizationPolicy >()));


  return S_OK;
}

Для каждой добавляемой политики указывается, какое событие она будет обрабатывать, и уникальный идентификатор, который будет идентифицировать политики при передаче данных из одного контекста в другой.

      // Возможные события, которые могут обрабатываться политиками контекста.
      enum tagCONTEXTEVENT
{  CONTEXTEVENT_NONE  = 0,
  CONTEXTEVENT_CALL  = 0x1,
  CONTEXTEVENT_ENTER  = 0x2,
  CONTEXTEVENT_LEAVE  = 0x4,
  CONTEXTEVENT_RETURN  = 0x8,
  CONTEXTEVENT_CALLFILLBUFFER  = 0x10,
  CONTEXTEVENT_ENTERWITHBUFFER  = 0x20,
  CONTEXTEVENT_LEAVEFILLBUFFER  = 0x40,
  CONTEXTEVENT_RETURNWITHBUFFER  = 0x80,
  CONTEXTEVENT_BEGINCALL  = 0x100,
  CONTEXTEVENT_BEGINENTER  = 0x200,
  CONTEXTEVENT_BEGINLEAVE  = 0x400,
  CONTEXTEVENT_BEGINRETURN  = 0x800,
  CONTEXTEVENT_FINISHCALL  = 0x1000,
  CONTEXTEVENT_FINISHENTER  = 0x2000,
  CONTEXTEVENT_FINISHLEAVE  = 0x4000,
  CONTEXTEVENT_FINISHRETURN  = 0x8000,
  CONTEXTEVENT_BEGINCALLFILLBUFFER  = 0x10000,
  CONTEXTEVENT_BEGINENTERWITHBUFFER  = 0x20000,
  CONTEXTEVENT_FINISHLEAVEFILLBUFFER  = 0x40000,
  CONTEXTEVENT_FINISHRETURNWITHBUFFER  = 0x80000,
  CONTEXTEVENT_LEAVEEXCEPTION  = 0x100000,
  CONTEXTEVENT_LEAVEEXCEPTIONFILLBUFFER  = 0x200000,
  CONTEXTEVENT_RETURNEXCEPTION  = 0x400000,
  CONTEXTEVENT_RETURNEXCEPTIONWITHBUFFER  = 0x800000,
  CONTEXTEVENT_ADDREFPOLICY  = 0x10000000,
  CONTEXTEVENT_RELEASEPOLICY  = 0x20000000
};

В общем случае процесс вызова будет проходить по следующей цепочке (Рисунок 2): клиентские политики > переключение контекста > серверные политики > вызов метода > серверные политики > переключение контекста > клиентские политики.


Рисунок 2. Процесс вызова COM объекта.

Использование политик

Использование политик контекста мы рассмотрим на простейшем примере – создадим два контекста, в один добавим собственное свойство, а другой оставим без изменений.

      // Два контекста для "опытов"
CComPtr<IObjContext> CreateAdvContext() 
{
  CComPtr<IObjContext> spContext;
  CComPtr<IPolicyMaker> spProperty;
  
  spProperty = new CComPtr<CMyProperty>();
  spContext.CoCreateInstance(__uuidof(StdObjectContext));
  
  // Добавляем свойство и фиксируем контекст
  spContext->SetProperty(__uuidof(CMyProperty), CPFLAG_ENVOY, spProperty);
  spContext->Freeze();

  return spContext;
}

CComPtr<IObjContext> CreateEmptyContext()
{
  CComPtr<IObjContext> spContext;

  spContext.CoCreateInstance(__uuidof(StdObjectContext));
  spContext->Freeze();

  return spContext;  
}

// Данный Callback вызов делается в контексте, в который мы добавили // дополнительное свойство.
HRESULT __stdcall advContextCallback(void * pData)
{
  return S_OK;
}

// Данный Callback вызов делается в “пустом” контексте
HRESULT __stdcall emptyContextCallback(void * pData)
{
  CComGITPtr<IObjContext> spAdvContext(reinterpret_cast<DWORD>(pData));
  CComPtr<IObjContext> spContext;

  spAdvContext.CopyTo(spContext);

  // Переходим в контекст, в который добавлено свойство CMyProperty// (контекст может находится в другом апартаменте).  
  spContext->DoCallback(advContextCallback, NULL, IID_IUnknown, 3);

  return S_OK;
}

int main(int argc, char * argv[]) 
{
  CoInit coinit(COINIT_APARTMENTTHREADED);

  CComGITPtr<IObjContext> spAdvContext = CreateAdvContext();
  CComGITPtr<IObjContext> spEmptyContext = CreateEmptyContext();

  // Делаем обратный вызов в пустой контекст.
  spEmptyContext->DoCallback(emptyContextCallback, 
    reinterpret_cast<void*>(spAdvContext.GetCookie()), 
    IID_IUnknown, 3);  
  
  return 0;
}

Что будет происходить в процессе вызова Callback-методов? Вызов метода emptyContextCallback ничем примечательным выделяться не будет. Контекст, в котором он вызывается, пуст, и никакие свойства задействованы не будут – просто за их отсутствием. Самое интересное будет происходить внутри метода emptyContextCallback, а именно – при осуществлении вызова в контекст с добавленным свойством (CMyProperty).

      // Примерная реализация свойства контекста. Все незначащие методы опущены.
      class CMyProperty : 
  public CComObjectRootEx<CComMultiThreadModel>,
  public CComCoClass<CMyProperty, &CLSID_CMyProperty>,
  publicIPolicyMaker
{
public:
  BEGIN_COM_MAP(CMyProperty)
    COM_INTERFACE_ENTRY(IPolicyMaker)
  END_COM_MAP()

public:
  STDMETHOD(AddClientPoliciesToSet)(IPolicySet *pPS, 
    IContext *pClientContext, 
    IContext *pServerContext) 
  {
    // Добавляем политику  
    pPS->AddPolicy(CONTEXTEVENT_CALLFILLBUFFER, 
      CLSID_ComputerNamePolicy, 
      new CComObject<CMyPolicy>());

    return S_OK;
  }
     
  // “Агентские” политики, так же, как и клиентские,// вызываются в исходном контексте. Они обычно используются для  // сбора информации, которая будет передана вместе с вызовом в // серверный контекст. За сбор этой информации отвечают Call-методы// политик.
  STDMETHOD(AddEnvoyPoliciesToSet)(IPolicySet *pPS, 
    IContext *pClientContext, 
    IContext *pServerContext) 
  {    
    pPS->AddPolicy(CONTEXTEVENT_CALLFILLBUFFER, 
      CLSID_ComputerNamePolicy, 
      new CComObject<CMyPolicy>());

    return S_OK;
  }
        
  //  //  Серверные политики вызываются при входе в контекст. //  Поэтому нас будет интересовать Enter-методы политик//
  STDMETHOD(AddServerPoliciesToSet)(IPolicySet *pPS, 
    IContext *pClientContext, 
    IContext *pServerContext) 
  {
    pPS->AddPolicy(CONTEXTEVENT_ENTERWITHBUFFER, 
      CLSID_ComputerNamePolicy, 
      new CComObject<CMyPolicy>());

    return S_OK;
  }        
};

//  Реализация основных методов класса “политики”.class CMyPolicy : 
  public CComObjectRootEx<CComMultiThreadModel>,
  public CComCoClass<CMyPolicy, &CLSID_CMyPolicy>,
  publicIPolicy
{
public:
  BEGIN_COM_MAP(CMyPolicy)
    COM_INTERFACE_ENTRY(IPolicy)
  END_COM_MAP()  
public:

  STDMETHOD(CallGetSize)(ICall  *pCall, ULONG  *pcb) 
  {
    *pcb = 0;
    return S_OK;
  }

  //  Метод CallFillBuffer отвечает за сбор информации из клиентского //  контекста.
  STDMETHOD(CallFillBuffer)(ICall  *pCall, void  *pvBuf, ULONG  *pcb) 
  {
    return S_OK;
  }

  //  Метод EnterWithBuffer выполняется в серверном контексте и принимает//  блок данных, сохраненный политикой с тем же GUID в методе CallFillBuffer
  STDMETHOD(EnterWithBuffer)(ICall  *pCall, void  *pvBuf, ULONG cb) 
  {
    return S_OK;
  }
};

Реализованное свойство через добавленные политики (CMyPolicy) получает доступ к процессу вызова методов COM-объектов (рисунок 3). Разберем подробнее эту диаграмму. Непосредственно серверный контекст представляют два объекта – CMyProperty и ServerContext (функция CreateAdvContext). Исходный контекст, который мы создаем в CreateEmptyContext – это ClientContext, и он служит отправной точкой (emptyContextCallback), из которой начинается процесс вызова в серверный контекст.

Как происходит создание политик? Первое, что создается в исходном контексте – “агентские” политики.

СОВЕТ

В каких случаях используются агентские политики? Данный вид политик позволяет серверному контексту получить своего представителя на вызывающей стороне. Созданные “агентские” политики получают возможность собрать в исходном контексте необходимую информацию и подготовить ее для передачи серверным политикам.

После того, как вызов будет обработан каждой «агентской» политикой на стороне клиента, произойдет переключение контекста, и вызов перейдет на серверную сторону. Там будет создан набор серверных политик (если их еще нет). На стороне сервера вызов пройдет через серверные политики, и лишь только после этого будет вызван метод целевого объекта (или функция, если это callback-вызов). Возврат из серверного контекста в клиентский (на диаграмме это не отражалось) пройдет в обратном порядке – серверные политики, потом клиентские. Здесь стоит отдельно отметить, что на этапе возврата политики уже создаваться не будут – все они были созданы на предыдущих этапах.


Рисунок 3. Межконтексный вызов.

Кроме “агентских” и серверных политик, есть еще и клиентские политики, которые у нас остались незадействованными. Группа клиентских политик позволяет контролировать ситуации, когда вызов покидает текущий контекст, – это могут быть и вызовы, которые осуществляются между контекстами, а также вызовы, которые уходят в другой процесс или апартамент (в общем случае это все межконтекстные вызовы, так как у каждого апартамента есть свой default-контекст).

Подведем некоторый итог всему вышесказанному. Все межконтекстные вызовы контролируются политиками. В свою очередь, политики делятся на две группы – политики, работающие в исходном, клиентском контексте, и политики, которые функционируют в контексте сервера. На стороне клиента вызов последовательно обрабатывается сначала “агентскими”, а потом и клиентскими политиками. На стороне сервера – все ограничивается обработкой в серверных политиках.

Контексты и COM+

Предыдущий пример показывал, как взаимодействуют два уже созданных контекста. Однако возможности взаимодействия контекстов на этом не заканчивается. Кроме использования фиксированного набора свойств и возможности внедрения “агентов” в вызывающий контекст есть еще одна (наверное, самая интересная возможность) – это наследование свойств контекста. Но перед тем, как перейти к рассмотрению механизма наследования, вернемся к тому, как создаются контексты. Для начала – с каждым апартаментом связан контекст “по умолчанию” и большинство не сконфигурированных COM-объектов работают именно в нем. Кроме default-контекста, мы можем для своих нужд создать произвольное число контекстов и заполнить их необходимыми свойствами (это рассматривалось в предыдущем примере). И напоследок – у нас остаются специальным образом сконфигурированные COM+-компоненты. Они могут быть созданы не только в текущем контексте. В зависимости от заявленных требований для такого компонента может быть создан индивидуальный контекст.

В каких случаях происходит создание нового контекста? Допустим, у нас есть COM+-компонент, для которого задано значение Required атрибута TransactionOption. Что происходит при создании такого компонента? Первое, что происходит, – проверяются свойства исходного контекста: потоковая модель, текущая транзакция и т.п. Если все свойства совпали – никаких проблем. Компонент активируется в текущем контексте и получает доступ ко всем его свойствам. Но, предположим, что не совпала потоковая модель. В такой ситуации у нас будет создан апартамент нужного типа (при необходимости), в этом апартаменте будет создан новый контекст, и в этот контекст будет передано Transaction-свойство из исходного контекста.

Исходя из этого, можно модифицировать предыдущий пример так, чтобы свойство CMyProperty передавалось в серверный COM+-компонент. Первое, что нужно сделать – это указать флаг CPFLAG_PROPAGATE. Этот флаг будет служить указанием, что данное свойство нужно транслировать во все вновь создаваемые контексты.

spContext->SetProperty(__uuidof(CMyProperty), CPFLAG_ENVOY | CPFLAG_PROPAGATE, spProperty);

Кроме этого, необходимо обеспечить механизм передачи состояния свойства из исходного процесс в процесс, в котором будет находиться создаваемый COM+-компонент. Для этого нужно добавить к классу CMyProperty поддержку интерфейса IMarshal. И, если все правильно сделать, во вновь созданный контекст будет добавлено нужное свойство. Сам процесс передачи свойства будет выглядеть, как показано на рисунке 4.


Рисунок 4. Передача свойства из одного контекста в другой.

Наверное, теперь пришло время на практике разобрать реализацию одного из свойств контекста. В качестве примера расширим стандартные возможности активации COM+-объектов. Думаю, что нет особой нужды рассказывать об интерфейсе IObjectConstruct. В COM+ этот интерфейс используется для передачи дополнительного параметра при создании экземпляра кокласса и принимает обычный строковый параметр. Этот параметр берется из настроек кокласса в COM+-каталоге. Данную реализацию нельзя назвать достаточно гибкой. Мы не можем сформировать данную строку на этапе выполнения – если мы хотим внести какие-либо свои изменения, то их нужно делать через настройки данного кокласса в COM+. Итак, напишем простейшее свойство, которое избавит нас от этого недостатка и позволит нам брать необходимые данные из клиентского приложения вместе с PROPAGATE-свойством. Что для этого потребуется? Наше свойство должно будет реализовать два интерфейса: IPolicyMaker и IMarshal.

      //
      //  Это свойство реализует альтернативный вариант вызова  
      //  интерфейса IObjectConstruct::Construct
      //
      class ATL_NO_VTABLE CConstructionProperty : 
  public CComObjectRootEx<CComMultiThreadModel>,
  public CComCoClass<CConstructionProperty, &CLSID_ConstructionProperty>,
  public IPolicyMaker,
  public IMarshalEnvoy,
  public IMarshal
{
  // Реализация интерфейса IMarshal

  STDMETHOD(GetUnmarshalClass)(REFIID riid, void *pv, DWORD dwDestContext, 
    void *pvDestContext, DWORD mshlflags, CLSID *pCid)
  {
    // Данное свойство мы будем передавать в другие процессы // по значению (by value)
    *pCid = GetObjectCLSID();
  return S_OK;
  }

  STDMETHOD(MarshalInterface)(IStream *pStm, 
  REFIID riid, void *pv, DWORD dwDestContext, 
  void *pvDestContext, DWORD mshlflags) 
  {
    // Здесь необходимо добавить код, который сохранит все необходимые для// передачи данные в поток pStmreturn S_OK;
  }
    
  STDMETHOD(UnmarshalInterface)(IStream *pStm, REFIID riid, void **ppv) 
  {
    //  Здесь нужно добавить код для восстановления из потока pStm полученной //  информации. А учитывая, что данный объект передается по значению, то  //  как результат Unmarshal нам достаточно вернуть свой интерфейс.returnthis->QueryInterface(riid, ppv);
  }

  //  Реализация интерфейса IPolicyMaker

  STDMETHOD(CreateStub)(IComObjIdentity *pID) 
  {
  if (pID->IsServer() != FALSE && pID->IsDeactivated() == FALSE)
    return S_OK; 

    CComPtr<IUnknown> spIdentity;
  //  Метод IComObjIdentity::GetIdentity возвращает ссылку на “сырой” объект, //  без proxy-оболочкиif (SUCCEEDED(pID->GetIdentity(&spIdentity)))
  {
    CComPtr<IObjectConstruct> spConstruct;
    if (SUCCEEDED(spIdentity->QueryInterface(&spConstruct)))
    {
      //  Здесь вместо NULL следует передать свою реализацию интерфнйса//  IObjectConstructString (или интерфейса который его заменит)
      spConstruct->Construct(NULL);
    }
  }    
  return S_OK;  
  }  
};

К этому примеру стоит сделать одно небольшое замечание – в случае использования JIT-активации создание stub произойдет только однажды, в то время как сам объект может пересоздаваться произвольное число раз. Эту проблему можно решить несколькими путями – через серверную политику, или добавив к реализованному свойству поддержку интерфейса IActivationEvents. Думаю, что при желании реализовать тот или иной способ не составит особого труда.

    MIDL_INTERFACE("788EA812-87B1-11D1-BBA6-00C04FC2FA5F")
    IActivationEvents : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE NewObject( 
            /* [in] */ IUnknown __RPC_FAR * pIdentity,
            /* [in] */ IUnknown __RPC_FAR * pComClassInfo) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE Activate( void) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE Deactivate( void) = 0;
        
    };

Итог

Подводя некоторый итог рассказанному в статье, хочется сказать, что это только малая часть из того, что реализовано в COM для поддержки контекстов. За “бортом” остались возможности расширения самого контекста через агрегацию в него дополнительных сервисов (примерно так, как делается в COM+ для поддержки интерфейса IObjectContext). Остался не упомянутым механизм использования “перехватчиков” (ICallInterceptor), который лежит в основе реализации контекстов. Если окажется, что это страшно интересует вас, дорогие читатели, я расскажу об этом в одной из следующих статей.

Несмотря на то, что COM появился достаточно давно, нельзя сказать, что использование контекстов хоть как-то широко освещалось. Между тем, есть надежда, что их использование позволит дать новые возможности уже разработанным приложениям.


Эта статья опубликована в журнале RSDN Magazine #6-2003. Информацию о журнале можно найти здесь
    Сообщений 6    Оценка 380        Оценить