Партнерка на США и Канаду по недвижимости, выплаты в крипто

  • 30% recurring commission
  • Выплаты в USDT
  • Вывод каждую неделю
  • Комиссия до 5 лет за каждого referral

ФЕДЕРАЛЬНОЕ АГЕНСТВО ПО ОБРАЗОВАНИЮ

МОСКОВСКИЙ ИНЖЕНЕРНО-ФИЗИЧЕСКИЙ ИНСТИТУТ

(ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ)

Лабораторный практикум

«Основы разработки приложений
Windows»

Книга 2

Москва 2005

УДК 32.973.1

ББК 681.3

Ф59

Лабораторный практикум «Основы разработки приложений Windows». Книга 2. Уч. пособие.

М.:МИФИ, 20с.

Пособие предназначено для широкого круга читателей, приступающих к освоению программирования на языке С++ в операционной системе Windows. В первой части описано использование в прикладных программах таких средств системы Windows, как шрифты, таймеры, дочерние окна, растровые изображения и др. Уделено внимание специфическим возможностям 32-разрядных приложений – особенностям обслуживания файлов и организации параллельных вычислений.

Вторая часть пособия – описание лабораторного практикума по изучению основ разработки приложений Windows.

Предназначено для обучения студентов кафедры компьютерных медицинских систем факультета автоматики и электроники МИФИ по курсам “Информатика”, “Компьютерный практикум” и “Языки программирования и операционные системы”. Пособие может быть также полезно студентам, аспирантам и преподавателям, имеющим представление о языке С++ и желающим самостоятельно освоить принципы разработки прикладных программ, предназначенных для работы в системе Windows.

Рецензенты: и

Рекомендовано редсоветом МИФИ в качестве учебного пособия

© Московский инженерно-физический институт
(государственный университет), 2005

О г л а в л е н и е

Часть 1. Теоретические сведения................................. 4

НЕ нашли? Не то? Что вы ищете?

1. Основы архитектуры защищенного режима.............. 4

Регистры процессора............................................................. 4

Адресация памяти.................................................................. 9

2. Логические шрифты......................................................... 13

Создание логических шрифтов........................................... 13

Вывод на экран текстовых строк......................................... 17

3. Таймеры Windows.............................................................. 19

Организация и обслуживание таймеров............................. 19

Мультимедийные таймеры.................................................. 22

Измерение интервалов времени.................................. 22

Организация периодического процесса...................... 23

Задание однократного интервала времени................ 25

4. Дочерние окна.................................................................... 26

Создание и использование дочерних окон......................... 26

Окна предопределенных классов в главном окне.............. 33

5. Вывод растровых изображений.................................... 35

Процедура вывода растрового изображения...................... 35

Компоновка составных изображений................................. 42

6. Обслуживание файлов в 32-разрядных
приложениях
Windows......................................................... 44

Базовые операции с файлами.............................................. 45

Открытие и создание файла...................................... 45

Запись и чтение файла................................................ 47

Файлы, проецируемые в память.......................................... 50

7. Процессы и потоки............................................................ 54

Создание дочернего процесса............................................. 54

Создание дочернего потока................................................. 55

Синхронизация потоков....................................................... 61

Общие характеристики объектов Windows.............. 61

Синхронизация с помощью состояний потока.......... 63

Синхронизация с помощью событий.......................... 65

Критические секции и защита данных....................... 68

8. Библиотеки динамической компоновки................... 70

Часть 2. Лабораторный практикум........................ 73

Работы лабораторного практикума.................................... 73

Индивидуальные задания лабораторного практикума...... 88

Список литературы..........................................................

Часть 1
Теоретические сведения

1. основы архитектурЫ защищенного режима

Регистры процессора

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

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

Количество и назначение регистров процессора, как и механизм адресации памяти, зависит от типа процессора. Мы будем рассматривать 32-разрядные процессоры фирмы Intel (например Pentium) и совместимые с ними процессоры других фирм.

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

Процессор содержит 8 регистров общего назначения (рис. 1.1), носящих мнемонические обозначения EAX, EBX, ECX и т. д. Все они имеют размер по четыре байта, т. е. могут хранить по одному целому числу типа int или unsigned int.

Рис. 1.1. Регистры общего назначения

