Сущность технологии COM

2bbc099f

Массивы


По умолчанию указатели, передаваемые через параметры, полагаются указателями на единичные экземпляры, а не на массивы. Для передачи массива в качестве параметра можно использовать синтаксис С для массивов и/или специальные атрибуты IDL для представления различной информации о размерности массива. Простейший способ передачи массивов — задать размерность во время компиляции:

HRESULT Method1([in] short rgs[8]);

Такое задание называется массивом постоянной длины (fixed array) и является наиболее простым для выражения на языке IDL и одновременно — наиболее простым и компактным представлением во время выполнения. Для такого массива интерфейсный заместитель выделит 16 байт (8 * sizeof (short)) в сообщении ORPC-запроса, а затем скопирует в сообщение все восемь элементов. Как только сервер получает ORPC-запрос, интерфейсная заглушка будет использовать память непосредственно из принимаемого блока в качестве аргумента функции, как показано на рис. 7.2.

Поскольку размер массива является постоянным и все содержимое массива уже содержится в принимаемом буфере, интерфейсная заглушка достаточно разумна, чтобы повторно использовать передаваемую память буфера в качестве текущего аргумента метода.

Только что показанный метод полезен, если во всех случаях единственно разумной длиной массива является 8. Это позволяет вызывающей программе пересылать любой выбранный ею массив из коротких целых чисел (shorts), при условии, что этот массив состоит только из восьми элементов:

void f(IFoo *pFoo) { short rgs[8] = { 1, 2, 3, 4, 5, 6, 7, 8 }; pFoo->Method1(rgs); }

На практике предсказание подходящей длины массива невозможно, так как слишком малая длина означает, что будет передано недостаточно элементов, а слишком большая длина приведет к чрезмерному объему передаваемого сообщения. Более того, если массив состоит из сложных типов данных, то маршалинг элементов за пределами фактического размера массива может обойтись весьма дорого и/или привести к ошибкам маршалинга.
Тем не менее, массивы постоянной длины полезны в тех случаях, когда размер массива не изменяется и известен во время формирования интерфейса.

Чтобы можно было определять размеры массивов во время выполнения, IDL (и используемый сетевой протокол NDR) разрешает вызывающей программе задавать длину массива на этапе выполнения. Массивы такого типа называются совместимыми (conformant). Максимальный допустимый индекс совместимого массива можно задавать либо во время выполнения, либо во время компиляции, а длина, называемая соответствием (conformance) массива, передается раньше чем текущие элементы, как это показано на рис. 7.3. Как и в случае массива постоянной длины, совместимые массивы могут передаваться в реализацию метода непосредственно из передаваемого буфера без какого-либо дополнительного копирования, так как в передаваемом сообщении всегда присутствует все содержимое массива.

Чтобы предоставить вызывающей программе возможность задать соответствие массива, IDL использует атрибут [size_is]:

HRESULT Method2([in] long cElems, [in, size_is(cElems)] short rgs[*]);



или

HRESULT Method3([in] long cElems, [in, size_is (cElems)] short rgs[]);

или

HRESULT Method4([in] long cElems, [in, size_is(cElems)] short *rgs);



Все эти типы являются эквивалентными в терминах базового пакетного формата. Любой из этих методов дает вызывающей программе возможность определить соответствующий размер массива следующим образом:

