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

лекции / Shchupak_Yu._Win32_API_Razrabotka_prilozheniy_dlya_Windows

.pdf
Скачиваний:
0
Добавлен:
11.02.2026
Размер:
13.15 Mб
Скачать

Базовые концепции

21

 

 

Таблица 1.1. Некоторые часто используемые типы Win32

 

 

 

 

Тип данных

Описание

 

 

 

 

BOOL

Булевский тип (эквивалентный bool)

 

BYTE

Байт (8-битное целое без знака)

 

DWORD

32-битное целое без знака

 

HANDLE

Дескриптор объекта

 

HBITMAP

Дескриптор битмэпа (растрового изображения)

 

HBRUSH

Дескриптор кисти

 

HCURSOR

Дескриптор курсора

 

HDC

Дескриптор контекста устройства

 

HFONT

Дескриптор шрифта

 

HICON

Дескриптор иконки (пиктограммы)

 

HINSTANCE

Дескриптор экземпляра приложения

 

HMENU

Дескриптор меню

 

HPEN

Дескриптор пера

 

HWND

Дескриптор окна

 

INT

32-битное целое со знаком

 

LONG

32-битное целое со знаком

 

LPARAM

Тип, используемый для описания lParam, четвертого параметра оконной процедуры

LPCSTR

Указатель на константную C-строку1

 

LPCTSTR

LPCWSTR, если определен макрос UNICODE, и LPCSTR в противном случае

 

LPCWSTR

Указатель на константную Unicode-строку2

 

LPSTR

Указатель на C-строку

 

LPTSTR

LPWSTR, если определен макрос UNICODE, и LPSTR в противном случае

 

LPWSTR

Указатель на Unicode-строку

 

LRESULT

Значение типа LONG, возвращаемое оконной процедурой

 

NULL

((void*) 0)

 

TCHAR

Wchar_t (Unicode-символ), если определен макрос UNICODE, и char в противном

 

случае

 

UINT

32-битное целое без знака

 

WPARAM

Тип, используемый для описания wParam, третьего параметра оконной

 

 

процедуры

 

 

 

 

Архитектура, управляемая событиями

В основе взаимодействия программы с внешним миром и с операционной систе мой лежит концепция сообщений.

С точки зрения приложения, сообщение является уведомлением о том, что про изошло некоторое событие, которое может требовать, а может и не требовать

1C строка обязана завершаться нулевым байтом. В каждом байте содержится один символ в соответ ствии с кодировкой ANSI. Далее, если это не оговаривается особо, под строками будут подразуме ваться именно C строки.

2Unicode cтрока также завершается нулевым байтом, но для хранения каждого символа использует ся два байта. Чтобы использовать в проекте кодировку UNICODE, необходимо выполнять ряд пра вил, которые приводятся в разделе «Отображение текста» в главе 2.

22 Глава 1. «Hello, World!», или Первые шаги к пониманию концепции Windows

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

Сообщение — это структура данных, содержащая следующие элементы:

дескриптор окна, которому адресовано сообщение;

код (номер) сообщения;

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

Не следует забывать, что Windows — многозадачная операционная среда. Со общения от внешних источников, например от клавиатуры, адресуются в каждый конкретный момент времени только одному из работающих приложений, а именно — активному окну. Но каким же образом Windows играет роль диспет чера сообщений? Для этого с момента старта операционная система создает в па мяти глобальный объект, называемый системной очередью сообщений. Все сооб щения, генерируемые как аппаратурой, так и приложениями, помещаются в эту очередь. Windows периодически опрашивает эту очередь и, если она не пуста, по сылает очередное сообщение нужному адресату, определяемому при помощи дес криптора окна.

Сообщения, получаемые приложением, могут поступать асинхронно из разных источников. Например, приложение может работать с системным таймером, посы лающим ему сообщения с заданным интервалом, и одновременно оно должно быть готовым в любой момент получить любое сообщение от операционной системы. Чтобы не допустить потери сообщений, Windows одновременно с запуском прило жения создает глобальный объект, называемый очередью сообщений приложения. Время жизни этого объекта совпадает с временем жизни приложения.

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

