Издание четвертое windows ® для профессионалов создание эффективных Win32-приложений с учетом специфики 64-разрядной версии Windows



Pdf просмотр
страница6/68
Дата28.11.2016
Размер3.57 Mb.
Просмотров12730
Скачиваний0
1   2   3   4   5   6   7   8   9   ...   68
Строковые функции Windows
Windows предлагает внушительный набор функций, работающих со строками. Они похожи на строковые функции из библиотеки C, например на
strcpy и wcscpy. Однако функции Windows являются частью операционной системы, и многие ее компоненты используют именно их, а не аналоги из библиотеки C. Я советую отдать предпочтение функциям операционной системы. Это немного повысит быстродействие Вашего приложения. Дело в том, что к ним часто обращаются такие тяжеловесные процессы, как оболочка операционной системы (Explorer.exe), и скорее всего эти функции будут загружены в память еще до запуска Вашего приложения.
Данные функции доступны в Windows 2000 и Windows 98. Но Вы сможете вызывать их ив более ранних версиях Windows, если установите Internet Explorer версии или выше.
По классической схеме именования функций в операционных системах их имена состоят из символов нижнего и верхнего регистра и выглядят таки т. д. Для использования этих функций включите в программу заголовочный файл ShlWApi.h. Кроме того, как я уже говорил, каждая строковая функция существует в двух версиях — для ANSI и для Unicode (например,
StrCatA и StrCatW).
Поскольку это функции операционной системы, их имена автоматически преобразуются в нужную форму, если в исходном тексте Вашей программы перед ее сборкой определен UNICODE.

