Выбрать главу

Для решения проблем, связанных с передачей больших массивов в качестве параметров метода, в СОМ имеется стандартная идиома разработки интерфейсов, позволяющая получателю данных явно осуществлять управление потоками элементов массива. Эта идиома основана на передаче вместо фактических массивов специального интерфейсного указателя СОМ. Этот специальный интерфейсный указатель, называемый нумератором (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

STDMETHODIMP QueryInterface(REFIID riid, void **ppv);

STDHETHODIMP_(ULONG) AddRef(void);

STDMETHODIMP_(ULONG) Release(void);

// IEnumLong methods

// методы IEnumLong

STDMETHODIMP Next(ULONG, long *, ULONG *);

STDMETHODIMP Skip(ULONG);

STDMETHODIMP Reset(void);

STDMETHODIMP Clone(IEnumLong **ppe);

};

Реализация генератора Next будет просто порождать запрошенное количество простых чисел:

STDMETHODIMP PrimeGenerator::Next(ULONG cElems, long *prgElems, ULONG *pcFetched) {

// ensure that pcFetched is valid if cElems > 1

// удостоверяемся, что pcFetched легален, если cElems больше единицы

if (cElems > 1 && pcFetched == 0) return E_INVALIDARG;

// fill the buffer

// заполняем буфер

ULONG cFetched = 0;

while (cFetched < cElems && m_nCurrentPrime <= m_nMax) {

prgElems[cFetched] = GetNextPrime(m_nCurrentPrime);

m_nCurrentPrime = prgElems[cFetchcd++];

} if (pcFetched)

// some callers may pass NULL

// некоторые вызывающие программы могут передавать NULL

*pcFetched = cFetched;

return cFetched == cElems ? S_OK : S_FALSE;

}

Отметим, что даже если имеются миллионы допустимых значений, одновременно в памяти будет находиться лишь малое их число.

Методу генератора Skip нужно просто генерировать и отбрасывать запрошенное количество элементов:

STDMETHODIMP PrimeGenerator::Skip(ULONG cElems) {

ULONG cEaten = 0; while (cEaten < cElems && m_nCurrentPrime <= m_nMax) {

m_nCurrentPrime = GetNextPrime(m_nCurrentPrime);

cEaten++; }

return cEaten == cElems ? S_OK : S_FALSE;

}

Метод Reset устанавливает курсор на начальное значение:

STDMETHODIMP PrimeGenerator::Reset(void) {

m_nCurrentPrime = m_nMin;

return S_OK;

}

а метод Clone создает новый генератор простых чисел на основе минимума, максимума и текущих значений, выданных существующим генератором:

STDMETHODIMP PrimeGenerator::Clone(IEnumLong **ppe) {

assert(ppe);

*рре = new PrimeGenerator(m_nMin, m_nMax, m_nCurrent);

if (*ppe) (*ppe)->AddRef();

return S_OK;

}

При наличии реализации PrimeGenerator реализация метода GetPrimes текущим объектом становится тривиальной:

STDMETHODIMP MyClass::GetPrimes(long nМin, long nMax, IEnumLong **ppe) {

assert(ppe);

*ppe = new PrimeGenerator (nMin, nMax, nMin);

if (*ppe) (*ppe)->AddRef();

return S_OK;

}

Большая часть этой реализации находится теперь в классе PrimeGenerator, а не в классе объекта.

Динамический вызов в сравнении со статическим

До сих пор говорилось о том, что СОМ основан на клиентских программах, имеющих на этапе разработки предварительную информацию об определении интерфейса. Это достигается либо через заголовочные файлы C++ (для клиентов C++), либо через библиотеки типов (для клиентов Java и Visual Basic). В общем случае это не представляет трудностей, так как программы, написанные на этих языках, перед употреблением обычно проходят фазу какой-либо компиляции. Некоторые языки не имеют такой фазы компиляции на этапе разработки и вместо этого распространяются в исходном коде с тем, чтобы интерпретироваться во время выполнения. Вероятно, наиболее распространенными среди таких языков являются языки сценариев на базе HTML (например, Visual Basic Script, JavaScript), которые выполняются в контексте Web-броузера или Web-сервера. В обоих этих случаях текст сценариев вкладывается в его исходном виде в файл HTML, а окружающая исполняемая программа выполняет текст сценариев «на лету», по мере анализа HTML. С целью обеспечить разнообразную среду программирования эти окружения позволяют сценариям вызывать методы СОМ-объектов, которые могут создаваться в самом тексте сценария или где-нибудь еще в HTML-потоке (например, какой-либо управляющий элемент, который является также частью Web– страницы). В таких средах в настоящее время невозможно использовать библиотеки типов или другие априорные средства для снабжения машины времени выполнения (runtime engine) описанием используемых интерфейсов. Это означает, что объекты сами должны помогать интерпретатору переводить исходный текст сценариев в содержательные вызовы методов.