void f(IFoo *pFoo) { short rgs[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; pFoo->Method2(8, rgs); }

Выражение, используемое атрибутом [size_is] указанным выше способом, может содержать любые другие параметры того же метода, а также арифметические, логические и условные операторы. К примеру, следующий IDL-код является допустимым и достаточно простым для понимания:

HRESULT Method5([in] long arg1, [in] long arg2, [in] long arg3, [in, size_is(arg1 ? (arg3+1) : (arg1 & arg2))] short *rgs);

Вызовы функции или другие языковые конструкции, способные вызвать побочные эффекты (такие, как операторы ++ и --), запрещены в выражениях атрибута [size_is].



Если атрибут [size_is] используется для описания совместимого массива, вложенного внутрь какой-либо структуры, он может применять любые другие элементы этой структуры:

typedef struct tagCOUNTED_SHORTS { long cElems; [size_is(cElems)] short rgs[]; } COUNTED_SHORTS;

HRESULT Method6([in] COUNTED_SHORTS *pcs);

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

void SendFiveShorts (IFoo *pFoo) { char buffer [sizeof (COUNTED_SHORTS) + 4 * sizeof (short)]; COUNTED_SHORTS& rcs = *reinterpret_cast<COUNTED_SHORTS*>(buffer); rcs.cElems = 5; rcs.rgs[0] = 0; rcs.rgs[1] = 1; rcs.rgs[2] = 2; rcs.rgs[3] = 3; rcs.rgs[4] = 4; pFoo->Method6(&rcs); }

IDL также поддерживает атрибут [max_is], который является стилистической вариацией атрибута [size_is]. Атрибут [size_is] показывает число элементов, которое может содержать массив; атрибут [max_is] показывает максимальный допустимый индекс в массиве (который на единицу меньше числа элементов, содержащихся в массиве). Это означает, что два приведенных ниже описания эквивалентны друг другу:

HRESULT Method7([in, size_is(10)] short *rgs); HRESULT Method8([in, max_is(9)] short *rgs);

Интересно, что хотя в атрибутах [size_is] могут быть использованы константы, как это показано выше, немного более эффективным представляется использование массива постоянной длины. Если используется совместимый массив, то в предыдущих примерах размер соответствия должен быть передан, несмотря на то, что его величина статична и известна на этапе компиляции как интерфейсному заместителю, так и интерфейсной заглушке.

Если бы содержимое массивов передавалось только от вызывающей программы в реализацию метода, то совместимый массив был бы достаточен почти для любых целей. Однако во многих случаях вызывающая программа хочет передать объекту пустой массив и получить его обратно заполненным нужными значениями. Как показано ниже, совместимые массивы можно использовать в качестве выходных параметров:

HRESULT Method9([in] long cMax, [out, size_is(cMax)] short *rgs);



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

void f(IFoo *pFoo) { short rgs[100]; pFoo->Method9(100, rgs); }

а также следующая реализация со стороны сервера:

HRESULT CFoo::Method9(long cMax, short *rgs) { for (long n = 0; n < cMax; n++) rgs[n] = n * n; return S_OK; }

Но что, если реализация метода не может правильно заполнить весь массив допустимыми элементами? В предыдущем фрагменте кода, даже если метод инициализирует только первые cMax/2 элементов массива, заглушка со стороны сервера, тем не менее, передаст весь массив из cMax элементов. Ясно, что это неэффективно, и для исправления этого положения в IDL и NDR имеется третий тип массивов, — переменный массив (varying array).

Переменный массив — это массив, который имеет постоянную длину, но может содержать меньше допустимых элементов, чем позволяет его фактическая емкость. Вне зависимости от фактической длины массива будет передаваться единое непрерывное подмножество содержимого переменного массива. Для задания подмножества элементов, подлежащих передаче, IDL использует атрибут [length_is]. В отличие от атрибута [size_is], описывающего длину массива, атрибут [length_is] описывает фактическое содержимое массива. Рассмотрим следующий код на IDL:

HRESULT Method10([in] long cActual, [in, length_is(cActual)] short rgs[1024]);

Во время передачи первым будет передано значение cActual, которое называется переменной длиной (variance) массива, и лишь затем сами величины. Для того чтобы переданный блок (region) мог появиться в любом месте массива, а не только в его начале, IDL и NDR поддерживают также атрибут [first_is], который указывает место, где начинается передаваемый блок. Данная величина смещения будет также передаваться вместе с содержимым массива, чтобы демаршалер знал, какая часть массива инициализируется. Аналогично тому, как атрибут [size_is] имел свою стилистическую вариацию [max_is], [length_is] также имеет вариацию — [last_is], в которой используется индекс вместо счетчика.


Два следующих определения эквивалентны:

HRESULT Metnod11([in, first_is(2), length_is(5)] short rgs(8]); HRESULT Method12([in, first_is(2), last_is(6)] short rgs[8]);

Оба метода инструктируют маршалер передавать только пять элементов массива, но демаршалирующая сторона выделяет место для восьми элементов и поступающие значения копируются в соответствующие места. Любые элементы, которых нет в передаваемом буфере, будут обнуляться.

Переменные массивы могут уменьшить объем сетевых передач, так как передаются только необходимые элементы. Однако, как показано на рис. 7.4, переменные массивы менее эффективны, чем совместимые массивы, в смысле избыточного копирования памяти. Массив, передаваемый в реализацию метода заглушкой со стороны сервера, размещен в отдельном блоке динамически распределяемой памяти ("в куче"). Вначале этот блок в процессе инициализации заполняется нулями, а затем содержимое передаваемого буфера копируется в соответствующие места памяти. Это приводит к одному или двум дополнительным проходам по памяти массива перед входом в метод, что для больших массивов может ухудшать производительность. Нельзя сказать, что переменные массивы бесполезны, но при использовании только в качестве входных параметров переменный массив значительно менее эффективен, чем логически эквивалентный ему совместимый массив.

Подобно массивам постоянной длины, переменные массивы требуют от разработчика интерфейса задания соответствия/длины во время компиляции. Это обстоятельство значительно ограничивает использование переменных массивов, так как на практике затруднительно предсказать оптимальный размер буфера для всех вариантов использования интерфейса (например, у некоторых клиентов могут быть жесткие ограничения на использование памяти, а другие могут назначить более высокую плату за прием-передачу и поэтому предпочли бы большие буферы).



К счастью, и IDL, и NDR позволяют задавать как содержимое (переменную длину), так и длину (соответствие) для данного массива путем комбинирования атрибутов [size_is] и [length_is].


При использовании обоих этих атрибутов массив носит название совместимого переменного массива, или просто открытого (open) массива. Для задания открытого массива необходимо просто дать возможность вызывающей программе устанавливать и длину, и содержимое через параметры:

HRESULT Method13([in] cMax, [in] cActual, [in, size_is(cMax), length_is(cActual)] short rgs[]);

или

HRESULT Method14([in] cMax, [in] cActual, [in, size_is(cMax), length_is(cActual)] short rgs[*]);

или

HRESULT Method15([in] cMax, [in] cActual, [in, size_is(cMax), length_is(cActual)] short *rgs);

каждый из которых предполагает такое использование со стороны клиента:

void f(IFoo *pFoo) { short rgs[8]; rgs[0] = 1; rgs[1] = 2; pFoo->Method13(8, 2, rgs); }

Как показано на рис. 7.5, при передаче открытого массива маршалер сначала выяснит длину массива, а затем смещение и длину его фактического содержимого. Как и в случае переменного массива, длина массива может быть больше, чем количество передаваемых элементов. Это означает, что содержимое передаваемого буфера не может быть передано непосредственно вызывающей программе, поэтому используется второй блок памяти, что увеличивает расход памяти.



Совместимые массивы являются самым полезным типом массивов для входных параметров. Открытые массивы наиболее полезны для выходных или входных/выходных параметров, поскольку они позволяют вызывающей программе выделять буфер произвольного размера, несмотря на то, что передаваться будет только необходимое в каждом случае количество элементов. IDL для обеспечения использования этих типов выглядит следующим образом:

HRESULT Method16([in] long cMax, [out] long *pcActual, [out, size_is(cMax), length_is(*pcActual)] short *rgs);

из чего следует такое использование со стороны клиента:

void f(IFoo *pFoo) { short rgs[8]; long cActual; pFoo->Method16(8, &cActual, rgs); // .. process first cActual elements of rgs // .. обрабатываем первые cActual элементов из массива rgs }

в то время как реализация со стороны сервера выглядит примерно так:



HRESULT CFoo::Method16(long cMax, long *pcActual, short *rgs) { *pcActual = min(cMax,5); // only write 1st 5 elems // записываем только первые пять элементов for (long n = 0; n < *pcActual; n++) rgs[n] = n * n; return S_OK; }

Это позволяет вызывающей программе контролировать задание размеров буфера, а реализация метода контролирует фактическое количество переданных элементов.

Если открытый массив будет использоваться в качестве входного/выходного параметра, то следует указать переменную длину массива в каждом направлении. Если число элементов на входе может отличаться от числа элементов на выходе, то параметр переменной длины тоже должен иметь входной/выходной тип:

HRESULT Method17([in] long cMax, [in, out] long *pcActual, [in, out, size_is(cMax), length_is(*pcActual)] short *rgs);

что предполагает следующий код на стороне клиента:

void f(IFoo *pFoo) { short rgs[8]; rgs[0] = 0; rgs[1] = 1; long cActual = 2; pFoo->Method17(8, &cActual, rgs); // .. process first cActual elements of rgs // .. обрабатываем первые cActual элементов из массива rgs }

Если число элементов на входе и на выходе одно и то же, то подойдет совместимый массив:

HRESULT Method18([in] long cElems, [in, out, size_is(cElems)] short *rgs);

Данный метод использует эффективность совместимого массива, и его гораздо проще использовать.

Приведенные выше примеры оперировали с одномерными массивами. Рассмотрим следующий прототип на С:

void g(short **arg1);

Этот прототип может означать в С все, что угодно. Возможно, функция ожидает указатель на одно короткое целое число:

void g(short **arg1) { // return ptr to static // возвращаем указатель на static static short s; *arg1 = &s; }

Или, возможно, функция ожидает массив из 100 коротких указателей:

void g(short **arg1) { // square 100 shorts by ref // квадрат из 100 коротких целых указателей for (int n = 0; n < 100; n++) *(arg1[n]) *= *(arg1[n]); }

А также, возможно, функция ожидает указатель на указатель на массив коротких целых:



void g(short **arg1) { // square 100 shorts // квадрат из 100 коротких целых for (int n = 0; n < 100; n++) (*arg1)[n] *= (*arg1)[n]; }

Этот синтаксический кошмар разрешается в IDL использованием такого синтаксиса, который часто побуждает пользователей-новичков бежать за утешением к документации.

Атрибуты IDL [size_is] и [lengtn_is] принимают переменное количество разделенных запятой аргументов, по одному на каждый уровень косвенности. Если параметр пропущен, то считается, что соответствующий уровень косвенности является указателем на экземпляр, а не на массив. Для того чтобы показать, что параметр является указателем на указатель на одиночный экземпляр, не требуется более никаких атрибутов:

HRESULT Method19([in] short **pps);

что означает такое расположение в памяти:

pps -> *pps-> **pps

Для того чтобы показать, что параметр является указателем на массив указателей на экземпляры, нужно написать следующий код IDL:

HRESULT Method20([in, size_is(3)] short **rgps);

что в памяти будет выглядеть примерно так:

rgps -> rgps[0] -> *rgps[0] rgps[1] -> *rgps[1] rgps[2] -> *rgps[2]

Для того чтобы показать, что параметр является указателем на указатель на массив экземпляров, следует написать такой код на IDL:

HRESULT Method21([in, size_is(,4)] short **pprgs);

что в памяти будет выглядеть следующим образом:

pprgs -> pprgs -> (pprgs)[0] (pprgs)[1] (pprgs)[2] (pprgs)[3]

Для того чтобы показать, что параметр является массивом указателей на массивы экземпляров, нужно написать следующее:

HRESULT Method22([in, size_is(3,4)] short **rgrgs);

что в памяти будет выглядеть примерно так:

rgrgs -> rgrgs[0] -> rgrgs[0][0] rgrgs[0][1] rgrgs[0][2] rgrgs[0][3] rgrgs[1] -> rgrgs[1][0] rgrgs[1][1] rgrgs[1][2] rgrgs[1][3] rgrgs[2] -> rgrgs[2][0] rgrgs[2][1] rgrgs[2][2] rgrgs[2][3]

Данный синтаксис, быть может, оставляет желать лучшего, тем не менее, он обладает большей гибкостью и меньшей неоднозначностью, чем на С.

Важно отметить, что приведенный выше метод IDL задает многомерный массив; формально он представляет собой массив указателей на массив указателей на экземпляры.


Это не то же самое, что многомерный массив в языке С, который может быть определен в IDL с использованием стандартного синтаксиса С:

HRESULT Method23([in] short rgrgs[3][4]);

Данный синтаксис предполагает, что все элементы массива будут размещены в памяти непрерывно, что определенно не совпадает с предположением предыдущего примера.

Допускается задавать первое измерение многомерного массива с помощью атрибута [size_is]:

HRESULT Method24([in, size_is(3)] short rgrgs[][4]);

однако нельзя задавать никакого иного измерения, кроме крайнего левого.

Выражения, использованные атрибутами [size_is], [length_is] и другими атрибутами задания размерности массива, не могут быть размещены в вызовах функций. При этом, например, стал бы затруднительным маршалинг строк, соответствие и/или переменная длина которых размещены в вызовах функций wcslen или strlen. Это означает, что такой код в IDL является недопустимым:

HRESULT Method24([in, size_is(wcslen(wsz) + 1)] const OLECHAR *wsz);

Поскольку это ограничение сделало бы использование строк чрезвычайно неудобным для клиентских программ, IDL поддерживает строковый атрибут, который требует на уровне маршалинга вызывать соответствующую функцию xxxlen для вычисления соответствия массива. Ниже приведено правильное задание строки в качестве входного параметра:

HRESULT Method25([in, string] const OLECHAR *wsz);

или:

HRESULT Method26([in, string] const OLECHAR wsz[]);

При использовании строк в качестве выходных или входных/выходных параметров почти всегда целесообразно явно задавать длину буфера вызывающей программы, для гарантии того, что он будет достаточно большим на стороне сервера. Рассмотрим следующий полный ошибок код IDL:

HRESULT Method27([in, out, string] OLECHAR *pwsz);

Если вызывающая программа запускает этот метод с помощью достаточно короткой строки:

void f(IFoo *pFoo) { OLECHAR wsz[1024]; wcscpy(wsz, OLESTR("Hello")); pFoo->Method27(wsz); // .. process updated string // .. обрабатываем обновленную строку }



то длина массива, размещенного на стороне сервера, будет вычислена, исходя из длины входной строки (эта длина равна шести с учетом заключительного нулевого символа). Рассмотрим следующую реализацию метода со стороны сервера:

HRESULT CFoo::Method27(OLECHAR *wsz) { DisplayString(wsz); // wsz only can hold 6 characters! // wsz может хранить только 6 символов! wcscpy(wsz, OLESTR("Goodbye")); return S_OK; }

Поскольку соответствие массива основывалось на величине wcslen(OLESTR("Hello")+1), то, когда реализация метода перезапишет в данную строку что-то более длинное, "хвост" этой строки перезапишет случайное число байтов памяти, что приведет к неисправимым ошибкам (будем надеяться, еще до выпуска данной программы в свет). Это означает, что, хотя вызывающая программа и имела достаточно памяти, заранее выделенной для записи результирующей строки, уровень маршалинга со стороны сервера не знал об этой кажущейся внешней памяти и выделил место, достаточное для хранения только шести символов строки Unicode. Код на IDL должен был быть таким:

HRESULT Method28([in] long cchMax, [in, out, string, size_is(cchMax)] OLECHAR *wsz);

а вызывающая программа могла бы использовать это так:

void f(IFoo *pFoo) { OLECHAR wsz[1024]; wcscpy(wsz, OLESTR("Hello")); pFoo->Method28(1024, wsz); // .. process updated string // .. обрабатываем обновленную строку }

Наиболее неприятным аспектом примера c [in, out, string] является то, что он прекрасно работает, когда входная строка имеет по крайней мере такую же длину, как выходная строка. Ошибки, связанные с этим методом, будут периодическими и могут ни разу не возникнуть на стадии тестирования проекта.

В большинстве обычных API-функций, когда функция возвращает в вызывающую программу данные переменной длины, вызывающая программа заранее выделяет буфер для хранения результатов функции, а реализация функции заполняет буфер, заготовленный вызывающей программой. Ответственность за задание правильного размера буфера лежит на вызывающей программе.


При использовании заданных вызывающей программой буферов для возвращения структур данных переменной длины (таких, как строки) может возникнуть проблема. Возможно, реализация метода захочет возвратить больше данных, чем ожидает вызывающая программа. Рассмотрим следующий код Windows SDK, который отображает текст редактирующего управляющего элемента, то есть текстового окна, позволяющего набирать и редактировать текст:

void Show(HWND hwndEdit) { TCHAR sz[1024]; GetWindowText(hwndEdit, sz, 1024); MessageBox(0, sz, _TEXT("Hi!"), MB_OK); }

Заметим, что разработчик Show полагает, что редактирующий управляющий элемент никогда не будет содержать больше 1024 символов. Каким образом он или она узнали об этом? Самым точным образом. Можно было бы подумать, что такая реализация была бы надежнее:

void Show(HWND hwndEdit) { int cch = GetWindowTextLength(hwndEdit); TCHAR *psz = new TCHAR[cch+1]; GetWindowText(hwndEdit, psz, cch); MessageBox(0, sz, _TEXT("Hi!"), MB_OK); delete[] psz; }

но как в данном примере вызывающая программа может быть уверена, что пользователь не напечатает еще символ после вызова GetWindowTextLength, но до вызова GetWindowText? Тот факт, что размещение основано на потенциально устаревшей информации, делает данную идиому чувствительной к условиям гонки.

Предшествующие идиомы программирования, возможно, и годятся для HWND, но совершенно неприменимы для объектов СОМ. В отличие от HWND, к объектам СОМ весьма вероятен одновременный доступ со стороны многих участников. Кроме того, стоимость двух вызовов метода для выполнения одной операции, как показано выше, очень быстро уменьшила бы производительность, особенно в распределенной среде, где задержка, вызванная передачей и приемом пакетов информации, создает огромные проблемы при циклических вызовах метода. В силу этих двух факторов при передаче типов данных с переменной длиной из реализации метода в вызывающую программу через [out]-параметр правильно организованный интерфейс СОМ предписывает реализации метода выделить пространство для результата, используя СОМ-распределитель памяти задачи.


Это необходимо, поскольку фактический размер результата может быть известен только внутри реализации метода. Этот динамически выделенный буфер возвращается программе, вызвавшей метод, и после того, как буфер уже не нужен, вызывающая программа должна освободить этот буфер распределителем памяти задачи в вызываемом процессе. Чтобы выразить эту идиому для строкового параметра, приведем следующий корректно работающий код IDL:

HRESULT Method29([out, string] OLECHAR **ppwsz);

из которого следует такая реализация со стороны сервера:

HRESULT CFoo::Method29(OLECHAR **ppwsz) { const OLECHAR wsz[] = OLESTR("Goodbye"); int cb = (wcslen(wsz) + 1) * sizeof(OLECHAR); *ppwsz = (OLECHAR*)CoTaskMemAlloc(cb); if (*ppwsz == 0) return E_OUTOFMEMORY; wcscpy(*ppwsz, wsz); return S_OK; }

Для правильного использования этого метода необходим такой код со стороны клиента:

void f(IFoo *pFoo) { OLECHAR *pwsz = 0; if SUCCEEDED(pFoo->Method29(&pwsz)) { DisplayString(pwsz); CoTaskMemFree(pwsz); } }

Хотя, с одной стороны, применение этой технологии может привести к избыточному копированию памяти, с другой стороны, уменьшается время на прием-передачу и гарантируется, что могут быть возвращены строки любой длины, причем вызывающей программе не требуется связывать дополнительное пространство буфера в ожидании сколь угодно больших строк.

Синтаксис массива, приведенный в этом разделе, является совершенно разумным для программистов на С и C++. К сожалению, в то время, когда пишется этот текст, Visual Basic не способен работать ни с какими массивами переменной длины и может воспринимать только массивы фиксированной длины. Для того чтобы позволить Visual Basic посылать и получать массивы переменной длины, файлы СОМ IDL определяют среди прочих составной тип, именуемый SAFEARRAY. SAFEARRAY — это довольно редко используемая структура данных, которая позволяет передавать в качестве параметров многомерные массивы, совместимые с типом VARIANT. Для определения размеров массива SAFEARRAY в СОМ предусмотрен тип данных SAFEARRAYBOUND:



typedef struct tagSAFEARRAYBOUND { ULONG cElements; // size_is for dimension // size_is для размерности LONG lLbound; // min index for dimension (usually 0) // минимальный индекс для размерности (обычно 0) } SAFEARRAYBOUND;

Тип данных SAFEARRAY внутри использует совместимый массив типа SAFEARRAYBOUND, чтобы придать некоторую форму содержимому массива:

typedef struct tagSAFEARRAY { USHORT cDims; // # of dimensions // число измерений USHORT fFeatures; // flags describing contents // флаги, описывающие содержимое ULONG cbElements; // # of bytes per element // число байтов на элемент ULONG cLocks; // used to track memory usage // применяется для слежения за использованием памяти void* pvData; // actual elements // фактические элементы [size_is(cDims)] SAFEARRAYBOUND rgsabound[] } SAFEARRAY;

Приведенный выше IDL в действительности не используется для описания сетевого формата массивов SAFEARRAY, однако он используется для их программного описания.

Чтобы обеспечить пользователю максимальную гибкость в вопросах управления памятью, в СОМ определены следующие флаги, которые могут использоваться с полем fFeatures:

FADF_AUTO /* array is allocated on the stack */ /* массив размещен в стеке */

FADF_STATIC /* array is statically allocated */ /* массив размещен статически */

FADF_EMBEDDEO /* array is embedded in a structure */ /* массив вложен в структуру */

FADF_FIXEDSIZE /* may not be resized or reallocated */ /* не может изменить размеры или быть перемещен*/

FADF_BSTR /* an array of BSTRs */ /* массив из BSTR */

FADF_UNKNOWN /* an array of IUnknown* */ /* массив из IUnknown* */

FADF_DISPATCH /* an array of IDispatch* */ /* массив из IDispatch* */

FADF_VARIANT /* an array of VARIANTS */ /* массив из 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.


Содержание раздела