/* массив из VARIANTS */
Для предоставления SAFEARRAY возможности определять типы данных своих элементов, компилятор IDL распознает специальный, специфический для SAFEARRAY, синтаксис:
HRESULT Method([in] SAFEARRAY(type) *ppsa);
где type – тип элемента в SAFEARRAY. Соответствующий прототип данного метода в C++ выглядел бы примерно так:
HRESULT Method(SAFEARRAY **psa);
Отметим, что в определении IDL используется только один уровень косвенности; в то же время в соответствующем определении C++ используются два уровня косвенности. Рассмотрим следующее определение на IDL, задающее массив типа SAFEARRAY из коротких целых чисел:
HRESULT Method([in] SAFEARRAY(short) *psa);
Соответствующее определение на Visual Basic выглядело бы таким образом:
Sub Method(ByVal psa As Integer())
Отметим, что в варианте на Visual Basic не указано явно размерностей массива. Напомним, однако, что Visual Basic поддерживает массивы с фиксированной длиной.
Тип данных SAFEARRAY поддерживается весьма богатым набором API-функций, которые позволяют изменять размерность массивов и производить обход их содержимого переносимым образом. Для доступа к элементам типа SAFEARRAY СОМ предусматривает следующие вызовы API-функций:
// get a pointer to the actual array elements
// получаем указатель на фактически элементы массива
HRESULT SafeArrayAccessData([in] SAFEARRAY *psa, [out] void ** ppv);
// release pointer returned by SafeArrayAccessData
// освобождаем указатель, возвращенный функцией SafeArrayAccessData
HRESULT SafeArrayUnaccessData([in] SAFEARRAY *psa);
// Get number of dimensions
// Получаем количество измерений
ULONG SafeArrayGetDim([in] SAFEARRAY *psa);
// Get upper bound of a dimension
// Получаем верхнюю границу измерения
HRESULT SafeArrayGetUBound([in] SAFEARRAY *psa, [in] UINT nDim, [out] long *pUBound);
// Get lower bound of a dimension
// Получаем нижнюю границу измерения
HRESULT SafeArrayGetLBound([in] SAFEARRAY *psa, [in] UINT nDim, [out] long *pLBound);
Эти методы обеспечивают компактный и надежный способ доступа к текущему содержимому массива. Рассмотрим следующий код на IDL:
HRESULT Sum([in] SAFEARRAY(long) *ppsa, [out, retval] long *pSum);
Тогда следующая реализация метода будет вычислять сумму элементов массива типа SAFEARRAY, состоящего из длинных целых чисел (long integers):
STDMETHODIMP MyClass::Sum(SAFEARRAY **ppsa, long *pnSum)
{
assert(ppsa && *ppsa && pnSum);
assert(SafeArrayGetDim(*ppsa) == 1);
long iUBound, iLBound;
// note that dimension indices are one-based
// отметим, что индексы размерности начинаются с единицы
HRESULT hr = SafeArrayGetUBound(*ppsa, 1, &iUBound);
assert(SUCCEEDED(hr));
hr = SafeArrayGetLBound(*ppsa, 1, &iLBound);
assert(SUCCEEDED(hr));
long *prgn = 0;
hr = SafeArrayAccessData(*ppsa, (void**)&prgn);
*pnSum = 0;
for (long i = 0; i < iUBound – iLBound; i++)
*pnSum += prgn[i];
SafeArrayUnaccessData(*ppsa);
return S_OK;
}
Отметим, что вызовы любых API-функций, которые имеют дело с размерностями массива, используют индексы, начинающиеся с единицы.
Приведенный выше фрагмент кода просто манипулировал содержимым существующего SAFEARRAY-массива. Для создания одномерного массива типа SAFEARRAY для передачи его в качестве параметра метода в СОМ имеется следующая API-функция, которая выделяет память для структуры SAFEARRAY и элементов этого массива в одном непрерывном блоке памяти:
SAFEARRAY *SafeArrayCreateVector(
[in] VARTYPE vt, // element type
// тип элемента
[in] long iLBound, // index of lower bound
// индекс нижней границы
[in] unsigned int cElems); // # of elements
// число элементов
Кроме того, в СОМ имеются различные функции, предназначенные для размещения многомерных массивов, однако их рассмотрение выходит за рамки данной дискуссии. При таком определении метода на IDL:
HRESULT GetPrimes([in] long nStart, [in] long nEnd, [out] SAFEARRAY(long) *ppsa);
следующее определение метода на 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;
}
Соответствующий код с клиентской стороны выглядел бы на Visual Basic примерно так:
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], так как клиент не может начать обработку тех частичных результатов операции, которые, возможно, уже получены к этому моменту.