Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Рихтер Дж., Назар К. - Windows via C C++. Программирование на языке Visual C++ - 2009

.pdf
Скачиваний:
6266
Добавлен:
13.08.2013
Размер:
31.38 Mб
Скачать

24 Часть I Материалы для обязательного чтения

Рис. 2-2. Состояние переменных перед вызовом _tcscpy_s

Поскольку длина строки «1234567890», копируемой в буфер szBuffer, точно равна размеру буфера (10 символов), концевой символ строки, «\0», в буфер не уместится. Не думайте, что значение result будет усечено (вызовом STRUNCATE) и последний символ, «9», просто не будет скопирован в буфер. Вместо этого возвращается ERANGE и переменные приходят в состояние, показанное на рис. 2-3.

Рис. 2-3. Состояние переменных после вызова _tcscpy_s

Есть и еще один «побочный эффект», который нельзя увидеть, не проанализировав содержимое ячеек памяти, расположенных сразу за szBuffer (рис. 2-4).

Рис. 2-4. Содержимое szBuffer после неудачного вызова _tcscpy_s

Первый символ установлен в «\0», а остальные байты теперь содержат значение 0xfd. В результате результирующая строка оказалась усеченной до пустой строки, а остальные байты буфера — занятыми значениями-заполнителями (0xfd).

Примечание. Если вам интересно, почему ячейки памяти между переменными заполнены значениями 0xcc (см. рис. 2-4), скажу: из-за автоматического обнаружения компилятором переполнения буфера во время выполнения (оно включается флагами /RTCs, /RTCu и /RTC1). Если код компилируется без флагов /RTCx, переменные sz* будут располагаться в памяти «вплотную». Помните, что всегда следует устанавливать эти флаги при компиляции — это позволит обнаружить еще не выявленные ошибки из-за переполнения буфера на ранних стадиях цикла разработки.

Глава 2. Работа с символами и строками.docx 25

Дополнительные возможности при работе со строками

Помимо безопасных строковых функций, библиотека С поддерживает ряд новых функций, предоставляющих дополнительные возможности при манипулировании строками. Например, с их помощью можно управлять символами-заполнителями и способом усечения строк. Естественно, поддерживаются как ANSI- (А), так и Unicode-версии (W) этих функций. Вот прототипы для некоторых их них:

HRESULT StringCchCat(PTSTR pszDest, size_t cchDest, PCTSTR pszSrc); HRESULT StringCchCatEx(PTSTR pszDest, size_t cchDest, PCTSTR pszSrc,

PTSTR *ppszDestEnd, size_t *pcchRemaining, DWORD dwFlags); HRESULT StringCchCopy(PTSTR pszDest, size_t cchDest, PCTSTR pszSrc); HRESULT StringCchCopyEx(PTSTR pszDest. size_t cchDest, PCTSTR pszSrc,

PTSTR *ppszDestEnd, size_t *pcchRemaining, DWORD dwFlags);

HRESULT StringCchPrintf(PTSTR pszDest, size_t cchDest,

PCTSTR pszFormat, …);

HRESULT StringCchPrintfEx(PTSTR pszDest, size_t cchDest,

PTSTR *ppszDestEnd, size_t *pcchRemaining, DWORD dwFlags,

PCTSTR pszFormat, …);

Несложно заметить строку «Cch» в именах показанных здесь методов: это сокращение от «Count of characters», обычно это значение получают при помощи макроса _countof. Также поддерживаются функции с именами, содержащими строку «Cb», такие как StringCbCat(Ex), StringCbCopy(Ex) и StringCbPrintf(Ex).

Эти функции принимают в виде параметра размер, выраженный в байтах, а не символах. Как правило, его значение получают с помощью оператора sizeof. Все эти функции возвращают HRESULT с одним из значений, перечисленных в табл.

2-2.

Табл. 2-2. Значения HRESULT для безопасных строковых функций

Значение

Описание

S_OK

