Строки BSTR

Аватар пользователя @mabu опубликовал

В языках программирования используются разные форматы для хранения строк, кто во что горазд. Для передачи строк в COM потребовался универсальный формат, не зависящий от языка программирования. Используй BSTR во всех интерфейсах, доступных из скриптов (то есть унаследованных от IDispatch).

BSTR расшифровывается как «Basic STRing». Такое название строка получила потому, что в Visual Basic именно так хранятся строки.

Внутреннее устройство

Тип данных BSTR

Посмотрим, как объявляется BSTR в заголовочных файлах. Открываем файл inc\win\wtypes.bi:

Type BSTR As OLECHAR Ptr
Type LPBSTR As BSTR Ptr

Выходит, что BSTR — это псевдоним для указателя на OLECHAR. Что такое OLECHAR выясняем в заголовочнике inc\win\wtypesbase.bi:

Type OLECHAR As WCHAR

И вновь отсылка. Смотрим определение WCHAR в файле inc\win\winnt.bi:

Type WCHAR As wchar_t

Осталось выяснить, что такое wchar_t. Определение этого типа находится в файле inc\crt\stddef.bi и зависит от конкретной платформы:

#ifdef __FB_DOS__
	' wchar_t в DOS — это беззнаковое 8‐битное целое
	Type wchar_t As UByte
#elseif defined( __FB_WIN32__ ) or defined( __FB_CYGWIN__ )
	' wchar_t в Windows — беззнаковое 16‐битное целое
	Type wchar_t As UShort
#else
	' wchar_t в Linux — знаковое 32‐битное целое
	Type wchar_t As Long
#endif

Таким образом, на Windows BSTR — это дополнительное имя для массива 16‐битных беззнаковых целых чисел. В данном случае эти числа интерпретируются как символы юникода в кодировке UTF-16 Little Endian.

Представление в памяти

BSTR — это составной тип данных. В нём выделяют три части.

ЧастьРазмерОписание
Префикс длиныSizeOf(UINT)Количество занимаемых байт символов строки данных без учёта нулевого символа
Строка данныхSizeOf(OLECHAR) * (число символов)Массив символов юникода, может содержать в себе нулевые символы
ОкончаниеSizeOf(OLECHAR)Завершающий нулевой символ

BSTR — это указатель. Этот указатель ссылается не на префикс, а на первый символ строки данных.

Схематичное представление строки в памяти в соответствии с кодировкой UTF-16 Little Endian:

             BSTR указывает на первый символ в массиве
               │
               ↓    Символы строки «Привет, Мир!»
╔═══════════╦═════╤═════╤═════╤═════╤═════╤═════╤═════╤═════╤═════╤═════╤═════╤═════╦═════╗
║     24    ║  П  │  р  │  и  │  в  │  е  │  т  │  ,  │     │  М  │  и  │  р  │  !  ║  \0 ║
╚═══════════╩═════╧═════╧═════╧═════╧═════╧═════╧═════╧═════╧═════╧═════╧═════╧═════╩═════╝
                    Байты строки «Привет, Мир!»
╔══╤══╤══╤══╦══╤══╤══╤══╤══╤══╤══╤══╤══╤══╤══╤══╤══╤══╤══╤══╤══╤══╤══╤══╤══╤══╤══╤══╦══╤══╗
║24│ 0│ 0│ 0║31│ 4│64│ 4│56│ 4│50│ 4│53│ 4│66│ 4│44│ 0│32│ 0│28│ 4│56│ 4│64│ 4│33│ 0║ 0│ 0║
╚══╧══╧══╧══╩══╧══╧══╧══╧══╧══╧══╧══╧══╧══╧══╧══╧══╧══╧══╧══╧══╧══╧══╧══╧══╧══╧══╧══╩══╧══╝
 ↑                                                                                    ↑
Префикс длины                                                   Завершающий нулевой символ

Такая реализация имеет ряд преимуществ:

Отличия WString и BSTR

Встроенная строка WString и строка BSTR похожи тем, что содержат в себе символы юникода. Однако у них есть некоторые отличия:

ОтличияWStringBSTR
СуществованиеМожет существовать как в стэке (статическая строка), так и в куче (динамическая строка)Существует только в куче
Ограничение длиныОграничена длиной буфера, а буфер — наличием свободной памятиОграничена 32‐битным числом, однако это число очень большое, и вряд ли понядобятся строки длиной 2 147 483 648 символов ‭‬
Начало строкиМожет указывать на любой символ в буфереВсегда указывает на первый символ в буфере
Нулевые символыНе может содержать в себе нулевые символы, так как нулевой символ является признаком конца строкиМожет содержать в себе нулевые символы
Конец строкиОпределяется завершающим нулевым символом, не входящим в строкуОпределяется длиной строки
СовместимосьНесовместима с BSTR, так как не содержит в себе префикс длиныСовместима с WString, так как заканчивается нулевым символом

Функции для работы с BSTR

Все функции располагаются в библиотеке OleAut32.dll. Для их использования требуется подключить следующий заголовочные файлы:

#include "windows.bi"
#include "win\ole2.bi"

Нам потребуются функции:

ПунктФункции
Создание строкиSysAllocString и SysAllocStringLen
Удаление строкиSysFreeString
Получение длиныSysStringLen и SysStringByteLen
ОбъединениеVarBstrCat

SysAllocString

Функция принимает на вход указатель на буфер WString и возвращает BSTR.

Declare Function SysAllocString( _
	ByVal As Const Wstring Ptr _
)As BSTR

В случае успеха возвращает BSTR, при ошибке — NULL.

Пример

Dim b As BSTR = SysAllocString("Привет, мир!")

SysAllocStringLen

