Сущность технологии СОМ. Библиотека программиста
Шрифт:
следующее определение метода на C++ возвращает вызывающей программе массив типа SAFEARRAY, размещенный в вызываемом методе:
STDMETHODIMP MyClass::GetPrimes (long nMin, long nMax, SAFEARRAY **ppsa)
{
assert(ppsa);
UINT cElems = GetNumberOfPrimes(nMin, nMax);
*ppsa = SafeArrayCreateVector(VT_I4, 0, cElems);
assert(*ppsa);
long *prgn = 0;
HRESULT hr = SafeArrayAccessData(*ppsa, (void**)&prgn);
assert(SUCCEEDED(hr));
for (UINT i=0; i < cElems; i++)
prgn[i] = GetNextPrime(i ? prgn[1 – 1] : nMin);
SafeArrayUnaccessData(*ppsa);
return S_OK;
}
Соответствующий код с клиентской
Function GetSumOfPrimes(ByVal nMin as Long, ByVal nMax as Long) as Long
Dim arr as Long
Dim n as Variant
Objref.GetPrimes nMin, nMax, arr
GetSumOfPrimes = 0
for each n in arr
GetSumOfPrimes = GetSumOfPrimes + n
Next n
End Function
что соответствует следующему коду на C++:
long GetSumOfPrimes (long nMin, long nMax)
{
SAFEARRAY *pArray = 0;
HRESULT hr = g_pObjRef->GetPrimes(nMin, nMax, &pArray);
assert(SUCCEEDED(hr) && SafeArrayGetDim(pArray) == 1);
long *prgn = 0;
hr = SafeArrayAccessData(pArray, (void**)&prgn);
long iUBound, iLBound, result = 0;
SafeArrayGetUBound(pArray, 1, &iUBound);
SafeArrayGetLBound(pArray, 1, &iLBound);
for (long n = iLBound; n <= iUBound: n++)
result += prgn[n];
SafeArrayUnaccessData(pArray);
SafeArrayDestroy(pArray);
return n;
}
Отметим, что вызывающая программа ответственна за освобождение ресурсов, выделенных для SAFEARRAY-массива, возвращенного как [out]-параметр. Вызов функции SafeArrayDestroy корректно освобождает всю память и все ресурсы, удерживаемые структурой SAFEARRAY.
Управление потоками данных
Отметим, что в предыдущих примерах использования массивов, в том числе типа SAFEARRAY , вопрос о том, какое количество данных будет передано в ORPC-сообщении, решал отправитель данных. Рассмотрим следующее простое определение метода на IDL:
HRESULT Sum([in] long cElems, [in, size_is(cElems)] double *prgd, [out, retval] double *pResult);
Если бы вызывающая программа должна была запустить этот метод следующим образом:
double rgd[1024 * 1024 * 16];
HRESULT hr = p->Sum(sizeof(rgd)/sizeof(*rgd), rgd);
то размер результирующего ответного сообщения ORPC-запроса был бы не меньше 128 Мбайт. Хотя лежащий в основе RPC-протокол вполне в состоянии разбивать большие сообщения на несколько сетевых пакетов, при использовании больших массивов все же возникают некоторые проблемы. Одна очевидная проблема состоит в том, что вызывающая программа должна иметь свыше 128 Мбайт доступной памяти сверх той, которая занята существующим массивом. Дублирующий буфер необходим интерфейсному заместителю для создания ответного ORPC-сообщения, в которое в конечном счете будет скопирован этот массив. Подобная проблема заключается в том, что процесс объекта также должен иметь больше 128 Мбайт доступной памяти для реконструирования полученных RPC-пакетов в единое сообщение ORPC. Если бы массив использовал атрибут [length_is], то следовало бы выделить еще 128 Мбайт, чтобы скопировать этот массив в память для передачи его методу. Эта проблема относится к параметрам как типа [in], так и [out]. В любом случае отправитель массива может иметь достаточное буферное пространство для создания OPRC-сообщения, а получатель массива –
Более сложная проблема с приведенным выше определением метода связана со временем ожидания (latency). Семантика ORPC-запроса требует, чтобы на уровне RPC/ORPC полное ORPC-сообшение реконструировалось до вызова метода объекта. Это означает, что объект не может начать обработку имеющихся данных, пока не получен последний пакет. Когда общее время передачи большого массива довольно велико, объект будет оставаться незанятым в течение значительного промежутка времени, ожидая получения последнего пакета. Возможно, что в течение этого времени ожидания многие элементы уже успешно прибыли в адресное пространство объекта; тем не менее, семантика вызова метода в СОМ требует, чтобы к началу текущего вызова присутствовали все элементы. Та же проблема возникает, когда массивы передаются как параметры с атрибутом [out], так как клиент не может начать обработку тех частичных результатов операции, которые, возможно, уже получены к этому моменту.
Для решения проблем, связанных с передачей больших массивов в качестве параметров метода, в СОМ имеется стандартная идиома разработки интерфейсов, позволяющая получателю данных явно осуществлять управление потоками элементов массива. Эта идиома основана на передаче вместо фактических массивов специального интерфейсного указателя СОМ. Этот специальный интерфейсный указатель, называемый нумератором (enumerator), позволяет извлекать элементы из отправителя со скоростью, подходящей для получателя. Чтобы применить эту идиому к приведенному выше определению метода, понадобится следующее определение интерфейса:
interface IEnumDouble : Unknown {
// pull a chunk of elements from the sender
// извлекаем порцию данных из отправителя
HRESULT Next([in] ULONG cElems, [out, size_is(cElems), length_is(*pcFetched)] double *prgElems, [out] ULONG *pcFetched);
// advance cursor past cElems elements
// переставляем курсор после элементов cElems
HRESULT Skip([in] cElems);
// reset cursor to first element
// возвращаем курсор на первый элемент
HRESULT Reset(void);
// duplicate enumerator's current cursor
// копируем текущий курсор нумератора
HRESULT Clone([out] IEnumDouble **pped);
}
Важно отметить, что интерфейс IEnum моделирует только курсор, а отнюдь не текущий массив. Имея такое определение интерфейса, исходное определение метода IDL:
HRESULT Sum([in] long cElems, [in, size_is(cElems)] double *prgd, [out, retval] double *pResult);
преобразуется следующим образом:
HRESULT Sum([in] IEnumDouble *ped, [out, retval] double *pResult);
Отметим, что подсчет элементов больше не является обязательным, так как получатель данных обнаружит конец массива, когда метод IEnumDouble::Next возвратит специальный HRESULT (S_FALSE ).
При наличии приведенного выше определения интерфейса корректной была бы следующая реализация метода:
STDMETHODIMP MyClass::Sum(IEnumDouble *ped, double *psum) {
assert(ped && psum);
*psum = 0; HRESULT hr; do {