[Previous] [Next]

Операции над строками UNICODE_STRING

Операционная система Windows NT издавна ориентирована на использование так называемых "широких" символов (занимающих два байта), что, в отличие от символов ASCII, o которых будет сказано ниже, без особых затруднений обеспечивает поддержку всех типов алфавитов, включая поддержку языков Юго-Восточной Азии.

Собственно тип данных UNICODE_STRING описывается в пакете DDK следующим образом (см. заголовочный файл ntdef.h):

typedef struct _UNICODE_STRING {
	USHORT Length;        // Длина строки (в двухбайтных символах)
	USHORT MaximumLength; // Максимально возможная длина строки
	PWSTR Buffer;         // указатель на буфер с двухбайтными символами
} UNICODE_STRING, *PUNICODE_STRING;  

Иногда UNICODE_STRING определяется выражением "counted string", что довольно точно передает сущность этого типа данных, то есть — "счетная строка", строка, где поддерживается учет действующих символов.

Нетрудно догадаться, что у программистов, долгое время привыкавших к простым ASCII кодировкам и столь же несложным функциям, оперирующим ASCIIZ строками, переход к использованию кодировки Юникод не вызовет энтузиазма.

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

Работа с типом UNICODE_STRING покажется менее сложной, если смириться с тем, что не следует "трогать его руками" (примерно, как CString в MFC), и научиться использовать набор системных функций, предназначенный для работы с ним.

Прежде всего, простая буква 'L', примененная перед строкой символов, дает указание препроцессору, трактовать эту строку как строку "широких" символов (строку WCHAR, но еще — не UNICODE_STRING). Соответственно, перейти от обычного текста, набираемого на клавиатуре компьютера, к строке UNICODE_STRING можно так:

UNICODE_STRING myNewUString;
RtlInitUnicodeString( &myNewUString, L"My Unicode text example." );

В следующем примере посредником при инициализации UNICODE_STRING выступает тип данных ANSI_STRING (счетная строка однобайтных символов):

ANSI_STRING     myNewANSIString;
UNICODE_STRING  myNewUString;
RtlInitAnsiString( &myNewUString, "My second text example." );
RtlAnsiStringToUnicodeString(&myNewUString, &myNewANSIString, TRUE);

Третий параметр, указанный как TRUE, заставляет данную функцию выделять память под буфер двухбайтных символов. Соответственно, по окончании работы с UNICODE_STRING (а в данном случае — при выходе из текущей функции, поскольку myNewUString определена как локальная переменная) следует выполнить освобождение буфера двухбайтных символов вызовом RtlFreeUnicodeString. To же необходимо проделать и в первом примере. Более того, аналогичное требование и к типу данных ANSI_STRING, для которого следует использовать RtlFreeAnsiString.

Сам тип ANSI_STRING определяется следующим образом (см. ntdef.h):

typedef struct _STRING {
	USHORT Length;
	USHORT MaximumLength;
	PCHAR Buffer;    // Здесь 'PCHAR' - просто 'char' указатель
} STRING, *PSTRING;
typedef STRING ANSI_STRING; 

Очевидно, второй способ более трудоемкий, и им редко кто пользуется.

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

Таблица 7.33. Прототип вызова RtlAppendUnicodeStringToString

NTSTATUS RtlAppendUnicodeStringToString IRQL — любой (если это допускает тип памяти буферов двухбайтных символов)
Параметры Объединяет строки UNICODE_STRING
IN OUT PUNICODE_STRING Destination Указатель на строку-получатель
IN OUT PUNICODE_STRING AppendString Указатель на присоединяемую строку
Возвращаемое значение STATUS_SUCCESS — строка присоединена и длина строки получателя обновлена
STATUS_BUFFER_TOO_SMALL — слишком мал размер буфера двухбайтных символов строки-получателя

При анализе приведенного выше описания возникает правомерный вопрос: что предпринимать, если объединение строк завершилось неудачей по причине недостаточно большого буфера принимающей строки UNICODE_STRING? Достойного ответа в пакете DDK на этот вопрос просто нет. Если требуемый размер буфера не составит труда вычислить как

(Destination->Length + AppendString->Length) * sizeof(WCHAR) 

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

Таблица 7.34. Прототип вызова RtlCompareUnicodeString

LONG RtlCompareUnicodeString IRQL == PASSIVE_LEVEL
Параметры Выполняет сравнение строк UNICODE_STRING
IN PUNICODE_STRING pString1 Указатель на первую строку
IN PUNICODE_STRING pString2 Указатель на вторую строку
BOOLEAN CaseInSensitive TRUE — игнорировать регистр (верхний/нижний)
Возвращаемое значение 0 — строки идентичны
< 0 — строка 1 меньше строки 2

Таблица 7.35. Прототип вызова RtlEqualUnicodeString

BOOLEAN RtlEqualUnicodeString IRQL == PASSIVE_LEVEL
Параметры Выполняет сравнение строк UNICODE_STRING
IN PUNICODE_STRING pString1 Указатель на первую строку
IN PUNICODE_STRING pString2 Указатель на вторую строку
BOOLEAN CaseInSensitive TRUE — игнорировать регистр (верхний/нижний)
Возвращаемое значение TRUE — строки идентичны
FALSE — строки различаются

Таблица 7.36. Прототип вызова RtlInt64ToUnicodeString