Успешный вызов. В целевом буфере находится исходная

строка, оканчивающаяся знаками «\0»

 

STRSAFE_E_INVALID

Неудачный вызов. В параметрах передан NULL

_PARAMETER

 

STRSAFE_E_

Неудачный вызов. Исходная строка не уместилась в целе-

INSUFFICIENT_

вом буфере

BUFFER

 

В отличие от безопасных функций (функций с суффиксом _s в именах), эти функции усекают строку, если она не умещается в буфер. Об этом сви-

детельствует возврат STRSAFE_E_INSUFFICIENT_BUFFER. Из кода

StrSafe.h видно, что этот код имеет значение 0x8007007а, и макрос SUCCEEDED/FAILED считает его неудачей вызова. Однако в этом слу-

26 Часть I Материалы для обязательного чтения

чае копируется часть содержимого исходного буфера, способная уместиться в целевом буфере, при этом последним символом скопированной строки становится «\0». Таким образом, если в предыдущем примере использовать StringCchCopy вместо _tcscpy_s, то в szBuffer оказалась бы строка «012345678». В зависимости от поставленной задачи, усечение строки при копировании может быть допустимо, а, может, и нет. Именно поэтому данная ситуация по умолчанию считается неудачей вызова. Например, при сборке пути путем конкатенации строк усечение сделает полученный результат бесполезным. В случае же подготовки текстового сообщения для пользователя усечение может оказаться приемлемым вариантом. В любом случае, вам решать, что делать с усеченным при копировании результатом.

И последнее (по порядку, но не по важности): у многих показанных выше функций есть расширенная (Ex) версия. Расширенные версии принимают три дополнительных параметра, описанных в табл. 2-3.

Табл. 2-3. Параметры расширенных функций

Параметр

Описание

size_t* pcchRemaining

Указатель на переменную, представляющую число незанятых симво-

 

лов в целевом буфере (без учета копируемого концевого нуля, \0). На-

 

пример, при копировании одного символа в буфер длиной 10 символов

 

будет возвращено значение 9, из которых доступны лишь 8 (остальные

 

символы будут усечены). Если pcchRemaining = NULL, число незаня-

 

тых символов не возвращается

LPTSTR* ppszDestEnd

Значение ppszDestEnd, отличное от NULL, указывает на «\0», завер-

 

шающий строку в целевом буфере

DWORD dwFlags

Одно или несколько значений, разделенных знаком «|»

STRSAFE_FILL_

При успешном вызове использует младший байт dwFlags для заполне-

BEHIND_NULL

ния области буфера, оставшейся незанятой (после концевого «\0»).

 

Подробнее см. в примечании о STRSAFE_FILL_BYTE после таблицы

STRSAFE_IGNORE_

Заставляет обращаться с указателями на строки, содержащими NULL,

NULLS

как с пустыми строками (TEXT(“”))

STRSAFE_FILL_

При неудачном вызове использует младший байт dwFlags для заполне-

ON_FAILURE

ния целевого буфера (за исключением концевого «\0»), подробнее см. в

 

описании STRSAFE_FILL_BYTE после таблицы. В случае ошибки

 

STRSAFE_E_INSUFFICIENT_BUFFER все символы возвращаемой

 

строки замещаются байтами-заполнителями

STRSAFE NULL_

При неудачном вызове первым символом целевого буфера становится

ON_FAILURE

«\0», то есть в буфер заносится пустая строка (TEXT(“”)). В случае

 

ошибки STRSAFE_E_INS U FFI-CIENT_BUFFER любая усеченная

 

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

 

Глава 2. Работа с символами и строками.docx 27

 

 

Параметр

Описание

STRSAFE_NO_

Как и в случае STRSAFE_NULL_ON_FAILURE, при сбое функции в

TRUNCATION

целевой буфер заносится пустая строка (ТЕХТ (“”)). В случае ошибки

 