аппаратное событие → системная очередь сообщений → очередь сообщений приложения.

Оконная процедура

Для понимания механизма обработки приложением поступающих к нему сооб щений сначала нужно объяснить, что такое «оконная процедура».

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

Если вы прочли только что сделанное определение, не споткнувшись ни на од ном из терминов, значит, вы — не новичок в программировании для Windows. Для тех же, кто видит эти термины впервые, нужны дополнительные разъяснения.

Начнем с понятия «функция обратного вызова». Так называют функции, ко торые вызывает сама операционная система. Поэтому в коде приложения вы не найдете прямого вызова такой функции. Компилятор узнает функцию обратного вызова по спецификатору CALLBACK. Оконная процедура обычно имеет заголовок со стандартным синтаксисом:

LRESULT CALLBACK Имя_функции(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

Базовые концепции

23

 

 

Вэтом определении LRESULT — тип возвращаемого значения (см. табл. 1.1), hWnd — дескриптор окна, которому адресовано сообщение, uMsg — код сообщения, wParam и lParam — параметры сообщения. Имя функции может быть произволь ным, но для главного окна приложения обычно используется имя WndProc.

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

Каждому коду сообщения в Windows сопоставлен уникальный символичес кий идентификатор. Все системные идентификаторы определены при помощи директивы #define в заголовочном файле winuser.h. Это облегчает чтение и пони мание программ.

Чаще всего приложение обрабатывает оконные сообщения (window messages), начинающиеся с префикса WM_, например: WM_PAINT, WM_SIZE, WM_MOVE и многие другие. Другие типы сообщений могут поступать от элементов управления, на пример: сообщения с префиксом BM_ поступают от кнопок, сообщения с префик сом EM_ — от текстовых полей, сообщения с префиксом LB_ — от списков.

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

Теперь о новом термине «оконный класс». На самом деле все окна создаются на базе того или иного оконного класса. Оконный класс играет роль типа для данного окна.

Оконные классы

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

Ссылка на оконный класс передается функции CreateWindow, вызываемой для создания окна.

Использование класса окна позволяет создавать множество окон на основе одного и того же оконного класса и, следовательно, использовать одну и ту же оконную процедуру. Например, все кнопки в программах Windows созданы на основе оконного класса BUTTON. Оконная процедура этого класса, расположенная в динамически подключаемой библиотеке, управляет обработкой сообщений для всех кнопок всех окон. Аналогичные системные классы имеются и для других эле ментов управления, таких как, например, списки и поля редактирования. В со вокупности эти классы называются предопределенными или стандартными окон ными классами.

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

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

24 Глава 1. «Hello, World!», или Первые шаги к пониманию концепции Windows

ВНИМАНИЕ

Не следует путать понятие оконного класса Windows с понятием класса C++ или, в частности, с понятием класса в библиотеке MFC. Дело в том, что исторически концепция оконных классов опередила по времени использование в Windows объектно-ориентированных языков.

Но как приложение узнает, что к нему пришло сообщение? Об этом рассказы вается в следующем разделе.

Цикл обработки сообщений

Непременным компонентом всех Windows приложений является цикл обра ботки сообщений. У приложения всегда есть главная функция WinMain. Обыч но она содержит вызовы функций для инициализации и создания окон, после чего следует цикл обработки сообщений и необходимый код для закрытия при ложения.

Что происходит в цикле обработки сообщений? Как известно, все сообщения, адресованные приложению, Windows записывает в очередь сообщений приложе ния. Извлечение сообщения из этой очереди осуществляет функция GetMessage.

Если очередное сообщение имеет код WM_QUIT, то происходит выход из цикла, после чего приложение завершает свою работу.

Если очередное сообщение не является сообщением WM_QUIT, то оно передает ся функции DispatchMessage, которая возвращает сообщение обратно в Windows. Windows отправляет сообщение для его обработки соответствующей оконной процедуре — иными словами, Windows вызывает оконную процедуру. После воз врата из оконной процедуры Windows передает управление оператору, который расположен после DispatchMessage, и работа цикла продолжается.

Все… Почва вспахана, взборонена, унавожена, увлажнена... Осталось опустить в нее первое зернышко…

Наипростейшая программа для Windows

Если вы еще ни разу не компилировали программу в интегрированной среде Microsoft Visual C++ 6.0, то оставьте, пожалуйста, закладку на этой странице и об ратитесь к приложению 1, где изложена технология создания нового проекта, до бавления к нему файлов с исходным кодом, компиляции, выполнения и отлад ки проекта.

OK! Вы уже вернулись?.. Тогда продолжим.

Создайте новый проект типа Win32 Application с именем HelloFromMsgBox. Добавьте к проекту новый файл с именем HelloFromMsgBox.cpp и введите в него

следующий код:

#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)

{

MessageBox(NULL, "Hello, Win32 world!", "Hello from Message Box", MB_OK); return 0;

}

Наипростейшая программа для Windows

25

 

 

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

Рис. 1.2. Окно программы HelloFromMsgBox

Что же можно делать с этим приложением? Прочитать радостное сообщение Hello, Win32 world!. Это раз. Перемещать окно по экрану, ухватившись мышью за его заголовок. Это два. Щелкнуть левой кнопкой мыши на кнопке OK. Это три. Еще можно щелкнуть мышью на кнопке закрытия окна. В двух последних случа ях приложение завершает свою работу, а его окно исчезает с экрана.

Не так уж и много возможностей, верно?.. Ну а что же вы хотите от програм мы, содержащей всего семь строк кода и всего две инструкции в теле функции

WinMain?

Рассмотрим теперь код нашей миниатюрной программы. В первой строке на ходится следующая инструкция:

#include <windows.h>

Эта директива подключает к программе главный заголовочный файл Windows приложений.

Далее следует заголовок функции WinMain:

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)

