Сообщений 9    Оценка 13        Оценить  
Система Orphus

BSTR в параметрах COM методов

Автор: Владислав Чистяков (VladD2)
The RSDN Group

Источник: RSDN Magazine #4-2004
Опубликовано: 28.10.2004
Исправлено: 10.12.2016

Q. Допустим, у меня есть такой метод

id(3), helpstring("returns some string")] 
HRESULT GetStringValue
  (BSTR SomeString, [out, retval] BSTR stringValue);

Я хочу в приведенном выше методе вернуть переданную мне в первом аргументе строку + "еще что-то". Если бы это был простой char* и char** тип, я бы сделал примерно так:

  ...
    GetStringValue("Some string",&stringValue);
    //stringValue="Some string  and anoter string"
    ...
void GetStringValue(char* SomeString, char** stringValue)
{
    char appendix[]=" and anoter string";
    int result_len=strlen(appendix);
    result_len+=strlen(SomeString);
    result_len++;
    *stringValue=(char*)malloc(result_len);
    memset(*stringValue,0,result_len);
    strcat(*stringValue,SomeString);
    strcat(*stringValue,appendix);
    return;
}

А вот с BSTR я никак не пойму что делать. Не могли бы вы подсказать, как приведенный выше код на АТЛ выглядит?

А. Сразу можно указать на ошибку и неточность (приводящую к ошибкам).

Сначала ошибка: [out, retval] BSTR stringValue

Когда параметры передаются как "out" или "in,out", то их обязательно нужно описывать как передаваемые по указателю, т.е.: [out, retval] BSTR * pbsValue. При этом память для строкового "out"-параметра должен выделить ты (для "in,out" только если нужно перезанять память, выделенную клиентом). Если бы это был параметр простого типа, то занимать ничего не пришлось бы, так как этот буфер должен разместить клиент. Подробнее см. далее.

Теперь неточность... Лучше всегда указывать тип ("in", "out", "in,out") при описании параметров в idl. Иначе ты будешь думать, что параметр - "in", а он может оказаться "in,out". Это может привести к неправильной работе с памятью.

Так что это описание лучше переписать так (то, что 1-й параметр "in", я понял из текста, приведенного ниже):

HRESULT GetStringValue([in] BSTR SomeString, [out, retval] BSTR * pbsValue);

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

Код на ATL может выглядеть так:

STDMETHODIMP CSomeClass::GetStringValue(BSTR SomeString, BSTR *stringValue)
{
  // Проверяем, что переданный параметр указывает на переменнуюif(!stringValue)
    return E_POINTER;

  // Создаем переменную-хелпер// bs - стандартный префикс для BSTR, а s я использую для указания // того что переменная является смарт-поинтером.
  CComBSTR sbsResult;

  // Конкатенируем к пустой строке первый параметр
  sbsResult.Append(SomeString);

  // Конкатенируем строковый литерал. OLECHAR раскрывается в L или в ничего// в зависимости от настроек компиляции (в Win32 всегда в L, но макрос // будет корректнее). 
  sbsResult.Append(OLESTR(" and anoter string"));

  // Отключаем BSTR от хелпера и копируем его в выходной параметр *stringValue// Если бы строка sbsResult была бы нам нужна далее, то вместо Detach нужно // было бы вызвать Copy().
  *stringValue = sbsResult.Detach();

  // Проверяем что память для строки выделена... паранойя конечно, но...if(!*stringValue)
    return E_OUTOFMEMORY;

  return S_OK; // Все в порядке!
}

// ... а теперь вызов...
CComBSTR sbsOut;
HRESULT hr = GetStringValue(CComBSTR(OLESTR("Some string")), &sbsOut);
if(FAILED(hr))
  return hr;
// Используем sbsOut...

Вместо CComBSTR и его метода Append можно вызывать COM-API-шный метод SysAllocStringLen (выделяющий память под BSTR). При этом память из строк нужно копировать как в случае с обыкновенными строками, но нельзя забывать, что BSTR имеет (обычно) размер символа 2 байта (sizeof(OLECHAR)). В принципе, через API можно написать более быстрый вариант, но коду будет больше, и он будет более опасным, а значит и возможность наделать ошибок будет выше...

Q. Вообще, когда речь идет о методах, возвращающих значения, и свойствах (get_), то указатель на что им передается в качестве аргумента? На пустое место?

A. Указатели в параметрах (вообще это тема большая и непростая, но я дам первое приближение... прошу учесть, что это - упрощенное объяснение) должен указывать на размещенный (клиентом) блок памяти. Но BSTR это тоже указатель! Причем "BSTR * " - это так называемый указатель первого уровня, а сам BSTR - вложенный. Они управляются разной логикой. Так, "BSTR * " должна разместить вызывающая сторона, а сам BSTR занимается вызываемой функцией (для "out"-параметра). "in"-параметры вообще не должны изменяться внутри функции (вернее с их представлением в стеке можно делать что угодно, но с памятью, на которую они могут указывать - ни-ни).

Вот правило для BSTR:

Память под строку (в любом случае) выделяется с помощью SysAllocStringXxx. Освобождается SysFreeString. Ну, или, как уже говорилось, можно использовать CComBSTR.


Эта статья опубликована в журнале RSDN Magazine #4-2004. Информацию о журнале можно найти здесь
    Сообщений 9    Оценка 13        Оценить