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

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 */