Более сложная проблема с приведенным выше определением метода связана со временем ожидания ( 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 {
// declare a buffer to receive some elements
// объявляем буфер для получения нескольких элементов
enum {
CHUNKSIZE = 2048 };
double rgd[CHUNKSIZE];
// ask data producer to send CHUNKSIZE elements
// просим источник данных послать CHUNKSIZE элементов
ULONG cFetched;
hr = ped->Next(CHUNKSIZE, rgd, &cFetched);
// adjust cFetched to address sloppy objects
// настраиваем cFetched на исправление некорректных объектов
if (hr == S_OK) cFetched = CHUNKSIZE;
if (SUCCEEDED(hr))
// S_OK or S_FALSE
// S_OK или S_FALSE
// consume/use received elements
// потребляем/используем полученные элементы
for (ULONG n = О; п < cFetched; n++) *psum += rgd[n];
}
while (hr == S_OK);
// S_FALSE or error terminates
// завершается по S_FALSE или по ошибке
}
Отметим, что подпрограмма Next возвратит S_OK в случае, если у отправителя имеются дополнительные данные для посылки, и S_FALSE , если пересылка закончена. Также отметим, что в данный код включена защита от некорректных реализации, которые не утруждают себя установкой переменной cFetched при возвращении S_OK ( S_OK означает, что все запрошенные элементы были извлечены).
Одно из преимуществ использования идиомы IEnum состоит в том, что она позволяет отправителю откладывать генерирование элементов массива. Рассмотрим следующее определение метода на IDL: HRESULT GetPrimes([in] long nMin, [in] long nMax, [out] IEnumLong **ppe);
Разработчик объекта может создать специальный класс, который генерирует по требованию простые числа и реализует интерфейс IEnumLong :
class PrimeGenerator : public IEnumLong {
LONG m_cRef;
// СОМ reference count
// счетчик ссылок СОМ
long m_nCurrentPrime;
// the cursor
// курсор long m_nMin;
// minimum prime value
// минимальное значение простого числа
long m_nMax;
// maximum prime value
// максимальное значение простого числа
public:
PrimeGenerator(long nMin, long nMax, long nCurrentPrime) : m_cRef(0), m_nMin(nMin), m_nMax(nMax),
m_nCurrentPrime(nCurrentPrime) { }
// IUnknown methods
// методы IUnknown
Читать дальше