Для того чтобы программа могла работать с более короткими переменными (например, short – 2 байта или char – 1 байт), младшие половины регистров EAX, EBX, ECX и EDX имеют самостоятельные обозначения – AX, BX, CX и DX соответственно; при этом они, в свою очередь, еще разделены пополам с образованием регистров длиной всего 1 байт. Старшая половина (старший байт) регистра AX носит название AH (от high – старший), младший байт – AL (от low – младший). Аналогично построены имена остальных байтовых регистров.

Компилятор, преобразуя текст программы в машинные коды, активно использует регистры процессора для временного хранения операндов команд и результатов выполняемых операций. На рис. 1.2 показано, как может выглядеть простой фрагмент программы на языке С++ после компиляции. Видно, что компилятор выделил для хранения переменных x и y регистры EAX и EDX, и в тот же регистр EDX поместил результат сложения. В дальнейшем по ходу программы этот результат может быть перенесен в память, а регистр EDX использован для выполнения других операций.

int x=0x; mov EAX,h

int y=0x; mov EDX,h

int z=x+y; add EDX, EAX

Рис. 1.2. Фрагмент программы и результат его компиляции

Особое значение имеют сегментные регистры процессора (рис. 1.3). Свое название они получили потому, что любая программа располагается в связных участках памяти, называемых сегментами.

Рис. 1.3. Сегментные регистры

Как правило, для программы выделяются три сегмента: команд, данных и стека (рис. 1.4). В сегменте команд хранятся программные строки, в сегменте данных располагаются глобальные данные, которые видны из любой точки программы, а сегмент стека служит для временного хранения локальных данных, место под которые выделяется лишь на время выполнения той функции, в которой они объявлены.

Рис. 1.4. Сегменты программы и роль сегментных регистров

В сегментных регистрах хранится информация, позволяющая процессору определить, в каком месте памяти располагается тот или иной программный сегмент. Поскольку в программу обычно входят три сегмента, для обращения к ним используются три сегментных регистра. Регистр CS всегда служит для адресации сегмента команд, в регистре DS обычно хранится адрес сегмента данных, а регистр SS используется исключительно для работы со стеком. Остальные сегментные регистры используются по мере надобности для обращения, например, к системным полям памяти.

По содержимому сегментного регистра процессор может определить, каков адрес начала сегмента в памяти (этот адрес называется базовым). Однако ему еще нужно добраться до конкретного данного, хранящегося в сегменте. Для этого служит смещение, которое представляет собой номер начального байта конкретного данного от начала сегмента. Таким образом, адрес любого байта памяти состоит из двух частей: содержимого сегментного регистра, по которому процессор определяет базовый адрес сегмента, и смещения, которое следует прибавить к базовому адресу, чтобы получить истинный адрес адресуемой ячейки памяти.

Смещение очередной команды, которую надлежит выполнять процессору, всегда находится в регистре-указателе команд EIP. Он не входит в состав регистров общего назначения (и, соответственно, не был показан на рис. 1.1), потому что не является программно адресуемым регистром, т. е. к нему нельзя напрямую обратиться из программы и изменить его значение. Содержимым регистра EIP управляет исключительно сам процессор: прочитав из памяти очередной байт команды, процессор увеличивает содержимое EIP на 1, чем и обеспечивается строго последовательное чтение байтов программы, находящейся в памяти. Поскольку при обращении к сегменту команд процессор использует сегментный регистр CS, содержимое пары регистров CS и EIP, которое обычно обозначается, как CS:EIP, в каждый момент времени характеризует то место, до которого дошла программа по ходу своего выполнения.

Текущее смещение в стеке всегда находится в регистре-указате­ле стека ESP. Поскольку адрес стека процессор определяет с помощью сегментного регистра SS, содержимое пары регистров SS:ESP характеризует то место стека, в котором находится последнее отправленное туда данное.

К сегменту данных процессор обращается обычно с помощью регистра DS; смещение же конкретной ячейки может находиться в этом случае в любом регистре общего назначения: EAX, EBX и других (за исключением, разумеется, ESP). Смещение может также входить непосредственно в код команды, т. е. храниться не в регистре, а в составе кода команды в памяти.