Эта функция эквивалентна функции main для DOS или UNIX, то есть с нее всегда начинается выполнение программы.

Функция возвращает значение типа int и принимает следующие параметры:

HINSTANCE hInstance —дескриптор, который Windows присваивает запущенно му приложению.

HINSTANCE hPrevInstance — в Win32 этот параметр не используется и поэтому всегда принимает нулевое значение1.

LPSTR lpCmdLine — указатель на строку, в которую копируются аргументы прило жения, если оно запущено в режиме командной строки. Запуск приложения

вэтом режиме возможен либо с помощью команды стартового меню Пуск Выполнить, либо из оболочки типа Norton Commander. При этом в командной стро ке набирается имя приложения, а после пробела указываются аргументы, разде ленные также символом пробела. Есть еще один способ запуска приложения

вэтом режиме — через ярлык EXE файла. Если у вас создан такой ярлык (яв ляющийся ссылкой на EXE файл), то щелкните на пиктограмме ярлыка пра вой кнопкой мыши и выберите в контекстном меню пункт Свойства. Затем в появившемся диалоговом окне Свойства укажите параметры командной стро ки, введя их в текстовое поле Object.

1 Этот параметр остался для совместимости версий от Windows 3.x, где он использовался для пред ставления дескриптора предыдущего экземпляра программы.

26 Глава 1. «Hello, World!», или Первые шаги к пониманию концепции Windows

int nCmdShow — целое значение, которое может быть передано функции ShowWindow. Этот параметр будет рассмотрен позже.

Спецификатор WINAPI определяет соглашения о вызове, то есть принятый

вWin32 порядок передачи параметров при вызове функций. Для нашей дальней шей работы этой информации достаточно, поэтому нужно просто запомнить, что

взаголовке функции WinMain он должен быть всегда.

Разобравшись с заголовком функции, перейдем к ее телу. Здесь имеется вызов функции MessageBox, которая, собственно, и выполняет всю содержательную ра боту приложения.