20
Ч АС Т Ь I
МАТЕРИАЛЫ ДЛЯ ОБЯЗАТЕЛЬНОГО ЧТЕНИЯ
Создание программ, способных использовать
и ANSI, и Unicode
Неплохая мысль — заранее подготовить свое приложение к Unicode, даже если Вы пока не планируете работать с этой кодировкой. Вот главное, что для этого нужно:
í
привыкайте к тому, что текстовые строки — это массивы символов, а не массивы байтов или значений типа
char;
í
используйте универсальные типы данных (вроде TCHAR или PTSTR) для текстовых символов и строк;
í
используйте явные типы данных (вроде BYTE или PBYTE) для байтов, указателей на байты и буферов данных;
í
применяйте макрос _TEXT для определения символьных и строковых литералов;
í
предусмотрите возможность глобальных замен (например, PSTR на модифицируйте логику строковой арифметики. Например, функции обычно принимают размер буфера в символах, а не в байтах. Это значит, что вместо
sizeof(szBuffer) Вы должны передавать (sizeof(szBuffer) / sizeof(TCHAR)). Но блок памяти для строки известной длины выделяется в байтах, а не символах, те. вместо
malloc(nCharacters) нужно использовать malloc(nCharacters *
sizeof(TCHAR)). Из всего, что я перечислил, это запомнить труднее всего — если
Вы ошибетесь, компилятор не выдаст никаких предупреждений.
Разрабатывая программы примеры для первого издания книги, я сначала написал их так, что они компилировались только с использованием ANSI. Но, дойдя до этой главы (она была тогда в конце, понял, что Unicode лучше, и решил написать примеры, которые показывали бы, как легко создавать программы, компилируемые с применением и Unicode, и ANSI. В конце концов я преобразовал все программы примеры так, чтобы их можно было компилировать в расчете на любой из этих стандартов.
Конверсия всех программ заняла примерно 4 часа — неплохо, особенно если учесть, что у меня совсем не было опыта в этом деле.
В Windows есть набор функций для работы с Unicode строками. Эти функции перечислены ниже.
Функция
Описание
lstrcat
Выполняет конкатенацию строк
lstrcmp
Сравнивает две строки с учетом регистра букв
lstrcmpi
Сравнивает две строки без учета регистра букв
lstrcpy
Копирует строку в другой участок памяти
lstrlen
Возвращает длину строки в символах
Они реализованы как макросы, вызывающие либо Unicode , либо ANSI версию функции в зависимости оттого, определен ли UNICODE при компиляции исходного модуля. Например, если UNICODE не определен,
lstrcat раскрывается в lstrcatA, определен в
lstrcatW.
Строковые функции
lstrcmp и lstrcmpi ведут себя не так, каких аналоги из библиотеки и wcscmpi), которые просто сравнивают кодовые позиции в символах строк. Игнорируя фактические символы, они сравнивают числовое значение каждого символа первой строки с числовым значением символа второй

21
Г ЛАВА 2
UNICODE
строки. Но
lstrcmp и lstrcmpi реализованы через вызовы Windows функции Compare
String:
int CompareString(
LCID lcid,
DWORD fdwStyle,
PCWSTR pString1,
int cch1,
PCWSTR pString2,
int Она сравнивает две Unicode строки. Первый параметр задает так называемый идентификатор локализации (locale ID, LCID) — 32 битное значение, определяющее конкретный язык. С помощью этого идентификатора
CompareString сравнивает строки с учетом значения конкретных символов в данном языке. Так что она действует куда осмысленнее, чем функции библиотеки Когда любая из функций семейства
lstrcmp вызывает CompareString, в первом параметре передается результат вызова Windows функции
GetThreadLocale:
LCID Она возвращает уже упомянутый идентификатор, который назначается потоку в момент его создания.
Второй параметр функции
CompareString указывает флаги, модифицирующие метод сравнения строк. Допустимые флаги перечислены в следующей таблице.
Флаг
Действие
NORM_IGNORECASE
Различия в регистре букв игнорируются
NORM_IGNOREKANATYPE
Различия между знаками хираганы и катаканы игнорируются
NORM_IGNORENONSPACE
Знаки, отличные от пробелов, игнорируются
NORM_IGNORESYMBOLS
Символы, отличные от алфавитно цифровых, игнорируются
NORM_IGNOREWIDTH
Разница между одно и двухбайтовым представлением одного итого же символа игнорируется
SORT_STRINGSORT
Знаки препинания обрабатываются также, как и символы, отличные от алфавитно цифровых
Вызывая
CompareString, функция lstrcmp передает в параметре fdwStyle нуль, а
lstrcmpi — флаг NORM_IGNORECASE. Остальные четыре параметра определяют две строки и их длину. Если
cch1 равен –1, функция считает, что строка pString1 завершается нулевым символом, и автоматически вычисляет ее длину. Тоже относится и к параметрами pString2.
Многие функции C библиотеки с Unicode строками толком не работают. Так,
tolower и toupper неправильно преобразуют регистр букв со знаками ударения. Поэтому для Unicode строк лучше использовать соответствующие Windows функции. К
тому же они корректно работают и с ANSI строками.
Первые две функции CharLower(PTSTR pszString);
PTSTR CharUpper(PTSTR преобразуют либо отдельный символ, либо целую строку с нулевым символом в конце. Чтобы преобразовать всю строку, просто передайте ее адрес. Но, преобразуя отдельный символ, Вы должны передать его так:

22
Ч АС Т Ь I
МАТЕРИАЛЫ ДЛЯ ОБЯЗАТЕЛЬНОГО ЧТЕНИЯ cLowerCaseChr = CharLower((PTSTR) Приведение типа отдельного символа к PTSTR вызывает обнуление старших битов передаваемого значения, а в его младшие 16 битов помещается сам символ.
Обнаружив, что старшие 16 битов этого значения равны 0, функция поймет, что Вы хотите преобразовать не строку, а отдельный символ. Возвращаемое 32 битное значение содержит результат преобразования в младших 16 битах.
Следующие две функции аналогичны двум предыдущим за исключением того, что они преобразуют символы, содержащиеся в буфере (который не требуется завершать нулевым символом CharLowerBuff(
PTSTR pszString,
DWORD cchString);
DWORD CharUpperBuff(
PTSTR pszString,
DWORD Прочие функции библиотеки C (например,
isalpha, islower и isupper) возвращают значение, которое сообщает, является ли данный символ буквой, а также строчная она или прописная. В Windows API тоже есть подобные функции, но они учитывают и язык, выбранный пользователем в Control Panel:
BOOL IsCharAlpha(TCHAR ch);
BOOL IsCharAlphaNumeric(TCHAR ch);
BOOL IsCharLower(TCHAR ch);
BOOL IsCharUpper(TCHAR И последняя группа функций из библиотеки C, о которых я хотел рассказать, —
printf. Если при компиляции _UNICODE определен, они ожидают передачи всех символьных и строковых параметров в Unicode; вином случаев ввела в семейство функций
printf своей C библиотеки дополнительные типы полей, часть из которых не поддерживается в ANSI C. Они позволяют легко сравнивать и смешивать символы и строки с разной кодировкой. Также расширена функция операционной системы. Вот несколько примеров (обратите внимание на использование буквы
s в верхнем и нижнем регистре szA[100];
// строковый буфер в ANSI
WCHAR szW[100];
// строковый буфер в Unicode
// обычный вызов sprintf: все строки в ANSI
sprintf(szA, "%s", "ANSI Str");
// преобразуем строку изв обычный вызов swprintf: все строки в Unicode swprintf(szW, L"%s", L"Unicode Str");
// преобразуем строку изв
23
Г ЛАВА 2
UNICODE
Ресурсы
Компилятор ресурсов генерирует двоичное представление всех ресурсов, используемых Вашей программой. Строки в ресурсах (таблицы строк, шаблоны диалоговых окон, меню и др) всегда записываются в Unicode. Если в программе не определяется макрос UNICODE, Windows 98 и Windows 2000 сами проводят нужные преобразования. Например, если при компиляции исходного модуля UNICODE не определен, вызов на самом деле приводит к вызову LoadStringA, которая читает строку из ресурсов и преобразует ее в ANSI. Затем Вашей программе возвращается ANSI представление строки.
Текстовые файлы
Текстовых файлов в кодировке Unicode пока очень мало. Нив одном текстовом файле, поставляемом с операционными системами или другими программными продуктами, не используется Unicode. Думаю, однако, что эта тенденция изменится в будущем — пусть даже в отдаленном. Например, программа Notepad в Windows позволяет создавать или открывать как Unicode , таки файлы. Посмотрите на ее диалоговое окно Save As (рис. 2 2) и обратите внимание на предлагаемые форматы текстовых файлов.
Рис. 2-2.
Диалоговое окно Save As программы Notepad в Windows 2000
Многим приложениям, которые открывают и обрабатывают текстовые файлы (например, компиляторам, было бы удобнее, если после открытия файла можно было бы определить, содержит он символы вили в Unicode. В этом может помочь функция
IsTextUnicode:
DWORD IsTextUnicode(CONST PVOID pvBuffer, int cb, PINT Проблема с текстовыми файлами в том, что не существует четких и строгих правил относительно их содержимого. Это крайне затрудняет определение того, содержит файл символы вили в Unicode. Поэтому
IsTextUnicode применяет набор статистических и детерминистских методов для того, чтобы сделать взвешенное предположение о содержимом буфера. Поскольку тут больше алхимии, чем точной науки,
нет гарантий, что Вы не получите неверные результаты от
IsTextUnicode.
Первый ее параметр,
pvBuffer, указывает на буфер, подлежащий проверке. При этом используется указатель типа void, поскольку неизвестно, в какой кодировке данный массив символов.

24
Ч АС Т Ь I
МАТЕРИАЛЫ ДЛЯ ОБЯЗАТЕЛЬНОГО ЧТЕНИЯ
Параметр
cb определяет число байтов в буфере, на который указывает pvBuffer. Так как содержимое буфера неизвестно счетчик именно байтов, а не символов.
Заметьте: вовсе необязательно задавать всю длину буфера. Но чем больше байтов проанализирует функция, тем больше шансов получить правильный результат.
Параметр
pResult — это адрес целочисленной переменной, которую надо инициализировать перед вызовом функции. Ее значение сообщает, какие тесты должна провести. Если pResult равен NULL, функция IsTextUnicode делает все проверки. (Подробнее об этом см. документацию Platform Функция возвращает TRUE, если считает, что буфер содержит текст в Unicode, ив ином случае. Да да, она возвращает именно булево значение, хотя в прототипе указано DWORD. Если через целочисленную переменную, на которую указывает
pResult, были запрошены лишь определенные тесты, функция (перед возвратом управления) устанавливает ее биты в соответствии с результатами этих тестов.
В Windows 98 функция
IsTextUnicode по сути не реализована и просто возвращает последующий вызов
GetLastError дает код ошибки Применение функции
IsTextUnicode иллюстрирует программа пример см. главу 17).
Перекодировка строк изв и обратно
Windows функция
MultiByteToWideChar преобразует мультибайтовые символы строки в «широкобайтовые»:
int MultiByteToWideChar(
UINT uCodePage,
DWORD dwFlags,
PCSTR pMultiByteStr,
int cchMultiByte,
PWSTR pWideCharStr,
int Параметр
uCodePage задает номер кодовой страницы, связанной с мультибайто вой строкой. Параметр
dwFlags влияет на преобразование букв с диакритическими знаками. Обычно эти флаги не используются, и
dwFlags равен 0. Параметр pMultiByteStr
указывает на преобразуемую строку, а
cchMultiByte определяет ее длину в байтах. Функция самостоятельно определяет длину строки, если
cchMultiByte равен –1.
Unicode версия строки, полученная в результате преобразования, записывается в буфер по адресу, указанному в
pWideCharStr. Максимальный размер этого буфера (в символах) задается в параметре
cchWideChar. Если он равен 0, функция ничего не преобразует, а просто возвращает размер буфера, необходимого для сохранения результата преобразования. Обычно конверсия мультибайтовой строки в ее Unicode эквивалент проходит так:
1.
Вызывают
MultiByteToWideChar, передавая NULL в параметре pWideCharStr и 0 в параметре
cchWideChar.
2.
Выделяют блок памяти, достаточный для сохранения преобразованной строки. Его размер получают из предыдущего вызова
MultiByteToWideChar.

25
Г ЛАВА 2
UNICODE
3.
Снова вызывают
MultiByteToWideChar, на этот раз передавая адрес выделенного буфера в параметре
pWideCharStr, а размер буфера, полученный припер вом обращении к этой функции, — в параметре
cchWideChar.
4.
Работают с полученной строкой.
5.
Освобождают блок памяти, занятый Unicode строкой.
Обратное преобразование выполняет функция
WideCharToMultiByte:
int WideCharToMultiByte(
UINT uCodePage,
DWORD dwFlags,
PCWSTR pWideCharStr,
int cchWideChar,
PSTR pMultiByteStr,
int cchMultiByte,
PCSTR pDefaultChar,
PBOOL Она очень похожа на
MultiByteToWideChar. И опять uCodePage определяет кодовую страницу для строки — результата преобразования. Дополнительный контроль над процессом преобразования дает параметр
dwFlags. Его флаги влияют на символы с диакритическими знаками и на символы, которые система не может преобразовать.
Такой уровень контроля обычно ненужен, и
dwFlags приравнивается Параметр
pWideCharStr указывает адрес преобразуемой строки, аза дает ее длину в символах. Функция сама определяет длину исходной строки, если
cchWideChar равен –1.
Мультибайтовый вариант строки, полученный в результате преобразования, записывается в буфер, на который указывает
pMultiByteStr. Параметр cchMultiByte опреде ляет максимальный размер этого буфера в байтах. Передав нулевое значение в
cchMulti
Byte, Вы заставите функцию сообщить размер буфера, требуемого для записи результата. Обычно конверсия широкобайтовой строки в мультибайтовую проходит в той же последовательности, что и при обратном преобразовании.
Очевидно, Вы заметили, что
WideCharToMultiByte принимает на два параметра больше, чем
MultiByteToWideChar; это pDefaultChar и pfUsedDefaultChar. Функция WideChar
ToMultiByte использует их, только если встречает широкий символ, не представленный в кодовой странице, на которую ссылается
uCodePage. Если его преобразование невозможно, функция берет символ, на который указывает
pDefaultChar. Если этот параметр равен NULL (как обычно и бывает, функция использует системный символ по умолчанию. Таким символом обычно служит знак вопроса, что при операциях с именами файлов очень опасно, поскольку он является и символом подстановки.
Параметр
pfUsedDefaultChar указывает на переменную типа BOOL, которую функция устанавливает как TRUE, если хоть один символ из широкосимвольной строки не преобразован в свой мультибайтовый эквивалент. Если же все символы преобразованы успешно, функция устанавливает переменную как FALSE. Обычно Вы передаете в этом параметре.
Подробнее эти функции и их применение описаны в документации Platform Эти две функции позволяют легко создавать ANSI и Unicode версии других функций, работающих со строками. Например, у Вас есть DLL, содержащая функцию,
которая переставляет все символы строки в обратном порядке. Unicode версию этой функции можно было бы написать следующим образом.

26
Ч АС Т Ь I
МАТЕРИАЛЫ ДЛЯ ОБЯЗАТЕЛЬНОГО ЧТЕНИЯ StringReverseW(PWSTR pWideCharStr) {
// получаем указатель на последний символ в строке pEndOfStr = pWideCharStr + wcslen(pWideCharStr) 1;
wchar_t cCharT;
// повторяем, пока не дойдем до середины строки while (pWideCharStr < pEndOfStr) {
// записываем символ во временную переменную cCharT = *pWideCharStr;
// помещаем последний символ на место первого
*pWideCharStr = *pEndOfStr;
// копируем символ из временной переменной на место
// последнего символа
*pEndOfStr = cCharT;
// продвигаемся на 1 символ вправо pWideCharStr++
// продвигаемся на 1 символ влево pEndOfStr
;
}
// строка обращена; сообщаем об успешном завершении return(TRUE);
}
ANSI версию этой функции можно написать так, чтобы она вообще ничем не за нималась, а просто преобразовывала ANSI строку в Unicode, передавала ее в функцию
StringReverseW и конвертировала обращенную строку снова в ANSI. Тогда функция должна выглядеть примерно так StringReverseA(PSTR pMultiByteStr) {
PWSTR pWideCharStr;
int nLenOfWideCharStr;
BOOL fOk = FALSE;
// вычисляем количество символов, необходимых для хранения широкосимвольной версии строки nLenOfWideCharStr = MultiByteToWideChar(CP_ACP, 0,
pMultiByteStr, 1, NULL, 0);
// Выделяем память из стандартной кучи процесса достаточную для хранения широкосимвольной строки Не забудьте, что MultiByteToWideChar возвращает количество символов, а не байтов, поэтому мы должны умножить это число на размер широкого символа = HeapAlloc(GetProcessHeap(), 0,
nLenOfWideCharStr * sizeof(WCHAR));
if (pWideCharStr == NULL)
return(fOk);

27
Г ЛАВА преобразуем мультибайтовую строку в широкосимвольную
MultiByteToWideChar(CP_ACP, 0, pMultiByteStr, 1,
pWideCharStr, nLenOfWideCharStr);
// вызываем широкосимвольную версию этой функции для выполнения настоящей работы fOk = StringReverseW(pWideCharStr);
if (fOk) {
// преобразуем широкосимвольную строку обратно в мультибайтовую
WideCharToMultiByte(CP_ACP, 0, pWideCharStr, 1,
pMultiByteStr, strlen(pMultiByteStr), NULL, NULL);
}
// освобождаем память, выделенную под широкобайтовую строку, 0, И, наконец, в заголовочном файле, поставляемом вместе с DLL, прототипы этих функций были бы такими StringReverseW (PWSTR pWideCharStr);
BOOL StringReverseA (PSTR pMultiByteStr);
#ifdef UNICODE
#define StringReverse StringReverseW
#else
#define StringReverse StringReverseA
#endif // !UNICODE

28
Г ЛАВА 3
Объекты ядра
И
зучение Windows API мы начнем с объектов ядра и их описателей (handles). Эта глава посвящена сравнительно абстрактным концепциям, темы, не углубляясь в специфику тех или иных объектов ядра, рассмотрим их общие свойства.
Я бы предпочел начать с чего то более конкретного, но без четкого понимания объектов ядра Вам не стать настоящим профессионалом в области разработки Win dows программ. Эти объекты используются системой и нашими приложениями для управления множеством самых разных ресурсов процессами, потоками, файлами и т. д. Концепции, представленные здесь, будут встречаться на протяжении всей книги.
Однако я прекрасно понимаю, что часть материалов не уляжется у Вас в голове до тех пор, пока Вы не приступите к работе с объектами ядра, используя реальные функции.
И при чтении последующих глав книги Вы, наверное, будете время от времени возвращаться к этой главе.
Что такое объект ядра
Создание, открытие и прочие операции с объектами ядра станут для Вас, как разработчика приложений, повседневной рутиной. Система позволяет создавать и оперировать с несколькими типами таких объектов, в том числе маркерами доступа, файлами (file objects), проекциями файлов (file mapping objects), портами завершения ввода вывода (I/O completion port objects), заданиями objects), почтовыми ящиками (mailslot objects), мьютексами (mutex objects), каналами, процессами (process objects), семафорами (semaphore потоками (thread objects) и ожидаемыми таймерами (waitable timer objects). Эти объекты создаются Windows функциями. Например,
CreateFileMapping заставляет систему сформировать объект проекция файла. Каждый объект ядра — на самом деле просто блок памяти, выделенный ядром и доступный только ему. Этот блок представляет собой структуру данных, в элементах которой содержится информация об объекте.
Некоторые элементы (дескриптор защиты, счетчик числа пользователей и др) присутствуют во всех объектах, но бо
' льшая их часть специфична для объектов конкретного типа. Например, у объекта процесс есть идентификатор, базовый приоритет и код завершения, ау объекта файл — смещение в байтах, режим разделения и режим открытия.
Поскольку структуры объектов ядра доступны только ядру, приложение не может самостоятельно найти эти структуры в памяти и напрямую модифицировать их содержимое. Такое ограничение Microsoft ввела намеренно, чтобы ни одна программа не нарушила целостность структур объектов ядра. Это же ограничение позволяет вводить, убирать или изменять элементы структур, не нарушая работы каких либо приложений.
Но вот вопрос если мы не можем напрямую модифицировать эти структуры, то как же наши приложения оперируют с объектами ядра Ответ в том, что в Windows

29
Г ЛАВА 3
Объекты ядра предусмотрен набор функций, обрабатывающих структуры объектов ядра по строго определенным правилам. Мы получаем доступ к объектам ядра только через эти функции. Когда Вы вызываете функцию, создающую объект ядра, она возвращает описатель, идентифицирующий созданный объект. Описатель следует рассматривать как
«непрозрачное» значение, которое может быть использовано любым потоком Вашего процесса. Этот описатель Вы передаете Windows функциям, сообщая системе, какой объект ядра Вас интересует. Но об описателях мы поговорим позже (в этой главе).
Для большей надежности операционной системы Microsoft сделала так, чтобы значения описателей зависели от конкретного процесса. Поэтому, если Вы передадите такое значение (с помощью какого либо механизма межпроцессной связи) потоку другого процесса, любой вызов из того процесса со значением описателя, полученного в Вашем процессе, даст ошибку. Ноне волнуйтесь, в конце главы мы рассмотрим три механизма корректного использования несколькими процессами одного объекта ядра.
Учет пользователей объектов ядра
Объекты ядра принадлежат ядру, а не процессу. Иначе говоря, если Ваш процесс вызывает функцию, создающую объект ядра, а затем завершается, объект ядра может быть не разрушен. В большинстве случаев такой объект все же разрушается но если созданный Вами объект ядра используется другим процессом, ядро запретит разрушение объекта до тех пор, пока от него не откажется и тот процесс.
Ядру известно, сколько процессов использует конкретный объект ядра, поскольку в каждом объекте есть счетчик числа его пользователей. Этот счетчик — один из элементов данных, общих для всех типов объектов ядра. В момент создания объекта счетчику присваивается 1. Когда к существующему объекту ядра обращается другой процесс, счетчик увеличивается на 1. А когда какой то процесс завершается, счетчики всех используемых им объектов ядра автоматически уменьшаются на 1. Как только счетчик какого либо объекта обнуляется, ядро уничтожает этот объект.



Поделитесь с Вашими друзьями:
1   2   3   4   5   6   7   8   9   ...   68


База данных защищена авторским правом ©nethash.ru 2019
обратиться к администрации

войти | регистрация
    Главная страница


загрузить материал