Еще один регистр процессора, о котором следует иметь представление – это регистр флагов. В отличие от остальных регистров, в каждом из которых хранится единое данное, регистр флагов представляет собой, в основном, набор однобитовых флагов. Некоторые из этих флагов устанавливаются и сбрасываются программистом по мере необходимости (например, флаг IF управления аппаратными прерываниями процессора), хотя следует отметить, что в защищенном режиме обращение к этим флагам осуществляется, как правило, не непосредственно из прикладной программы, а из специальных программ, работающих на высшем уровне привилегий – так называемых драйверов.

Другими флагами управляет сам процессор, устанавливая или сбрасывая их по результатам выполнения каждой команды. Если, например, в результате выполнения арифметической операции образовалось отрицательное число, устанавливается флаг знака SF; если получился нулевой результат, устанавливается флаг нуля ZF и т. д. Анализ флагов позволяет процессору выполнять операции сравнения и условных переходов. Содержимое регистра флагов отображается в отладчике, и программист имеет возможность определить состояние флагов в любой точке программы.

Адресация памяти

Как уже отмечалось, адрес любого байта памяти состоит из двух компонентов – содержимого сегментного регистра (оно называется селектором) и смещения. Получение из этих двух компонентов истинного физического адреса памяти осуществляется процессором посредством довольно сложной цепочки преобразований (рис. 1.5).

Рис. 1.5. Цепочка преобразования адресов в защищенном режиме

При расшифровке кодов команд процессор имеет дело с содержимым сегментных регистров (селекторами) и смещениями, которые могут храниться в тех или иных регистрах или входить в коды команд в виде операндов. Их же наблюдает программист, отлаживая программу с помощью отладчика. Комбинацию селектора и смещения иногда называют виртуальным адресом.

Получив из программы виртуальный адрес, процессор преобразует его в линейный. Делается это следующим образом.

В состав селекторов входят номера (индексы) ячеек специальной таблицы, содержащей дескрипторы сегментов программы. Эта таблица заранее строится операционной системой и размещается в системных полях памяти. Каждый дескриптор таблицы дескрипторов имеет размер 8 байт, и в нем хранятся все характеристики, необходимые процессору для обслуживания этого сегмента: базовый линейный адрес сегмента, его граница, которая представляет собой номер последнего байта сегмента, а также атрибуты сегмента, определяющие его свойства (рис. 1.6). Таким образом, селекторы в конечном счете характеризуют сегменты памяти.

Рис. 1.6. Преобразование виртуального адреса в линейный

Процессор с помощью селектора определяет индекс дескриптора адресуемого сегмента, извлекает из него базовый линейный 32-разрядный адрес сегмента и, прибавив к нему 32-разрядное же смещение, образует адрес адресуемой ячейки памяти. Этот адрес называется линейным, потому что в отличие от виртуального адреса, состоящего из двух частей, он представляет собой одно 32-разрядное число, которое может принимать, в принципе, любое значение от 0 до 0xFFFFFFFF, т. е. до 4 Г – 1.

Существенной характеристикой 32-разрядных приложений Windows является то, что они работают в так называемой плоской модели памяти (иногда для обозначения плоской памяти используют англоязычный термин “FLAT”, являющийся, кстати, ключевым словом некоторых языков программирования). Базовым адресам всех сегментов, хранящимся в таблице дескрипторов, присваивается значение 0, а границам сегментов – значение 4 Г – 1. Таким образом, все сегменты в плоской модели памяти имеют размер 4 Гбайт и накладываются друг на друга. Однако различным участкам программ (как прикладных, так и системных) назначаются различные смещения. Поскольку базовые адреса всех сегментов равны 0, смещения, действующие в программе, совпадают с линейными адресами. Это дает основание говорить, что 32-разрядные программы работают в линейном пространстве адресов.

Из всего пространства линейных адресов система Windows выделяет первые два гигабайта для прикладных программ, а вторые два гигабайта – для системных (рис. 1.7).

Рис. 1.7.Распределение линейного адресного пространства

Прикладные программы размещаются начиная с 5-го мегабайта линейного пространства, т. е. с адреса 0x400000 (первые 4 мегабайта зарезервированы для системного использования), поэтому функция WinMain может начинаться, например, с адреса 0x40107C, глобальные данные размещаться по адресам, начинающимся с 0x а стек начинаться с адреса 0x68FE30.