Ну а где же обещанный ранее цикл обработки сообщений? Где текст оконной процедуры?.. Раскроем секрет: написание такой лаконичной программы стало возможно только благодаря использованию функции MessageBox.

Прежде чем объяснять этот феномен, ознакомимся с досье на функцию MessageBox, которое можно найти в справочной системе MSDN1. Для этого нужно установить текстовый курсор на любом символе имени функции и нажать клави шу F1. В появившемся диалоговом окне Найденные разделы следует выбрать строку MessageBox и нажать кнопку Показать. На экран будет выведено описание функции MessageBox, из которого следует, что функция создает, отображает и обслуживает окно сообщений. Окно сообщений — это диалоговое окно, содержащее указанное программистом текстовое сообщение и одну или несколько кнопок.

Функция имеет следующий прототип2:

int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);

Ее параметры интерпретируются следующим образом:

hWnd — дескриптор родительского окна. Он принимает значение NULL, если ро дительского окна нет.

lpText — указатель на строку, содержащую текст сообщения.

lpCaption — указатель на строку, содержащую текст заголовка диалогового окна.

uType — параметр содержит комбинацию флагов, задающих количество и типы кнопок в диалоговом окне, а также наличие заданной пиктограммы.

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

1Microsoft Developer Network (MSDN) — это набор онлайновых и оффлайновых служб, предназначен ных для оказания помощи разработчику в написании приложений с использованием продуктов и технологий фирмы Microsoft. Справочная система MSDN (MSDN Library), обычно устанавливае мая на компьютере вместе со средой Microsoft Visual Studio 6.0, содержит обширную информацию по программированию, в том числе и описание структур данных и функций Win32 API. В дальней шем вместо термина Справочная система MSDN будет употребляться термин MSDN.

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

Наипростейшая программа для Windows

27

 

 

Вот почему в программе нет ни оконной процедуры, ни цикла обработки со общений, и она претендует на титул самой короткой программы для Windows. Но, как уже говорилось, утилитарной пользы от этой суперкороткой програм мы не очень много.

Зато есть польза, и немалая, — гносеологическая! Так, мы уже знаем, что такое WinMain и как интерпретируются ее параметры. Это раз. Мы уже умеем пользо ваться функцией MessageBox. Это два. Анализ текста программы дает также повод поговорить о стиле написания Windows программ, а точнее, о так называемой вен герской нотации, которая была предложена венгерским программистом фирмы Microsoft Чарльзом Симони (Charles Simonyi). Суть этой нотации заключается в том, что имя переменной начинается с префикса, который содержит одну или несколько строчных букв. Этот префикс определяет тип переменной. За префик сом следует само имя, которое может состоять как из прописных, так и из строч ных букв, но первая буква имени — всегда прописная. Например, идентификатор nMyVariable обозначает некоторую целую переменную. Основные префиксы вен герской нотации приведены в табл. 1.2.

Таблица 1.2. Префиксы имен переменных для указания типа данных

Префикс

Соответствующий тип данных

 

 

bBOOL

by BYTE

cñhar

dw

DWORD

fn

Функция

hДескриптор

iINT

lLONG

lp

Дальний указатель

lpsz

Дальний указатель на строку, заканчивающуюся нулевым байтом

nshort èëè int

pУказатель

pfn

Указатель на функцию

psz

Указатель на строку, заканчивающуюся нулевым байтом

pv

Указатель на тип void

sz

Строка, заканчивающаяся нулевым байтом

uUINT

vvoid

wWORD

xКороткое целое число, используемое в качестве координаты x

yКороткое целое число, используемое в качестве координаты y

Теперь, уважаемый читатель, вам должно быть понятно, откуда берутся и что означают такие имена переменных, как lpText или uType. Более того, вы можете также придерживаться этой системы обозначений, придумывая имена для ваших собственных переменных. Но на последнем мы не настаиваем, так как вопрос сти ля программирования либо решается индивидуально на основе личных вкусов и предпочтений, либо регламентируется правилами той фирмы, в которой рабо тает программист.