NTSTATUS RtlInt64ToUnicodeString IRQL == PASSIVE_LEVEL
Параметры Преобразует число int64 в UNICODE_STRING
IN ULONGLONG Value Исходное число
IN ULONG Base Формат: 16 — шестнадцатеричный, 8 — октавный, 2 — двоичный, 0 или 10 — десятичный.
IN OUT PUNICODE_STRING s Строка UNICODE_STRING
Возвращаемое значение

STATUS_SUCCESS
STATUS_BUFFER_OVERFLOW — слишком мал размер буфера UNICODE_STRING
STATUS_INVALID_PARAMETER — ошибочен параметр Base

Аналогичный прототип вызова имеют также функции преобразования целых чисел без знака и указателей в строку UNICODE_STRING — RtlIntegerToUnicodeString и RtlIntPtrToUnicodeString, соответственно.

Таблица 7.37. Прототип вызова RtlUpcaseUnicodeString

NTSTATUS RtlUpcaseUnicodeString IRQL == PASSIVE_LEVEL
Параметры Преобразует все символы строки src в символы верхнего регистра
IN OUT OPTIONAL PUNICODE_STRING dest Указатель на строку с буфером, подготовленным для приема преобразованной строки или NULL (в последнем случае преобразование происходит по месту)
IN OUT PUNICODE_STRING src Исходная строка
IN BOOLEAN AllocateDstStringBuff Если TRUE — выделить буфер под результат преобразования (в этом случае его следует освободить вызовом RtlFreeUnicodeString по окончании работы с этой строкой)
Возвращаемое значение

STATUS_SUCCESS
STATUS_BUFFER_OVERFLOW — слишком мал размер буфера UNICODE_STRING
STATUS_INVALID_PARAMETER — ошибочен параметр Base

Операции над строками ANSI символов

Работа со строками символов ANSI (8-битными) более привычна для программистов. Такие средства тоже присутствуют в наборе встроенных функций пакета и в наборе системных вызовов RtlXxx.

Строками простых 8-битных символов, в которых окончание обозначено нулем (так называемые строки SZ, String, Zero end), можно манипулировать при помощи уцелевших встроенных функций strlen, memcpy, strcpy, strncpy, strcat и даже strstr. Набор функций, которыми можно попытаться воспользоваться, приведен в файле DDK XP в заголовочном файле inc\crt\string.h, хотя в официальной документации DDK они не упоминаются вовсе, так что к ним следует применять правила использования по-умолчанию. Например, не обращаться к строкам, размещенным в страничной памяти, на высоких уровнях IRQL и т.п. Другие функции, например, мощную sprintf или хотя бы atoi, компилятор DDK не предоставляет.

Вариант счетной строки обычных 8-битных символов вводится в пакете DDK как тип данных ANSI_STRING. Это аналогичная UNICODE_STRING структура данных, в которой собственно строка хранится по адресу, указанному в поле Buffer, как уже было показано выше. Длину строки можно взять из поля Length. Хотя в большинстве случаев вполне успешно проходит обращение со строкой по адресу Buffer, как если бы она была строкой SZ (заканчивающейся нулем), однако, строго говоря, надеяться на это не следует, поскольку документация DDK не указывает, что на это можно вполне рассчитывать.

Можно ли перейти от строки UNICODE_STRING к обычной строке 8-битных символов? Можно, но при этом произойдет потеря информации о языковой локализации. Ниже показан несложный способ, как перейти к счетной строке ANSI, а затем и к обычной строке SZ.

// Полагаем, что уже существует счетная строка UNICODE_STRING
// по адресу ptrToUNICODE_STRING
ANSI_STRING ansiStr; // стековая переменная (не инициализирована)
char        str[100];// стековая переменная


NTSTATUS stat =      // инициализируем строку ANSI
RtlUnicodeStringToAnsiString( &ansiStr, ptrToUNICODE_STRING, TRUE );

if(NT_SUCCESS(stat))
{
// Теперь поле ansiString.Buffer указывает на выделенный буфер,
// куда помещена строка ANSI символов, полученная из строки
// UNICODE_STRING, изначально находившейся по адресу, куда
// указывает переменная ptrToUNICODE_STRING.
// Поле ansiStr.Length уже содержит информацию о длине строки.
	int len= (ansiStr.Length > 99? 99: ansiStr.Length);
	strncpy(str, ansiStr.Buffer, len);
	str[len+1]=0;

	DbgPrint("SZ string = %s(len=%d).", str, len);

	// Освобождаем буфер, выделенный под хранение ANSI строки:
	RtlFreeAnsiString(&ansiStr);
} 

Заметим, что переменная str будет хранить строку 8-битных символов до выхода из данного программного модуля, поскольку str — локальная переменная.

Прототип вызова функции RtlUnicodeStringToAnsiString описан в таблице ниже. В том случае, если третий параметр указан как TRUE, то будет выполнено выделение памяти для хранения строки, полученной из строки UNICODE_STRING. Соответственно, по окончании использования строки ANSI_STRING следует освободить числящуюся за драйвером область памяти, что выполняется вызовом RtlFreeAnsiString.

Таблица 7.38. Прототип вызова RtlUnicodeStringToAnsiString

NTSTATUS RtlUnicodeStringToAnsiString IRQL == PASSIVE_LEVEL
Параметры Преобразует информацию строки UNICODE_STRING в строку ANSI_STRING
IN OUT PANSI_STRING ansiStringPtr Указатель на строку ANSI_STRING, которая должна быть получена в результате вызова
IN PUNICODE_STRING Source Указатель на исходную строку UNICODE_STRING
BOOLEAN AllocFlag TRUE — выделить память под буфер строки ANSI_STRING
Возвращаемое значение STATUS_SUCCESS или код ошибки

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