Реально размер прикладных программ редко превышает несколько единиц или десятков мегабайт (часто гораздо меньше), поэтому “залезть” во второй гигабайт прикладная программа может лишь в том случае, если она обрабатывает очень большие массивы данных, например массив из 250 миллионов целых чисел, который займет в памяти, действительно, около одного гигабайта.

Большая часть системных программ располагается в 4-м гигабайте. Поэтому линейные адреса (и смещения) системных программ обычно имеют значения, начинающиеся с 0xC (например, 0xC0141A90).

Получив линейный адрес адресуемого байта, процессор с помощью таблиц страниц (их еще называют таблицами страничной трансляции) преобразует его в 32-разрядный физический адрес того байта памяти, где находится или, точнее, с которого начинается конкретная команда или конкретное данное. Этот адрес зависит от объема памяти, установленной на компьютере, и может располагаться в любом месте физической памяти (кроме 1-го мегабайта, где размещаются программы MS-DOS, видеопамять и ПЗУ BIOS).

Страничная трансляция представляет собой довольно сложный механизм, в котором принимают участие аппаратные средства процессора и находящиеся в памяти таблицы страничной трансляции. Упрощенно процедуру страничной трансляции можно представить следующим образом.

Все пространство линейных адресов так же, как и пространство физических адресов реально установленной на компьютере памяти, делится на страницы объемом по 4 Кбайт. Каждой странице линейных адресов соответствует своя ячейка, или запись в таблице страниц. В эту ячейку (она имеет размер всего 4 байта) система заносит физический адрес той страницы памяти, в которую в настоящий момент загружена часть программы, соответствующая данной странице линейных адресов, чем и осуществляется отображение смещений программы на физические адреса памяти (рис. 1.8).

Если в процессе поддержки многозадачного режима система выгрузит из памяти текущую программу (или ее часть), в соответствующих ячейках таблицы страниц делается отметка “не загружена”, и связь между линейными и физическими адресами для данной программы разрывается. В дальнейшем, при повторной загрузке той же программы, она может попасть уже в другие участки физической памяти, и система соответствующим образом модифицирует таблицу страниц. В результате физические адреса, по которым загружаются части программы, в процессе работы системы динамически изменяются (и, разумеется, на разных компьютерах будут разными), в то время, как линейные адреса (смещения) конкретной программы, назначаемые ей в процессе компиляции, жестко фиксированы.

Рис. 1.8. Страничная трансляция адресов

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

2. Логические шрифты

Создание логических шрифтов

Разрабатывая изобразительные детали приложения, естественно воспользоваться богатыми возможностями, предоставляемыми системой Windows в части шрифтового оформления документов. Для того чтобы создать красивый и наглядный документ или экранный кадр, приходится использовать шрифты с различным начертанием, оформлением (курсив, жирный, подчеркнутый), размером и пространственной ориентацией. В зависимости от принципа хранения в памяти компьютера формы символов различают растровые (точечные) и масштабируемые шрифты, которые еще называют шрифтами TrueType. Достоинство шрифтов TrueType заключается в том, что они позволяют изменять в широких пределах раз­мер и другие характеристики символов (например, ширину букв) без снижения качества изображения, что и обусловило их широкое применение.

Операционная система Windows поставляется с базовым набором растровых и масштабируемых шрифтов с разнообразными начертаниями и характеристиками. Среди них имеются как шрифты с равной шириной всех символов (они традиционно используются, например, в исходных текстах программ), так и более приятные для глаза пропорциональные шрифты, у которых ширина символа зависит от его формы (буква “Ш”, например, шире символа “1”). Многие прикладные программы при их установке расширяют базовый набор Windows, добавляя свои шрифты; наконец, можно приобрести и установить в системе наборы шрифтов независимых разработчиков.

Работая с какой-либо коммерческой прикладной программой, использующей шрифты, например с текстовым редактором Microsoft Word или графическим редактором CorelDraw, пользователь может выбирать для оформления документа любые шрифты из установленных в системе, назначая им требуемые характеристики (размер, интервал между символами и строками т. д.) с помощью средств используемого редактора. Сложнее обстоит дело при разработке собственного приложения Windows. На экран можно вывести только тот шрифт, дескриптор которого загружен в контекст устройства; при необходимости изменить характеристики шрифта надо сначала создать новый шрифт, хотя под этим обманчивым термином понимается не разработка собственного шрифта, а выбор одного из шрифтов, установленных в системе, и придание ему требуемых характеристик.