Функция SysAllocStringLen принимает на вход указатель на буфер WString и количество символов, копируемых из буфера в будущую BSTR.

Declare Function SysAllocStringLen( _
	ByVal As Const Wstring Ptr, _
	ByVal As UINT _
)As BSTR

В случае успеха возвращает BSTR, при ошибке — NULL.

Пример

' Скопируется строка «Привет»
Dim b As BSTR = SysAllocStringLen("Привет, мир!", 6)

Обратное преобразование в строку

Достаточно привести данные BSTR к указателю на данные WString:

Dim b As BSTR = SysAllocString("Привет, мир!")
Dim pW As WString Ptr = b
Print *pW

SysStringLen и SysStringByteLen

Функция SysStringLen возвращает длину строки в символах без учёта завершающего нуля:

Declare Function SysStringLen( _
	ByVal As BSTR _
)As UINT

Функция SysStringByteLen возвращает занимаемую символами строки память в байтах без учёта завершающего нуля:

Declare Function SysStringLen( _
	ByVal As BSTR _
)As UINT

Пример

Dim b As BSTR = SysAllocStringLen("Привет, мир!")
Print "Длина строки", SysStringLen(b) ' Выведет 12
Print "Количество байт", SysStringLen(b) ' Выведет 24

SysFreeString

Когда строка больше не нужна, её следует удалить функцией SysFreeString.

Declare Sub SysFreeString( _
	ByVal As BSTR _
)

Функция не возвращает значений.

Пример

Dim b As BSTR = SysAllocString("Привет, мир!")
SysFreeString(b)

Копирование BSTR

Встроенной функции, копирующей BSTR нет, но её можно написать самостоятельно.

Dim b As BSTR = SysAllocString("Привет, мир!")
' Копирование
Dim copy As BSTR = SysAllocStringLen(b, SysStringLen(b))

VarBstrCat

Функция объединеняет две строки и возвращает результат выходящим параметром.

Declare Function VarBstrCat( _
	ByVal bstrLeft As BSTR, _
	ByVal bstrRight As BSTR, _
	ByVal pbstrResult As LPBSTR _
)As HRESULT

В случае успеха возвращает S_OK, в случае ошибки — ошибочный код HRESULT.

Пример

Dim bstrLeft As BSTR = SysAllocString("Привет, ")
Dim bstrRight As BSTR = SysAllocString("мир!")

Dim bstrCombine As BSTR = NULL
VarBstrCat(b1, b2, @bstrCombine)

Dim pB As WString Ptr = bstrCombine
Print *pB ' Выведет «Привет, мир!»

' Когда строки не нужны, их следует уничтожить
SysFreeString(bstrLeft)
SysFreeString(bstrRight)
SysFreeString(bstrCombine)

Выделение и освобождение памяти для BSTR

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

BSTR как возвращаемое значение функции

Функция имеет тип данных BSTR.

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

Пример

Где‐то объявлена функция:

Declare Function GetStringValue( _
	ByVal Value As Integer _
)As BSTR

Использующий функцию код:

' Получаем строку
Dim b As BSTR = GetStringValue(265)

' Мы должны сами убить строку
SysFreeString(b)

BSTR как входящий параметр функции

Такие параметры часто помечают атрибутом in и имеют тип данных BSTR.

Правило: память под строку выделяет вызывающая сторона, функция может только читать содержимое строки. Если функция внутри себя где‐нибудь сохраняет строку, она создаёт её копию.

Пример

Где‐то объявлена функция:

Declare Function MyWebBrowser.PutStatusText( _
	ByVal b As BSTR  _
)As HRESULT

Использующий функцию код:

' Сами выделяем память под строку
Dim bstrStatus As BSTR = SysAllocString("Успешно")

If bstrStatus <> NULL Then
	' Передаём строку в функцию
	pBrowser->PutStatusText(bstrStatus)
	' Сами очищаем память
	SysFreeString(bstrStatus)
End If

BSTR как выходящий параметр функции

Такие параметры часто помечены атрибутом out и имеют типы данных BSTR Ptr или LPBSTR. Если выходящий параметр трактуется как возвращаемое значение, то дополнительно указывается атрибут retval.

Выходящий параметр функция должна вернуть по указателю.

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

Пример

Где‐то объявлена функция:

Declare Function MyWebBrowser.GetStatusText( _
	ByVal pbstr As BSTR Ptr  _
)As HRESULT

Использующий функцию код:

Dim bstrStatus As BSTR
pBrowser->GetStatusText(@bstrStatus)

' Мы сами удаляем строку, которую вернула функция
SysFreeString(bstrStatus)

BSTR как входящий и выходящий параметр функции

Такие параметры часто помечены атрибутом in, out и имеют типы данных BSTR Ptr или LPBSTR. Если выходящий параметр трактуется как возвращаемое значение, то дополнительно указывается атрибут retval.

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

Общее правило: память под строку создаёт вызывающая сторона, функция может освободить занятую память и выделить новую. После возврата управления ответственность за освобождение памяти несет вызывающая сторона.

Пример

Реализация функции:

Function MyWebBrowser.GetStatusText( _
		ByVal InputString As BSTR, _
		ByVal ppReturnString As BSTR Ptr _
	)As HRESULT
	
	' Нам нужна строка InputString, сохраняем её себе
	this.CopyInputString = SysAllocStringLen(InputString, SysStringLen(InputString))
	
	' Проверяем, что переданный параметр указывает на переменную
	If ppReturnString = NULL Then
		Return E_POINTER
	End If
	
	' Выделяем память под строку
	*ppReturnString = SysAllocString("Успешно")
	
	' Проверяем что память для строки выделена
	If *ppReturnString = NULL Then
		Return E_OUTOFMEMORY
	End If
	
	Return S_OK
	
End Function