28

Глава 1. «Hello, World!», или Первые шаги к пониманию концепции Windows

 

Прежде чем перейти к рассмотрению «нормальной» Windows программы, сле

дует отметить, что в функциях Win32 может использоваться одна из следующих

систем координат:

экранные координаты (screen coordinates);

оконные координаты (window coordinates);

координаты клиентской области (client coordinates).

 

На рис. 1.3 показаны главное окно программы на экране монитора, а также

взаимоотношения между этими системами координат.

 

Рис. 1.3. Системы координат Windows

Кроме этого на рисунке показаны основные элементы главного окна програм мы, на которые мы будем ссылаться в дальнейшем изложении.

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

Size (Размер), Minimize (Свернуть), Maximize (Развернуть), Close (Закрыть). После дние три функции дублируются кнопками, расположенными в правой части полосы заголовка.

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

Программа «Hello, World!» — первый вариант

29

 

 

Еще ниже находится клиентская область окна. Это главная область вывода приложения. Ответственность за управление клиентской областью окна лежит на приложении.

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

Программа «Hello, World!» — первый вариант

Полноценная программа для Win32 должна содержать как минимум две функ ции:

WinMain — главную функцию, в которой создается основное окно программы и запускается цикл обработки сообщений;

WndProc — оконную процедуру, обеспечивающую обработку сообщений для ос новного окна программы.

На некотором псевдоязыке каркас Windows программы можно представить следующим образом:

WinMain (список аргументов)

{

Подготовить и зарегистрировать класс окна с требуемыми характеристиками; Создать экземпляр окна зарегистрированного класса;

Пока не произошло необходимое для выхода событие { Извлечь очередное сообщение из очереди сообщений; Передать его через Windows оконной функции;

}

Возврат из программы;

}

WndProc (список аргументов)

{

Обработать полученное сообщение; Возврат;

}

Не будем отступать от традиции и покажем, как написать приложение, кото рое создает окно и выводит в нем строку «Hello, World!». Весь текст программы, приведенный в листинге 1.1, размещается в файле Hello1.cpp.

Листинг 1.1. Проект Hello1

//////////////////////////////////////////////////////////////////////

// Hello1.cpp #include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

//====================================================================

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)

{

HWND hMainWnd;

char szClassName[] = "MyClass";

продолжение

30 Глава 1. «Hello, World!», или Первые шаги к пониманию концепции Windows

Листинг 1.1 (продолжение)

MSG msg;

WNDCLASSEX wc;

// Заполняем структуру класса окна

wc.cbSize

= sizeof(wc);

wc.style

= CS_HREDRAW | CS_VREDRAW;

wc.lpfnWndProc

= WndProc;

wc.cbClsExtra

= 0;

wc.cbWndExtra

= 0;

wc.hInstance

= hInstance;

wc.hIcon

= LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor

= LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);

wc.lpszMenuName

= NULL;

wc.lpszClassName = szClassName;

wc.hIconSm

= LoadIcon(NULL, IDI_APPLICATION);

//Регистрируем класс окна if (!RegisterClassEx(&wc)) {

MessageBox(NULL, "Cannot register class", "Error", MB_OK); return 0;

}

//Создаем основное окно приложения

hMainWnd = CreateWindow(

szClassName, "A Hello1 Application", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,

(HWND)NULL, (HMENU)NULL, (HINSTANCE)hInstance, NULL

);

if (!hMainWnd) {

MessageBox(NULL, "Cannot create main window", "Error", MB_OK); return 0;

}

//Показываем наше окно ShowWindow(hMainWnd, nCmdShow);

//UpdateWindow(hMainWnd); // См. примечание в разделе "Отображение окна…"

//Выполняем цикл обработки сообщений до закрытия приложения

while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg);

}

return msg.wParam;

}

//==================================================================== LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

HDC hDC; PAINTSTRUCT ps; RECT rect;

switch (uMsg)