Правда, в системе имеется несколько готовых шрифтов, дескрипторы которых можно получить со “склада” Windows с помощью макроса SelectFont() или обобщенной функции Select­Object(). Дескриптор одного из них по умолчанию загружается в контекст устройства при его создании. Однако возможности таких шрифтов ограничены, так как они не допускают изменения своих характеристик (кроме цвета).

Процедура создания нового шрифта довольно проста. Для этого нужно объявить в программе структурную переменную типа LOGFONT, заполнить ее поля требуемыми значениями и вызвать функцию Windows CreateFontIndirect(). Эта функция вернет дескриптор нового шрифта; после выбора полученного дескриптора в контекст устройства любая функция вывода на экран текста будет использовать именно этот шрифт. Если в приложении желательно использовать разные шрифты, их можно создать заранее и выбирать в контекст устройства по мере необходимости. Функция CreateFontIndirect() использует в качестве исходного материала физический шрифт, хранящийся на диске в виде файла; результатом работы этой функции будет логический шрифт, дескриптор которого и загружается в контекст устройства. Вывести на экран текст непосредственно физическим шрифтом нельзя, так как функции GDI работают только с логическими шрифтами. Даже если мы хотим иметь шрифт с характеристиками, в точности соответствующими физическому шрифту, все равно из него сначала надо образовать логический шрифт (обнулив все члены структуры LOGFONT, кроме имени шрифта) и лишь затем им пользоваться.

Структура LOGFONT содержит много членов, однако обычно можно ограничиться заданием лишь небольшой их части. Следует только иметь в виду, что неправильная установка того или иного члена этой структуры может привести к весьма неприятным последствиям, так как Windows, не сумев создать в точности заказанный вами шрифт, будет пытаться подобрать наиболее подходящий; часто в этом случае подбирается шрифт, весьма далекий от заказанного.

Структуру LOGFONT целесообразно использовать для задания характеристик масштабируемых (TrueType) шрифтов; только в этом случае будут действовать такие, например, характеристики шрифта, как угол наклона или размер (растровые шрифты допускают изменение размера, но лишь в обусловленных пределах или при низком качестве увеличения).

Структура LOGFONT имеет следующий состав членов:

typedef struct tagLOGFONT

int lfHeight;//Высота

int lfWidth;//Средняя ширина; если=0, то по умолчанию

int lfEscapement;//Угол наклона в единицах 1/10 градуса

int lfOrientation;//Не используется

int lfWeight;//Насыщенность: FW_NORMAL (нормальная),

FW_BOLD (полужирный шрифт)

BYTE lfItalic;//Если=1, то курсив

BYTE lfUnderline;//Если=1, то подчеркивание

BYTE lfStrikeOut;//Если=1, то перечеркивание

BYTE lfCharSet;//Набор символов; обычно=0

BYTE lfOutPrecision;//Точность соответствия; обычно=0

BYTE lfClipPrecision;//Способ вырезки части символа;

//обычно=0

BYTE lfQuality;//Качество; обычно=0

BYTE lfPitchAndFamily;//Шаг и семейство; обычно=0

BYTE lfFaceName[LF_FACESIZE];//Имя начертания шрифта

} LOGFONT;

Наиболее важным является последний член описанной выше структуры, символьный массив lfFaceName. В него надо скопировать полное имя шрифта из числа шрифтов, установленных в Windows, например Times New Roman Cyr, Arial Cyr, Cou­rier New Cyr и т. д. Если в имени шрифта будет допущена хотя бы незначительная ошибка, Windows, скорее всего, создаст шрифт с совершенно другим начертанием. Обратите также внимание на то, что член lfFaceName описан в структуре LOGFONT, как символьный массив длиной LF_FACESIZE символов. В эту переменную следует занести не адрес имени шрифта, а саму символьную строку с именем шрифта. Поэтому этот член нельзя инициализировать оператором присваивания; придется воспользоваться функцией C++ strcpy() для копирования строк.

После того, как новые шрифты использованы для вывода на экран текстовых строк и необходимость в них отпала, их следует удалить, чтобы освободить занимаемую ими память. Это можно сделать и перед завершением приложения в функции OnDe­stroy() обработки сообщения WM_DESTROY с помощью макроса DeleteFont() или обобщенной функции DeleteObject(). Практически при завершении приложения Windows удаляет все созданные в нем объекты.