STRSAFE_E_INSUFFICIENT_BUF-FER любая усеченная строка будет

 

перезаписана

Примечание. Даже если установлен флаг STRSAFE_NO_TRUNCATION, в целевой буфер будут скопированы все символы исходной строки, которые в нем уместятся. Далее первый и последний символы в целевом буфере устанавливаются в «\0». Это важно только в ситуациях, когда по соображениям безопасности следует уничтожить ненужные данные.

В завершение вернемся к комментарию на стр. 21. Как видно из рис. 2-4, все ячейки целевого буфера, от «\0» и до конца, заменены значениями 0xfd. Расширенные (Ex) версии этих функций позволяют при желании отменить заполнение буфера, весьма затратное (особенно в случае большого буфера) в плане системных ресурсов. Если добавить STRSAFE_FILL_BEHIND_ NULL к dwFlag, остальные символы будут установлены в «\0». Если же заменить

STRSAFE_FILL_BEHIND_NULL макросом STRSAFE_FILL_BYTE, для заполне-

ния незанятой части целевого буфера будет использоваться заданный байт.

Строковые функции Windows

Windows предлагает внушительный набор функций, работающих со строками. Многие из них, такие как lstrcat и lstrcpy, считаются устаревшими и не рекомендуются к использованию, поскольку не позволяют обнаружить ошибок, возникающих из-за переполнения буфера. Кроме того, в ShlwApi.h определен ряд удобных строковых функций, таких как StrFormatKBSize и StrFormatByteSize, форматирующих числовые значения, связанные с работой операционной системы. Подробнее о строковых функциях оболочки см. по ссылке http://msdn2.microsoft.com/en-us/library/ms538658.aspx.

Операция сравнения строк используется при проверке равенства, а также при сортировке. Наилучшие функции для этой операции — CompareString(Ex) и CompareStringOriginal. CompareString(Ex) используется для сравнения строк, которые затем будут показаны пользователю, с учетом языковых особенностей. Вот про-

тотип функции CompareString.

int CompareString( LCID locale, DWORD dwCmdFlags, PCTSTR pString1,

28 Часть I Материалы для обязательного чтения

int cch1, PCTSTR pString2, int cch2);

Она сравнивает две строки. Первый параметр задает так называемый идентификатор локализации (locale ID, LCID) — 32-битное значение, определяющее конкретный язык, С помощью этого идентификатора CompareString сравнивает строки с учетом значения конкретных символов в данном языке. Так что она действует куда осмысленнее, чем функции библиотеки С. Однако, эта операция существенно медленнее сравнения по порядку. Получить LCID можно вызовом

Windows-функции GetThreadLocale:

LCID GetThreadLocale();

Второй параметр функции CompareString указывает флаги, модифицирующие метод сравнения строк. Допустимые флаги перечислены в следующей таблице.

Табл. 2-4. Флаги функции CompareString

Флаг

Действие

 

NORM_IGNORECASE

Различия в регистре букв

игнорируются

LINGUIS-

 

TIC_IGNORECASE

 

 

NORM_IGNOREKANATYPE

Различия между знаками хираганы и катаканы игнорируются

NORM_IGNORENONSPACE

Знаки, отличные от пробелов,

игнорируются

LINGUIS-

 

TIC_IGNOREDIACRITIC

 

 

NORM_ IGNORESYMBOLS

Символы, отличные от алфавитно-цифровых, игнорируются

NORM_IGNOREWIDTH

Разница между одно- и двухбайтовым представлением одного

 

и того же символа игнорируется

 

 

NORM_STRDVGSORT

Знаки препинания обрабатываются так же, как и символы, от-

 

личные от алфавитно-цифровых

 

 

Остальные четыре параметра CompareString задают две строки и их длину в символах (а не в байтах!), соответственно. Если передать в параметре cch1 отрицательное значение, функция рассчитывает длину строки pString1, предполагая, что эта строка оканчивается нулем. То же верно и для параметра cch2 в отношении строки pString2. Если вам нужны дополнительные возможности, связанные с особенностями различных языков, взгляните на функции CompareStringEx.

