BSTR как параметр COM-методов
От: VladD2 Российская Империя www.nemerle.org
Дата: 12.12.01 03:04
Оценка: 23 (5) +1 :)
#Имя: FAQ.com.bstr
G>Владислав, спасибо большое за предыдущий ответ, можно Вас еще поспрашивать?

Да, пожалуйста. Причем можно смело на ты.

G>Если можно на примере:

G>Допустим у меня есть такой метод
G>
G>[id(3), helpstring("returns some string")] 
G>HRESULT GetStringValue
G>    (BSTR SomeString, [out, retval] BSTR stringValue);
G>


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

Сначала ошибка: [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 где префикс будет сбивать и путать программиста. Внутри программы лучше их давать.

G>(кстати, VB захотелось именно BSTR, это нормально?)


Да.

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

G>А вот с BSTR я никак не пойму что делать Парился с преобразованием типов и так и этак, то не компилируется, то потом черти что выводит. Владислав, если не трудно, не могли бы Вы подсказать как приведенный выше код на АТЛ выглядит. Я вот тут место даже приготовил


Удивительно, но тут описание правильное!

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 была бы нам нужна далее, то вместо Dettach нужно 
  // было бы вызвать Copy().
  *stringValue = sbsResult.Dettach();

  // Проверяем что память для строки выделена... паранойя конечно, но...
  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 можно написать более быстрый вариант, но коду будет больше, и он будет более опасным, а значит и возможность наделать ошибок будет выше...

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


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

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

[in] BSTR – память под строку занимает вызывающая сторона. Функция может только читать содержимое строки.
[b][out] BSTR * [/b]- память под строку занимает функция. Вызывающая сторона должна освободить занятую строку.
[in, out] BSTR * - память под строку занимает вызывающая сторона. Функция может освободить занятую память и выделить новую. После возврата управления ответственность за освобождение памяти несет вызывающая сторона.

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

G>Со свойствами (там где строки) SysAllocString() и SysReAllocString это стандартная техника?


Да. Но удобнее пользоваться хелпером CComBSTR.

VD>>Лучше просто читать MSDN. Там все есть.

G>Я ждал и боялся этого ответа Ну, читать так читать

Перебарывай страх и научись в нем искать то что нужно. На этом сайте есть хорошая статья как это делать. Найди и прочти.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.