Вывод на экран текстовых строк

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

·  в функции OnCreate() обработки сообщения WM_CREATE создаются необходимые шрифты, а их дескрипторы запоминаются в глобальных переменных;

·  в функции OnPaint() обработки сообщений WM_PAINT с помощью макроса SelectFont() в контекст устройства вы­бирается дескриптор требуемого шрифта;

·  при необходимости в контексте устройства устанавливаются требуемые характеристики шрифта – цвет и прозрачность фона;

·  с помощью функций TextOut() или DrawText() в окно приложения выводятся требуемые строки.

Функция TextOut() позволяет вывести в окно приложения строку текста, начиная с точно заданной точки, координаты которой в виде номеров пикселов указываются в параметрах функции. Координаты относятся к верхнему левому углу знакоместа. Поскольку шрифты в Windows могут иметь различный размер, высота знакоместа обычно заранее неизвестна, и для того чтобы последовательные строки не накладывались друг на друга, необходимо с помощью структуры типа TEXTMETRIC и функции GetTextMet­rics() определить высоту символов и, исходя из этой величины, задавать координаты следующих строк. Эта процедура была описана в пособии [1].

Функция DrawText() позиционирует текст по-другому. Из ее прототипа, взятого из интерактивного справочника

int DrawText(HDC hDC, LPCTSTR lpString, int nCount,

LPRECT lpRect, UINT uFormat);

видно, что в качестве ее параметров выступают: дескриптор контекста устройства hDC, адрес выводимой строки lpString, длина выводимой строки nCount, адрес lpRect структурной переменной типа RECT с координатами прямоугольной области экрана, в которую выводится текст, а также константа форматирования текста uFormat.

Функция DrawText() выводит заданный текст в указанную прямоугольную область (которая может быть и гораздо больше выводимой строки по размеру), причем расположение текста внутри области определяется последним параметром функции. Использованная в приводимом ниже примере символическая константа DT_CENTER позиционирует текст по горизонтали по центру прямоугольника (рис. 2.1).

Рис. 2.1. Расположение текстовой строки в главном окне приложения при использовании функции DrawText()

Текст может быть позиционирован также по левому краю заданной прямоугольной области (константа DT_LEFT) или по правому краю (константа DT_RIGHT).

Если текст состоит из одной строки, а высота области превышает высоту текста, то строку можно позиционировать и по вертикали, для чего используются константы DT_VCENTER, DT_BOTTOM или DT_TOP. Позиционирование по вертикали требует явного объявления текста однострочным с помощью константы DT_SINGLE­LINE. Все константы позиционирования можно комбинировать с помощью побитовой операции ИЛИ (символ | языка С++).

Вывод текстовых строк с помощью функции DrawText() очень удобен в плане задания аккуратного расположения строк, хотя и оказывается более сложным, чем при использовании функции TextOut(). Ниже приведен текст функции OnPaint(), использованной при приложении, вывод которого приведен на рис. 2.1:

void OnPaint(HWND hwnd){

PAINTSTRUCT ps;

RECT r;//Прямоугольная область для вывода строки текста

char s[]="Строка текста";//Сама строка

HDC hdc=BeginPaint(hwnd,&ps);//Получим контекст устройства

r. left=5;//Задаем координаты прямоугольника

r. top=30;//в пределах главного окна

r. right=ps. rcPaint. right-5;//Ради симметрии по горизонтали

r. bottom=80;//Высота прямоугольника почти произвольна

SelectFont(hdc, hFont);//Выбираем крупный шрифт

//(созданный заранее)

SetBkMode(hdc, TRANSPARENT);//Назначаем прозрачность фона

DrawText(hdc, s,strlen(s),&r,//Собственно вывод строки

DT_CENTER|DT_VCENTER|DT_SINGLELINE);//Центрируем

EndPaint(hwnd,&ps);

}

Ширина окна здесь получена из структурной переменной ps, где она входит в состав элемента rcPaint типа RECT.

3. Таймеры Windows

Организация и обслуживание таймеров