Для сравнения строк при программной генерации строковых элементов (путей, разделов и параметров реестра, XML-атрибутов и элементов) применяют функцию CompareStringOrdinal:

int CompareStringOrdinal( PCWSTR p8tring1,

Глава 2. Работа с символами и строками.docx 29

int cchCount1, PCWSTR pString2, int cchCount2, BOOL bIgnoreCase);

Эта функция выполняет сравнение строк как строк программы, то есть без учета региональных параметров, и потому работает быстро. Это наиболее полезная функция, поскольку текст программы, как правило, не виден конечным пользователям. Учтите, что она работает только с Unicode-строками.

Функции CompareString и CompareStringOrdinal возвращают значения, кото-

рые не похожи на значения, возвращаемые функциями вида *cmp из библиотеки С.

CompareString(Ordinal) возвращает 0 в случае сбоя; CSTR_LESS_THAN (значение 1), если pString1 меньше pString2; CSTR_EQUAL (значение 2), если pString1 = pString2; и CSTR_GREATER_THAN (значение 3), если pString1 больше pString2. Можно немного упростить себе жизнь, если (после успешного вызова) вычесть 2 из возвращаемого значения — получится результат, соответствующий значению, возвращаемому функциями из библиотеки С (-1,0, и +1).

Почему Unicode?

Разрабатывая приложение, вы определенно должны использовать преимущества Unicode, поскольку они:

упрощают локализацию приложений для распространения их на мировом рынке;

позволяют распространять единственный двоичный EXEили DLL-файл, поддерживающий все языки;

увеличивают эффективность приложений, поскольку с Unicode-строками код работает быстрее, используя при этом меньше памяти. Внутренне Windows работает только с Unicode-символами и строками, поэтому получив ANSI-символ или строку, Windows должна преобразовать ее в Unicode-эквивалент, выделив для этого память;

дают возможность вызывать все современные Windows-функции (некоторые Windows-функции поддерживают только Unicode-символы и строки);

облегчают интеграцию с СОМ, требующей использования Unicode-символов и строк;

гарантируют простую интеграцию вашего кода с .NET Framework (также требующей применения Unicode-символов и строк);

упрощают манипулирование собственными ресурсами приложений (которые также всегда хранятся в Unicode).

30 Часть I Материалы для обязательного чтения

Рекомендуемые приемы работы с символами и строками

В начале этого раздела я вкратце повторю правила, которых следует придерживаться при написании кода, а в конце дам несколько советов о том, как эффективнее манипулировать Unicode- и ANSI-строками. Даже если вы не планируете использовать Unicode сейчас, все же желательно создавать приложения в расчете на работу с Unicode. При разработке следуйте этим правилам:

старайтесь думать о тестовых строках как о массивах символов, а не массивах значений типа chars или массивах байтов;

используйте обобщенные типы данных (такие, как TCHAR/PTSTR) для текстовых строк и символов;

используйте явные типы данных (такие как BYTE и PBYTE) для хранения байтов, указателей на байты и буферов;

используйте для литералов макросы TEXT или _T но не применяйте их вперемешку — это вносит в код путаницу;

пользуйтесь глобальной заменой (например, для замены PSTR на PTSTR);

оптимизируйте выделение памяти для строк. Функции обычно принимают размер буфера, выраженный в символах, а не в байтах. Это означает, что переда-

вать следует _countof(szBuffer), а не sizeof(szBuffer). Кроме того, помните, что размер блока памяти, выделяемого для строк, включающих известное число символов, указывают в байтах. Следовательно, необходимо вызывать malloc(nCharacters* sizeof(TCHAR)), а не malloc(nCharacters). Это правило труднее всего запомнить, и компилятор никак не предупреждает о его нарушении, поэтому здесь придется весьма кстати макрос следующего вида:

