Сущность технологии СОМ. Библиотека программиста - Дональд Бокс
- Дата:20.06.2024
- Категория: Компьютеры и Интернет / Программирование
- Название: Сущность технологии СОМ. Библиотека программиста
- Автор: Дональд Бокс
- Просмотров:3
- Комментариев:0
Шрифт:
Интервал:
Закладка:
Интерфейсы на базе IDispatch (часто называемые dispinterface – диспинтерфейс, или диспетчерский интерфейс ) логически эквивалентны обычному интерфейсу СОМ. Основное различие состоит в методах вызова на практике логических операций интерфейса. В случае обычного интерфейса СОМ вызовы методов основываются на статике, на априорном знании сигнатуры методов интерфейса. В случае диспинтерфейса вызовы методов основаны на текстовых представлениях ожидаемой сигнатуры вызовов методов. Если вызывающая программа правильно угадывает сигнатуру метода, то вызов может быть правильно диспетчеризован. Если же вызывающая программа неправильно угадывает сигнатуру метода, то диспетчеризовать вызов, возможно, не удастся. Если для параметров метода используются неверные типы данных, то преобразование их в нужные является делом объекта (если это вообще возможно).
Простейший способ выразить диспинтерфейс на IDL – это использовать ключевое слово dispinterface:
[uuid(75DA6450-DD0F-11d0-8C58-0880C73925BA)] dispinterface DPrimeManager {
properties: [id(1), readonly] long MinPrimeOnMachine;
[id(2)] long MinPrime;
methods: [id(3)] long GetNextPrime([in] long n);
}
Этот синтаксис вполне читабелен; однако он предполагает, что вызывающая программа будет всегда обращаться к свойствам и методам объекта через IDispatch. История показала, что по мере развития программных сред этапа разработки и выполнения они часто становятся способными использовать обычные интерфейсы СОМ. Для обеспечения того, чтобы обращение к диспинтерфейсу было успешным и в будущих средах подготовки сценариев, как правило, лучше моделировать интерфейс как двойственный, или дуальный (dual interface).
Двойственные интерфейсы являются обычными интерфейсами СОМ, наследующими от IDispatch. Поскольку IDispatch является базовым интерфейсом, то он абсолютно совместим с полностью интерпретируемыми клиентами сценариев. В то же время этот интерфейс совместим вверх со средами, которые могут непосредственно связываться со статически определенным интерфейсом СОМ. Ниже приведено IDL-определение для двойственного варианта интерфейса DPrimeManager:
[object, dual, uuid(75DA6450-DD0F-11d0-8C58-0080C73925BA)] interface DIPrimeManager : IDispatch {
[id(1), propget]
HRESULT MinPrimeOnMachine( [out, retval] long *pval); [id(2), propput]
HRESULT MinPrime([in] longval);
[id(2), propget] HRESULT MinPrime([out, retval] long *pval);
[id(3)] long GetNextPrime([in] long n);
}
Заметим, что этот интерфейс наследует IDispatch, а не IUnknown. Также отметим, что данный интерфейс имеет атрибут [dual] . Этот атрибут заставляет сгенерированную библиотеку типов включить в себя диспетчерский вариант интерфейса, который совместим со средами, не поддерживающими двойственные интерфейсы. Атрибут [dual] относится к категории атрибутов [oleautomation] и также заставляет сгенерированную библиотеку типов добавлять ключи реестра для универсального маршалера во время выполнения RegisterTypeLib.
Если интерфейс определен как двойственный, то реализация методов IDispatch является тривиальной. Дело в том, что синтаксический анализатор библиотеки типов реализует два из четырех методов IDispatch . Если двойственный интерфейс был определен заранее, объекту необходимо на этапе инициализации просто загрузить библиотеку типов: class PrimeManager:
DIPrimeManager { LONG m_cRef;
// СОМ reference count
// счетчик ссылок СОМ ITypeInfo *m_pTypeInfo;
// ptr. to type desc.
// указатель на описание типов
// IUnknown methods…
// методы IUnknown…
// IDispatch methods…
// методы IDispatch…
// IPrimeManager methods…
// методы IPrimeManager…
PrimeManager(void) : m_cRef(0) {
ITypeLib *ptl = 0;
HRESULT hr = LoadRegTypeLib(LIBID_PrimeLib, 1, 0, 0, &ptl);
assert(SUCCEEDED(hr));
hr = ptl->GetTypeInfoOfGuid(IID_DIPrimeManager, &m_pTypeInfo);
ptl->Release(); } virtual PrimeManager(void) { m_pTypeInfo->Release(); }
};
Имея приведенное выше определение класса, метод GetTypeInfo просто возвращает описание данного интерфейса:
STDMETHODIMP PrimeManager::GetTypeInfo (UINT it, LCID lcid, ITypeInfo **ppti) {
assert(it == 0 && ppti != 0);
(*ppti = m_pTypeInfo)->AddRef();
return S_OK;
}
Если бы объект поддерживал несколько локализованных библиотек типов, то реализации следовало бы использовать параметр LCID, чтобы решить, какое описание типа нужно возвратить. Соответствующая реализация GetTypeInfoCount еще проще:
STDMETHODIMP PrimeManager::GetTypeInfoCount(UINT *pit) {
assert(pit != 0); *pit = 1;
// only 0 or 1 are allowed
// допускаются только 0 или 1
return S_OK;
}
Единственными допустимыми значениями счетчика являются нуль (это означает, что данный объект не содержит описаний своего интерфейса) и единица (это означает, что данный объект содержит описания своего интерфейса). Даже если объект поддерживает несколько локализованных описаний типа, результирующий счетчик остается равным единице.
Методы GetTypeInfo и GetTypeInfoCount фактически являются вспомогательными. Истинным ядром интерфейса IDispatch являются методы GetIDsOfNames и Invoke. Реализация GetIDsOfNames направляет вызов в машину синтаксического анализа библиотеки типов, встроенную в СОМ:
STDMETHODIMP PrimeManager::GetIDsOfNames(REFIID riid, OLECHAR **pNames, UINT cNames, LCID lcid, DISPID *pdispids) {
assert(riid == IID_NULL);
return m_pTypeInfo->GetIDsOfNames(pNames, cNames, pdispids);
}
Поскольку библиотека типов содержит все имена методов и соответствующие им DISPID , реализация не представляет труда для синтаксического анализатора. Метод Invoke реализован аналогичным образом:
STDMETHODIMP PrimeManager::Invoke(DISPID id, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pd, VARIANT *pVarResult, EXCEPINFO *pe, UINT *pu) {
assert(riid == IID_NULL);
void *pvThis = static_cast<DIPrimeManager*>(this);
return m_pTypeInfo->Invoke(pvThis, id, wFlags, pd, pVarResult, pe, pu);
}
Первым параметром ITypeInfo::Invoke является указатель на интерфейс. Тип этого интерфейса должен быть таким же, как интерфейс, который описан в информации о типах. Когда передаваемые аргументы корректно синтаксически преобразованы в стек вызова (call stack), синтаксический анализатор будет вызывать текущие методы через этот интерфейсный указатель. Рис. 7.6 иллюстрирует последовательность вызовов для сред подготовки сценариев, которые осуществляют вызовы через двойственные интерфейсы.
Двунаправленные интерфейсные контракты
Как было показано в главе 5, объекты, постоянно находящиеся в различных апартаментах, могут использовать сервисные программы друг друга вне зависимости от того, резидентом какого апартамента является другой объект. Поскольку удаленный доступ в СОМ основан на концепции апартаментов, разработчикам необходимо рассматривать процессы не как клиенты или серверы в чистом виде, а скорее как набор из одного или нескольких апартаментов, которые способны одновременно экспортировать и импортировать интерфейсы.
Как два объекта договариваются о том, чьи интерфейсы будут использоваться для взаимного сотрудничества, в значительной степени является спецификой области применения. Для примера рассмотрим следующий интерфейс, моделирующий программиста:
[uuid(75DA6457-DD0F-11d0-8C58-0080C73925BA),object]
interface IProgrammer : IUnknown {
HRESULT StartHacking(void);
HRESULT IsProductDone([out, retval] BOOL *pbIsDone);
}
Клиент будет использовать такой интерфейс следующим образом:
HRESULT ShipSoftware(void)
{
IProgrammer *pp = 0;
HRESULT hr = CoGetObject(OLESTR(«programmer:Bob»), 0,
IID_IProgrammer, (void**)&pp);
if (SUCCEEDED(hr)) {
hr = pp->StartHacking();
BOOL bIsDone = FALSE;
while (!bIsDone && SUCCEEDED(hr)) {
Sleep(15000);
// wait 15 seconds
// ожидаем 15 секунд
hr = pp->IsProductDone(&bIsDone);
// check status
// проверяем состояние
}
pp->Release();
}
}
Очевидно, что этот код весьма неэффективен, поскольку клиент каждые 15 секунд опрашивает состояние объекта. Более эффективным для клиента был бы следующий подход: подготовить второй объект, которому объект-программист (programmer object) мог бы сообщить, когда данный объект придет в нужное состояние. Этот подготовленный клиентом объект должен экспортировать интерфейс, предоставляющий контекст, через который мог бы работать объект-программист:
[uuid(75DA6458-DD9F-11d0-8C58-0080C73925BA),object]
interface ISoftwareConsumer : IUnknown {
HRESULT OnProductIsDone(void);
HRESULT OnProductWillBeLate([in] hyper nMonths);
}
При таком парном определении интерфейса должен существовать некий механизм для информирования объекта-программиста о том, что у клиента имеется реализация ISoftwareConsumer, с помощью которой он может получать уведомления от объекта-программиста об изменениях состояния. Одной из распространенных методик является определение IProgrammer таким образом, чтобы он имел явные методы, через которые клиенты могли бы связываться со своими объектами-потребителями (consumer object). Канонической формой этой идиомы является включение метода Advise:
interface IProgrammer : IUnknown {
HRESULT Advise ([in] ISoftwareConsumer *psc, [out] DWORD *pdwCookie);
: : :
посредством которого клиент подготавливает парный объект-потребитель, а программист возвращает DWORD для подтверждения связи. Затем этот DWORD можно было бы использовать в соответствующем методе Unadvise:
- Аквариум. (Новое издание, исправленное и переработанное) - Виктор Суворов (Резун) - Шпионский детектив
- Права на результаты интеллектуальной деятельности и средства индивидуализации: Комментарий к части четвертой Гражданского кодекса Российской Федерации - Вадим Погуляев - Юриспруденция
- У меня есть идея! Что дальше? - Михаил Соболев - О бизнесе популярно
- Ориентирование - К. М. Станич - Современные любовные романы
- Проект 365 - Константин Рочев - Поэзия