В прикладных задачах часто возникает необходимость времен­нόй синхронизации тех или иных процессов. Представим себе, например, что компьютер используется для управления экспериментальной или производственной установкой. Тогда одной из функций управляющей программы может быть периодическое переключение текущего режима работы установки – значений действующих в ней электрических или магнитных полей, давлений, температур и т. д. В других случаях может использоваться “однократная” временная синхронизация, когда установка включается, отрабатывает заданное время и выключается по истечении установ­лен­ного временного интервала. Если интервал времени необходимо выдержать с высокой точностью, то для его задания приходится использовать специальные аппаратные средства (автономные или связанные с компьютером) – измерители временных интервалов; если же высокой точности не требуется, то вполне можно воспользоваться машинным таймером. Непосредственный доступ к физическому машинному таймеру, как и к другим аппаратным средствам компьютера, в Windows запрещен, однако Windows предоставляет прикладному программисту функции, позволяющие установить в приложении требуемое количество программных таймеров, с помощью которых приложение может обеспечить временную синхронизацию и задание временных интервалов.

Приложение устанавливает, или активизирует таймер, вызывая функцию SetTimer(). Эта функция имеет следующий прототип:

UINT SetTimer(

HWND hwnd,//Дескриптор окна, с которым связан этот таймер

UINT idTimer,//Идентификатор таймера

UINT uTimeout,//Период срабатывания таймера в миллисекундах

TIMERPROC tmprc//Прикладная функция обслуживания таймера

);

Через параметр hwnd системе Windows передается дескриптор окна, для которого устанавливается данный таймер. Сама функция SetTimer() может быть вызвана в любом месте программы, однако, указав дескриптор hwnd, мы связываем таймер с конкретным окном, в оконную функцию которого будут поступать сообщения WM_TIMER.

Параметр idTimer определяет номер, который мы присваиваем данному таймеру. При установке единственного таймера этот номер не имеет значения и на его месте можно указать 0, но при наличии нескольких таймеров (из которых один, например, генерирует периодические сигналы, а другой задает время измерения) номер, присвоенный таймеру, позволяет в дальнейшем определить, от какого именно таймера пришло данное сообщение WM_TIMER.

Параметр uTimeout задает период срабатывания данного таймера. Таймер после установки начинает периодически с интервалом uTimeout генерировать сообщения WM_TIMER, поступающие в окно hwnd. Этот временнόй интервал задается в миллисекундах, что, строго говоря, не имеет смысла, так как период отсчета времени программным таймером имеет величину около 50 мс.

Последний параметр, tmprc, дает возможность организовать обслуживание таймера несколько иначе. Параметр представляет собой имя CALLBACK-функции, которая должна быть определена в программе и содержать процедуру прикладной обработки прерываний от таймера. Если этот параметр указан, то при каждом срабатывании таймера система Windows будет непосредственно, в обход оконной функции, вызывать функцию tmprc(). Такой способ установки таймера можно использовать, например, в тех случаях, когда в приложении отсутствует главное окно, что, впрочем, не типично для приложений Windows. Если сообщения от таймера предполагается обрабатывать обычным образом, посредством оконной функции, то на месте последнего параметра функции SetTimer() указывается NULL.

Функция SetTimer() в случае своего успешного выполнения (нормальной установки таймера) возвращает номер данного таймера, т. е. фактически значение второго параметра.

Как уже отмечалось, таймер после своей установки начинает периодически генерировать сообщения с заданным интервалом; если таймер надо остановить, используется функция KillTimer() с дескриптором окна и номером таймера в качестве параметров. В частности, можно остановить таймер в прикладной функции обработки его сообщения. В этом случае мы получим режим задания однократного временнόго интервала.

Приведем в качестве примера скелетную схему программы, в которой требуется в течение заданного времени периодически выводить на экран некоторую информацию. Нас будут интересовать три прикладные функции: OnCreate(), в которой устанавливается таймер, OnTimer(), где периодически выполняются некоторые содержательные действия, например, чтение из установки текущего измеряемого значения, и OnPaint(), где это значение выводится в главное окно приложения. Поскольку эти функции будут вызываться в ответ на сообщения WM_CREATE, WM_TIMER и WM_PAINT, в составе оконной функции главного окна должны в этом случае присутствовать макросы HANDLE_MSG, соответствующие этим сообщениям.

Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 4 5 6