#define chmalloc(nCharacters) (TCHAR*)malloc(nCharacters * sizeof(TCHAR))

избегайте функций из семейства printf, особенно при использовании типов полей %s и %S для преобразования между ANSI и Unicode. Вместо них исполь-

зуйте MultiByteToWideChar и WideCharToMultiByte, как показано ниже, в разде-

ле о перекодировке строк;

всегда определяйте символы UNICODE и _UNICODE одновременно либо не определяйте ни один из них.

Этих правил следует придерживаться при манипулировании строками:

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

не пользуйтесь небезопасными функциями библиотеки С для манипулирования строками (см. выше). Общее правило таково: не используй-

Глава 2. Работа с символами и строками.docx 31

те функцию, манипулирующую буфером, если она не принимает размер целевого буфера как параметр. Для этой цели библиотека С поддерживает новые функции, такие как memcpy_s, memmove_s, wmemcpy_s и wmemmove_s. Они доступны, если определен символ __STDC_WANT_SECURE_LIB__ (в CrtDefs.h он определен по умолчанию, не отменяйте это умолчание);

используйте преимущества флагов компилятора /GS

(http://msdn2.microsoft.com/en-us/library/aa290051(VS.71).aspx) и /RTCs, позво-

ляющих автоматически обнаруживать переполнение буфера;

не используйте для манипулирования строками методы Kernel32, такие как lstrcat и lstrcpy;

в коде приходится манипулировать двумя типами строк. Первый тип - это фрагменты текста программы, включая имена файлов, пути, XML-элементы и атрибуты, а также разделы и параметры реестра. Для работы с такими строками используют функцию CompareStringOrdinal, поскольку она не учитывает региональные параметры и потому работает очень быстро. Это — преимущество, поскольку ни регион, ни страна, в которой запущено приложение, не влияет на такие строки. Другой тип строк — это строки, отображаемые в пользователь-

ском интерфейсе. С ними работают при помощи функции CompareString(Ex), которая учитывает региональные параметры при сравнении строк.

Здесь у вас нет выбора: профессиональные разработчики просто не могут использовать в коде небезопасные функции, манипулирующие буфером. Именно по этой причине во всех примерах, приведенных в этой книге, используются безопасные функции из библиотеки С.

Перекодировка строк из Unicode в ANSI и обратно

Windows-функция MultiByteToWideChar преобразует мультибайтовые символы строки в «широкобайтовые»;

int MultiByteToWideChar( UINT uCodePage, DWORD dwFlags,

PCSTR pMultiByteStr, int cbMultiByte, PWSTR pWideCharStr, int cchWideChar);

Параметр uCodePage задает номер кодовой страницы, связанной с мультибайтовой строкой. Параметр dwFlags влияет на преобразование букв с диакритическими знаками. Обычно эти флаги не используются, и dwFlags равен 0 (подробнее о допустимых значениях этого флага см. в документации

MSDN по ссылке http://msdn2.microsoft.com/en-us/library/ms776413.aspx).

Параметр pMultiByteStr указывает на преобразуемую строку, а cchMultiByte

32 Часть I Материалы для обязательного чтения

определяет ее длину в байтах. Функция самостоятельно определяет длину строки,

если cchMultiByte равен -1.

Unicode-версия строки, полученная в результате преобразования, записывается в буфер по адресу указанному в pWideCharStr. Максимальный размер этого буфера (в символах) задается в параметре cchWideChar. Если он равен 0, функция ничего не преобразует, а просто возвращает размер буфера, необходимого для сохранения результата преобразования (с учетом концевого символа «\0»), Обычно конверсия мультибайтовой строки в ее Unicode-эквивалент проходит так:

1.Вызывают MultiByteToWideChar, передавая NULL в параметре pWideCharStr, 0 в параметре cchWideChar и -1 — в параметре cbMultiByte.

2.Выделяют блок памяти, достаточный для сохранения преобразованной строки. Его размер получают путем умножения результата предыдущего вызова MultiByteToWideChar на sizeof(wchar_t).

3.Снова вызывают MultiByteToWideChar, на этот раз передавая адрес выделенного буфера в параметре pWideCharStr, а размер буфера, полученный при первом обращении к этой функции, — в параметре cchWideChar.

4.Работают с полученной строкой.

5.Освобождают блок памяти, занятый Unicode-строкой.

Обратное преобразование выполняет функция WideCharToMultiByte.

int WideCharToMultiByte( UINT uCodePage, DWORD dwFlags, PCWSTR pWideCharStr, int cchWideChar, PSTR pMultiByteStr, int cbMultiByte, PCSTR pDefaultChar,

PB00L pfUsedDefaultChar);

Она очень похожа на MultiByteToWideChar. И опять uCodePage определяет кодовую страницу для строки — результата преобразования. Дополнительный контроль над процессом преобразования дает параметр dwFlags. Его флаги влияют на символы с диакритическими знаками и на символы, которые система не может преобразовать. Такой уровень контроля обычно не нужен, и dwFlags приравнивается 0.

Параметр pWideChar указывает адрес преобразуемой строки, а cchWideChar задает ее длину в символах. Функция сама определяет длину исходной строки, ес-

ли cchWideChar равен -1.

Мультибайтовый вариант строки, полученный в результате преобразования, записывается в буфер, на который указывает pMultiByteStr. Параметр cchMultiByte определяет максимальный размер этого буфера в байтах. Передав нулевое значение в cchMultiByte, вы заставите функцию сообщить размер буфера, требуемого для записи результата. Обычно

Глава 2. Работа с символами и строками.docx 33

конверсия широкобайтовой строки в мультибайтовую проходит в той же последовательности, что и при обратном преобразовании, за одним исключением: сразу возвращается размер буфера (в байтах) для хранения результатов преобразования.

Очевидно, вы заметили, что WideCharToMultiByte принимает на два параметра больше, чем MultiByteToWideChar, это pDefaultChar и pfUsedDefaultChar. Функ-

ция WideCharToMultiByte использует их, только если встречает широкий символ, не представленный в кодовой странице, на которую ссылается uCodePage. Если его преобразование невозможно, функция берет символ, на который указывает pDefaultChar. Если этот параметр равен NULL (как обычно и бывает), функция использует системный символ по умолчанию. Таким символом обычно служит знак вопроса, что при операциях с именами файлов очень опасно, поскольку он является и символом подстановки.

Параметр pfUsedDefaultChar указывает на переменную типа BOOL, которую функция устанавливает как TRUE, если хоть один символ из широкосимвольной строки не преобразован в свой мультибайтовый эквивалент. Если же все символы преобразованы успешно, функция устанавливает переменную как FALSE. Обычно вы передаете NULL в этом параметре.

Подробнее эти функции и их применение описаны в документации Platform SDK.

Экспорт DLL-функций для работы с ANSI и Unicode

Эти две функции позволяют легко создавать ANSI- и Unicode-версии других функций, работающих со строками. Например, у вас есть DLL, содержащая функцию, которая переставляет все символы строки в обратном порядке. Unicodeверсию этой функции можно было бы написать следующим образом.

BOOL StringReverseW(PWSTR pWideCharStr, DWORD cchLength) {

// получаем указатель на последний символ в строке

PWSTR pEndOfStr = pWideCharStr + wcsnlen_s(pWideCharStr , cchLength) - 1; wchar_t cCharT;

// повторяем, пока не дойдем до середины строки while (pWideCharStr < pEndOfStr) {

//записываем символ во временную переменную cCharT = *pWideCharStr;

//помещаем последний символ на место первого

*pWideCharStr = *pEndOfStr;

//копируем символ из временной переменной на место,

*pEndOfStr = cCharT;

Соседние файлы в предмете Программирование на C++