Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
5
Одной из важнейших функций любой операционной системы (ОС) является предоставление прикладной программе средств взаимодействия с аппаратурой, другими программами и, наконец, с самой ОС. Набор подобных средств взаимодействия называется интерфейсом прикладных программ (API application programming interface). В большинстве однозадачных ОС прикладная программа обычно имеет возможность игнорировать API при доступе к ресурсам. Например, в MS DOS API реализован в виде функций программного прерывания 21h, однако, ничто не мешает прикладной программе непосредственно вызывать функции BIOS или обращаться к аппаратуре через порты ввода/вывода. Подобная ситуация недопустима в мультизадачных ОС, так как неизбежно будет приводить к конфликтам при доступе к разделяемым ресурсам. Поэтому в таких ОС единственным способом связи прикладной программы с внешним миром является использование API.
Windows95 и Windows NT 4.0 используют единый 32-разрядный API, который носит название API WIN32. В идеале это означает, что прикладная программа, использующая WIN32, будет корректно работать и под управлением Windows95, и под управлением Windows NT 4.0. Для большинства программ это соответствует действительности, однако, в силу определенных различий в архитектуре рассматриваемых ОС имеется некоторый набор функций API, которые реализованы в Windows NT, но отсутствуют в Windows95, и наоборот. Программы, использующие эти функции будут успешно компилироваться (ведь API единый!), но при работе программы в среде другой ОС будут возникать ошибки. Подобный эффект достигается за счет того, что неподдерживаемые функции реализованы в виде “заглушек”, возвращающих информацию об ошибке. К счастью, таких функций немного и они достаточно редко используются. В данном пособии мы будем избегать использования подобных функций, а где это необходимо будет приводиться соответствующая информация.
API WIN32 представляет собой совокупность более 1000 системных функций. В отличие от MS DOS, где для вызова функций ОС используется механизм программных прерываний, вызов системных функций WIN32 производится по их именам. При этом независимо от языка программирования имена функций, а также входные и выходные параметры остаются неизменными (что, в общем, и следовало ожидать). Это делает программы для Windows, написанные на различных языках, очень похожими. Поэтому, несмотря на то, что в данном пособии в примерах используется язык С (все примеры были отлажены в среде программирования Borland C++ 5.01), практически вся информация может быть с успехом применена и при программировании на любом другом языке программирования. Важно также отметить, что при компиляции в загрузочный модуль не включается код системных функций, а помещаются только ссылки на них. Сами функции API содержатся в так называемых библиотеках динамической компоновки (DLL-библиотеках), входящих в состав ОС. Подробнее о DLL-библиотеках мы поговорим в отдельной главе.
Структура приложения Windows
Прикладная программа, разработанная специально для Windows95 или Windows NT называется приложением WIN32 (WIN32 application). Поэтому в дальнейшем мы будем использовать термин “приложение” вместо термина “прикладная программа”.
Архитектура приложения представляет собой архитектуру программы, управляемой событиями. Ключевую роль в организации работы всех подсистем и приложений Windows играет система сообщений. Процесс обработки сообщений представлен на рис. 1.
6
Рис. 1. Процесс обработки сообщений
7
В Windows95 и Windows NT реализована вытесняющая мультизадачность на уровне потоков. Поток (thread) описывает последовательность исполнения кода внутри процесса. В соответствии с установленным приоритетом система выделяет каждому потоку кванты процессорного времени, задаваемые системным таймером. Любому потоку в приложении соответствует некоторая функция - функция потока. При запуске приложения создается процесс, а в его рамках первичный поток, который и получает управление. Приложение должно состоять как минимум из одной функции функции первичного потока, которая при программировании на языке С заменяет функцию main и должна быть определена следующим образом:
int WINAPI WinMain (HINSTANCE hinstance , HINSTANCE hPrevinstance,
LPSTR lpszCmdLine, int nCmdShow)
{ // Тело функции }
Все параметры , получаемые функцией WinMain , формируются операционной системой. Параметры hinstance и hPrevinstance представляют собой соответственно идентификаторы текущей и предыдущей копии приложения. Приложения WIN32 функционируют в изолированных виртуальных адресных пространствах и в силу этого не имеют возможности определить через параметр hPrevinstance, не запущено ли приложение вторично. Этот параметр использовался в предыдущих версиях Windows, для приложений WIN32 он всегда равен нулю. Параметр lpszCmdLine - дальний указатель на строку запуска приложения , параметр nCmdShow указывает рекомендуемый приложению способ представления главного окна при запуске. Приложение может игнорировать этот параметр, однако это не является хорошим стилем программирования. Обычно значение данного параметра передается без изменений функции ShowWindow для отображения на экране главного окна приложения. Ниже приведены возможные значения параметра nCmdShow, определенные как константы с префиксами SW_ :
SW_HIDE - скрыть окно и активизировать другое окно;
SW_MINIMIZE минимизировать окно и активизировать окно верхнего уровня в системном списке;
SW_RESTORE активизировать и отобразить окно в его первоначальном размере и позиции;
SW_SHOW - активизировать и отобразить окно в его текущем размере и позиции;
SW_SHOWMAXIMIZED - активизировать и отобразить окно в полном размере;
SW_SHOWMINIMIZED - активизировать и отобразить окно в виде пиктограммы;
SW_SHOWMINNOACTIVE - отобразить окно в виде пиктограммы, активное окно остается активным;
SW_SHOWNA - отобразить окно в текущем состоянии, активное окно остается активным;
SW_SHOWNOACTIVATE - отобразить окно в его последнем размере и позиции, активное окно остается активным;
SW_SHOWNORMAL аналогично SW_RESTORE.
Модификатор вызова WINAPI определяет порядок помещения в стек параметров функции при вызове. Для функций также определены следующие модификаторы:
#define CALLBACK __stdcall
#define WINAPI __stdcall
#define APIENTRY WINAPI
#define APIPRIVATE __stdcall
#define PASCAL __stdcall
Значение, возвращаемое функцией, должно быть равно значению параметра wParam сообщения WM_QUIT или нулю, если выход произошел до запуска цикла обработки сообщений (см. далее).
8
Сделаем одно важное замечание относительно используемых типов данных. Для приложений Windows определено множество новых типов данных, которые выведены из стандартных типов данных языка С. Однако, при программировании следует использовать именно новые типы данных, а не их стандартные эквиваленты. В противном случае будет затруднена модификация программ для их использования в последующих версиях Windows. Например, прототип функции WinMain не изменился при переходе от API WIN16 (Windows 3.1) к API WIN32. Однако, в WIN16 тип HINSTANCE представлял собой 16-разрядное целое, а в WIN32 он стал 32-разрядным. Во избежание ошибок рекомендуется в исходном тексте вводить строгий контроль типов при помощи определения :
#define STRICT
Кроме того при именовании переменных следует придерживаться так называемой “венгерской нотации”, согласно которой имя переменной должно начинаться с одной или нескольких строчных букв, отражающих ее тип. Так, в параметре hInstance функции WinMain префикс h означает идентификатор (handle), а префикс lpsz в lpszCmdLine означает дальний указатель на ASCIIZ-строку (long pointer on string zero).
Отметим, что в рамках одного процесса программист имеет возможность создать несколько параллельных потоков. Мы отложим рассмотрение мультипотоковых приложений до специальной главы, а пока остановимся на приложениях использующих только первичный поток.
Для нормальной работы приложения функция потока (то есть в нашем случае функция WinMain) должна обрабатывать сообщения. Сообщение представляет собой сформированную операционной системой информацию о некотором событии во “внешнем” (по отношению к потоку) мире, о котором, по мнению ОС, должен “узнать” поток. Имеется три основных источника сообщений : аппаратура, потоки других процессов (или одного процесса в случае мультипотоковых приложений) и сама ОС.
При нажатии клавиши на клавиатуре, кнопки мыши, перемещении мыши или при других операциях с аппаратурой драйвер устройства распознает соответствующее аппаратное событие и помещает информацию о нем в очередь системного потока необработанного ввода (RIT raw input thread). Главной задачей RIT является определение потока, которому необходимо направить информацию об аппаратном событии. Так, в случае сигнала от мыши, RIT определяет поверх какого окна находится курсор мыши, а в случае сигнала от клавиатуры RIT определяет, какой поток является приоритетным потоком (foreground thread), т.е. с каким потоком пользователь работает в данный момент. Затем RIT направляет информацию о событии в виртуальную очередь ввода выбранного потока. ОС преобразует эту информацию в сообщение, которое направляется в очередь сообщений потока, которому принадлежит виртуальная очередь ввода.
Сообщения от операционной системы и от других потоков помещаются непосредственно в очередь сообщений соответствующего потока. Чаще всего потоки обмениваются сообщениями при помощи функции API SendMessage, которая подробно будет рассмотрена несколько позднее. Кроме того, распространенной практикой является посылка сообщений в собственную очередь сообщений.
Под сообщением в WIN32 понимается структура данных, определенная следующим образом :
typedef struct tagMSG
{ HWND hwnd; // идентификатор окна , которому направлено сообщение
UINT message; // цифровой код (номер) сообщения
WPARAM wParam;// специфическая для каждого сообщения
LPARAM lParam;// информация
DWORD time;// время создания сообщения
POINT pt;// положение курсора в момент создания сообщения
} MSG;
9
Коды сообщений определены как константы с различными префиксами. Сообщениям, предназначенным для управления окнами, соответствуют константы с префиксами WM_. Наиболее часто используемые сообщения будут нами рассмотрены по мере необходимости. Полный список и описание сообщений можно найти в справочной документации и в электронном справочнике WIN32 Programmers Reference системы программирования Borland C++ 5.01.
Для обработки сообщений функция потока должна содержать цикл обработки сообщений (message loop). Основу этого цикла составляют две функции API WIN32, имеющие следующие прототипы:
BOOL GetMessage (LPMSG lpmsg , HWND hwnd, UINT uMsgFilterMin , UINT uMsgFilterMax);
LONG DispatchMessage(const MSG * lpmsg);
Простейший цикл обработки сообщений выглядит следующим образом:
MSG msg;
. . .
while (GetMessage(&msg , 0 , 0 , 0 ) ) { DispatchMessage(&msg); }
Функция GetMessage извлекает очередное сообщение из очереди сообщений вызвавшего ее потока. Параметр lpmsg - дальний указатель на структуру типа MSG. Параметр hwnd (идентификатор окна) определяет конкретное окно из числа окон, созданных потоком, которому послано сообщение. Сообщения, инициируемые устройствами ввода, такими как клавиатура или мышь, посылаются окну, владеющему фокусом ввода (активному окну). В каждый момент времени поток либо вообще не владеет фокусом ввода (пользователь работает с окном другого потока), либо фокус ввода принадлежит одному из его окон. Если в качестве второго параметра функции GetMessage указан NULL ( или 0 ) , то сообщения будут выбираться для всех окон, принадлежащих вызвавшему функцию потоку. Третий и четвертый параметры функции GetMessage задают диапазон номеров сообщений , извлекаемых из очереди. Если оба параметра содержат нуль , то извлекаются все сообщения , направленные потоку. Функция GetMessage возвращает значение FALSE в случае, если извлекается сообщение WM_QUIT, во всех остальных случаях возвращается значение TRUE. Выборка сообщения WM_QUIT приводит к выходу из цикла, после чего обычно поток завершается. Следует также отметить важную особенность функции GetMessage : если функция обнаруживает, что очередь сообщений потока пуста, то поток блокируется и ему не выделяется процессорное время до тех пор, пока новое сообщение не поступит в очередь. Если пусты очереди всех прикладных потоков, то обычно процессорное время распределяется между низкоприоритетными системными потоками, в частности, запускается хранитель экрана.
Функция DispatchMessage определяет окно , которому предназначено сообщение , и вызывает соответствующую функцию окна . Функция окна относится к так называемым косвенно вызываемым ( callback ) функциям , которые создаются разработчиком приложения , а вызываются Windows. Каждому окну , создаваемому потоком, должна соответствовать некоторая функция окна (допустимо , когда одна функция определяется как функция окна сразу для нескольких окон). В функции окна сосредоточены все действия потока по обработке сообщений , направленных окну. Так как абсолютное большинство приложений можно рассматривать как совокупность окон, можно считать , что основная работа приложения выполняется функцией ( или функциями) окна.
Функции окна соответствует следующий прототип (имя функции выбирается произвольно):
LRESULT CALLBACK WndProc ( HWND hwnd , UINT msg ,
WPARAM wParam , LPARAM lParam);
Параметры функции полностью аналогичны соответствующим полям стуктуры MSG.
Типичная структура функции окна имеет следующий вид :
10
LRESULT CALLBACK WndProc ( HWND hwnd , UINT msg ,
WPARAM wParam , LPARAM lParam);
{ // обработка сообщений
switch (msg)
{ case сообщениe :{ . . . return 0 ; }
case сообщениe :{ . . . return 0 ; }
. . .
case WM_DESTROY :
{
// это сообщение приходит , когда пользователь закрывает окно
PostQuitMessage(0); // посылаем в очередь сообщение WM_QUIT
return 0 ;
}
}
// остальные сообщения обрабатываются по умолчанию
return DefWindowProc ( hwnd , msg , wParam , lParam );
}
Фактически функция окна представляет собой один большой оператор выбора из обрабатываемых функцией окна сообщений и содержит обработчики этих сообщений. Как правило, программист планирует обработку далеко не всех сообщений, поступающих в функцию окна. Поэтому, перед возвратом из функции необработанные сообщения передаются на обработку по умолчанию функции DefWindowProc. Значение, возвращаемое функцией окна в общем случае определяется тем, какое обрабатывалось сообщение. Обратим также внимание на обработку сообщения WM_DESTROY, которое поступает в функцию окна, когда пользователь закрывает окно. Если речь идет о главном окне приложения, то стандартно после закрытия этого окна приложение должно быть завершено. Для этого при помощи функции PostQuitMessage в очередь сообщений соответствующего потока посылается сообщение WM_QUIT. Это один из примеров посылки сообщения в собственную очередь.
Регистрация класса окна и создание окна
Приложение, использующее графический интерфейс с пользователем , должно иметь, как минимум, одно окно главное окно приложения. Для того, чтобы создать окно, необходимо вначале зарегистрировать класс окна . Класс окна определяет набор свойств , характерных для всех окон , создаваемых на базе данного класса. В Windows определены стандартные классы окон, используемые для создания стандартных органов управления. Стандартные классы не регистрируются приложением и для них не определяется функция окна , так как эти операции выполняются автоматически при загрузке Windows.
Для регистрации класса окна используется следующая функция API WIN32:
ATOM WINAPI RegisterClassEx ( const WNDCLASSEX FAR * lpwcx);
В качестве параметра эта функция получает дальний указатель на структуру WNDCLASSEX, которую должно сформировать приложение перед регистрацией класса окна. Структура WNDCLASSEX определяется следующим образом :
typedef struct _WNDCLASSEX
{ // размер структуры WNDCLASSEX
UINT cbSize;
// стиль класса окна (если равен нулю используется стиль по умолчанию)
UINT style;
11
// указатель на функцию окна, обрабатывающую сообщения, предназначенные
// для всех окон, созданных на основе данного класса
WNDPROC lpfnWndProc;
// размер дополнительной области данных, зарезервированной в описании класса окна
// (должно быть равно нулю, если не используется
int cbClsExtra;
// размер дополнительной области данных, зарезервированной для каждого окна,
// созданного на основе данного класса ( должно быть равно нулю если не используется )
int cbWndExtra;
// идентификатор приложения, зарегистрировавшего данный класс
HANDLE hInstance;
// идентификатор пиктограммы, используемой для окна данного класса
HICON hIcon;
// идентификатор курсора, используемого для окнa данного класса
HCURSOR hCursor;
// идентификатор кисти ( цвета ) фона окна
HBRUSH hbrBackground;
// дальний указатель на строку меню
LPCTSTR lpszMenuName;
// дальний указатель на строку с именем класса
LPCTSTR lpszClassName;
// идентификатор маленькой пиктограммы для заголовка окна
HICON hIconSm;
} WNDCLASSEX;
Рассмотрим более подробно некоторые поля структуры WNDCLASSEX.
Стиль класса окна определяет внешний вид и поведение всех окон , созданных на базе данного класса. Стиль класса задается в виде логической комбинации констант с префиксом CS_ :
CS_BYTEALIGNCLIENT внутренняя область окна выравнивается по границе байта видеопамяти;
CS_BYTEALIGNWINDOW все окно выравнивается по границе байта видеопамяти;
CS_CLASSDC создается единый контекст отображения для всех окон данного класса;
CS_DBLCLKS функция окна будет получать сообщения о двойном щелчке мышью;
CS_GLOBALCLASS класс является глобальным и доступен другим приложениям;
CS_HREDRAW окно должно быть перерисовано при изменении его ширины;
CS_NOCLOSE в системном меню запрещается функция закрытия окна;
CS_PAREHTDC окно будет пользоваться родительским контекстом отображения;
CS_OWNDC для каждого окна данного класса создается отдельный контекст отображения;
CS_SAVEBITS для данного окна Windows должна хранить его битовое изображение, по которому при необходимости окно восстанавливается на экране;
CS_VREDRAW окно должно быть перерисовано при изменении его высоты.
Наиболее часто используются стили CS_VREDRAW | CS_HREDRAW (при изменении размера окна ему посылается сообщение WM_PAINT , в ответ на которое функция окна должна обновить окно) и CS_DBLCLKS (для окна отслеживаются двойные щелчки мышью). Некоторые другие стили, в частности связанные с контекстом отображения, будут описаны в дальнейшем в соответствующих главах.
Курсор мыши и пиктограмма относятся к ресурсам приложения , доступ к ним осуществляется через идентификаторы ресурсов. Ресурсы мы подробно изучим далее в этой главе. Здесь лишь отметим , что идентификаторы пиктограммы и курсора возвращаются
12
соответственно функциями LoadIcon и LoadCursor, причем, если в качестве первого параметра этих функций указан NULL , то загружается системный ресурс, заданный в виде
предопределенной константы с префиксом IDC_ для курсора и IDI_ для пиктограммы.
Для заполнения фона окна приложение должно создать кисть и передать ее идентификатор функции RegisterClassEx. В простейшем случае идентификатор кисти может быть получен преобразованием к типу HBRUSH одной из предопределенных констант , задающих системные цвета . Эти константы имеют префикс COLOR_. Константа COLOR_WINDOW задает системный цвет фона. Кроме того , для заполнения фона окна можно использовать еще один ресурс приложения - изображение bitmap. Для этого оно должно быть загружено при помощи функции LoadBitmap , а затем превращено в кисть с помощью функции CreatePatternBrush. Если в качестве первого параметра функции LoadBitmap передается NULL , то загружается предопределенное изображение bitmap, заданное в качестве второго параметра константой с префиксом OBM_ . Более подробно эти вопросы рассматриваются в главе, посвященной интерфейсу графических устройств GDI.
Важно также отметить, что именно при регистрации класса окна через поле lpfnWndProc классу окна ставится в соответствие функция окна, которая будет обрабатывать сообщения для всех окон, созданных на базе данного класса.
Функция RegisterClassEx возвращает уникальный идентификатор класса окна типа ATOM или нуль при ошибке.
Для создания окна используется функция CreateWindow :
HWND WINAPI CreateWindow(
LPCSTR lpszClassName , // указатель на строку с именем класса
LPCSTR lpszWindowName , // указатель на заголовок окна
DWORD dwStyle , // стиль окна
int x , // координаты верхнего
int y , // левого угла окна
int nWidth , // ширина окна
int nHeight , // высота окна
HWND hwndParent , // идентификатор родителького окна или 0
HMENU hmenu , // идентификатор ресурса - меню или 0
HINSTANCE hinst , // идентификатор приложения , создавшего окно
LPVOID lpvParam // указатель на область данных для окна или NULL
// этот указатель передается окну вместе с сообщением WM_PAINT );
Функция CreateWindow не отображает окно на экране (для этого предназначена функция ShowWindow). Функция CreateWindow создает в оперативной памяти структуру типа WND , на которую фактически указывает идентификатор окна типа HWND , возвращаемый этой функцией . Если создать окно не удалось , функция возвращает значение NULL .
Стиль окна используется для дальнейшего уточнения внешнего вида и поведения конкретного окна , созданного на базе некоторого класса. Стиль окна указывается в виде логической комбинации предопределенных констант с префиксом WS_ . Для экономии места мы не будем здесь рассматривать все возможные стили окна, при необходимости соответствующую информацию можно найти в справочной документации и в электронном справочнике WIN32 Programmers Reference системы программирования Borland C++ 5.01 (описание функции CreateWindow). Остановимся на наиболее часто используемых классах.
Все многообразие стилей окон сводится к трем основным стилям : перекрывающиеся (overlapped) окна , временные (pop-up) окна и дочерние (child) окна . Перекрывающиеся окна обычно используются в качестве главного окна приложения. Константа для стиля перекрывающегося окна определена следующим образом:
#define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX)
13
Константа WS_OVERLAPPED определяет базовый стиль окна как перекрывающееся окно, а остальные константы в указанном определении уточняют внешний вид окна. Оно должно иметь
заголовок (константа WS_CAPTION), системное меню (WS_SYSMENU), толстую рамку (WS_THICKFRAME), кнопки минимизации и максимизации (WS_MINIMIZEBOX | WS_MAXIMIZEBOX). Приложение может создавать несколько перекрывающихся окон , связанных между собой “отношениями собственности” . В качестве параметра hwndParent при создании окна можно указать идентификатор ранее созданного окна , которое становится владельцем создаваемого окна. При уничтожении окна-владельца уничтожаются и все принадлежащие ему окна. Обычное перекрывающееся окно , не имеющее окна-владельца , может располагаться в любом месте экрана. Подчиненные окна располагаются всегда над поверхностью окна-владельца.
Временные окна обычно используются для вывода на экран информационных сообщений и остаются на экране непродолжительное время. Стиль таких окон определен следующим образом :
#define WS_POPUPWINDOW (WS_POPUP | WS_BORDER | WS_SYSMENU)
Константа WS_POPUP определяет базовый стиль окна как временное окно, а остальные константы в указанном определении уточняют внешний вид окна. Оно должно иметь рамку и системное меню. При необходимости иметь заголовок следует указать этот стиль в комбинации со стилем WS_CAPTION. Временные окна могут иметь окно-владельца и сами владеть другими окнами. В общем случае , можно рассматривать временные окна как частный случай перекрывающихся окон. Все замечания , сделанные выше относительно перекрывающихся окон , справедливы и для временных окон.
Дочерние окна чаще всего используются приложениями для создания органов управления на основе зарегистрированных в системе стандартных классов. Окна этого стиля , как правило , не имеют рамки и заголовка и сами рисуют все , что должно быть в них изображено. Стиль этих окон определен следующим образом :
#define WS_CHILDWINDOW (WS_CHILD)
Дочернее окно всегда имеет окно-родителя , идентификатор которого передается в качестве параметра hwndParent при создании окна. Кроме того , при создании дочернего окна в качестве параметра hmenu следует указать присвоенный создаваемому окну целый идентификатор. Этот идентификатор используется дочерним окном при отправлении сообщений родительскому окну . Дочернее окно как бы “прилипает” к поверхности родительского окна и перемещается вместе с ним , все дочерние окна исчезают при сворачивании родительского окна в пиктограмму и появляются вновь при восстановлении родительского окна.
Координаты , используемые функцией CreateWindow для создания перекрывающихся и временных окон совпадают с физическими координатами экрана , начало координат находится в верхнем левом углу экрана , ось Х направлена вправо , ось Y - вниз. Для дочерних окон начало координат расположено в верхнем левом углу родительского окна. Единицей измерения физических координат является один пиксел. Более подробно эти вопросы рассматриваются в главе, посвященной интерфейсу графических устройств GDI.
Простейшее приложение WIN32
Рассмотрим пример простейшего приложения WIN32. Это приложение отображает при запуске главное окно, при нажатии над его поверхностью левой или правой кнопок мыши , а также при нажатии клавиши на клавиатуре на экран выдается соответствующая информация.
// =======================================================================
// Простейшее приложение Windows
// =======================================================================
14
#define STRICT // включаем строгую проверку типов
#include <windows.h> // главный include-файл для приложений Windows
#include <mem.h>
// Прототипы функций
// Функция окна
LRESULT WINAPI WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
// Имя класса окна
char const szClassName[] = "Application";
// Заголовок окна
char const szWindowTitle[] = "Hello , world !";
// =======================================================================
// Функция WinMain
// Получает управление при запуске приложения
// =======================================================================
#pragma argsused // запрещаем предупреждение о неиспользуемых в теле функции параметрах
int APIENTRY
WinMain( HINSTANCE hInstance, // идентификатор текущей копии приложения
HINSTANCE hPrevInstance, // идентификатор предыдущей копии приложения, для приложений WIN32 всегда NULL
LPSTR lpszCmdLine, // указатель на командную строку
int nCmdShow)// способ отображения главного окна приложения
{MSG msg; // структура для работы с сообщениями
HWND hwnd; // идентификатор главного окна приложения
WNDCLASSEX wc; // структура для регистрации класса окна
// ----------------------------------------------------
// Выполняем регистрацию класса окна
// ----------------------------------------------------
// Записываем нулевые значения во все поля структуры
memset(&wc, 0, sizeof(wc));
// Размер структуры WNDCLASSEX
wc.cbSize = sizeof(WNDCLASSEX);
// Стиль класса окна - по умолчанию
wc.style = 0;
// Указатель на функцию окна, обрабатывающую сообщения, предназначенные для всех окон,
// созданных на основе данного класса
wc.lpfnWndProc = (WNDPROC) WndProc;
// Размер дополнительной области данных, зарезервированной в описании класса окна
wc.cbClsExtra = 0;
// Размер дополнительной области данных, зарезервированной для каждого окна,
// созданного на основе данного класса
wc.cbWndExtra = 0;
// Идентификатор приложения, которое создало данный класс
wc.hInstance = hInstance;
// Идентификатор пиктограммы, используемой для окна данного класса
// загружается стандартная пиктограмма приложения из ресурсов Windows
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
// Идентификатор курсора, используемого для окна данного класса
// загружается стандартный курсор из ресурсов Windows
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
// Цвет фона окна
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW);
15
// Идентификатор меню
wc.lpszMenuName = (LPSTR)NULL;
// Имя, которое присваивается создаваемому классу и используется при создании
// окон данного класса
wc.lpszClassName = (LPSTR)szClassName;
// Идентификатор маленькой пиктограммы в заголовке окна данного класса
// загружается стандартная пиктограмма логотип из ресурсов Windows
wc.hIconSm = LoadIcon(NULL, IDI_WINLOGO);
// Регистрация класса , завершение работы в случае ошибки
if (!RegisterClassEx(&wc)) return FALSE;
// ----------------------------------------------------
// Создаем главное окно приложения
// ----------------------------------------------------
hwnd = CreateWindow(
szClassName, // имя класса окна
szWindowTitle, // заголовок окна
WS_OVERLAPPEDWINDOW, // стиль окна
CW_USEDEFAULT, // задаем размеры и расположение
CW_USEDEFAULT, // окна, принятые по умолчанию
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL , // идентификатор родительского окна
NULL , // идентификатор меню
hInstance, // идентификатор приложения
NULL); // указатель на дополнительные параметры
// Если создать окно не удалось, завершаем приложение
if(!hwnd)return FALSE;
// Рисуем окно. Для этого после функции ShowWindow, рисующей окно, вызываем функцию //UpdateWindow, посылающую сообщение WM_PAINT в функцию окна
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
// Запускаем цикл обработки сообщений
while(GetMessage(&msg, 0, 0, 0)) { DispatchMessage(&msg); }
// Возвращаем значение wParam, переданное в качестве параметра функции PostQuitMessage
// в процессе инициирования завершения работы приложения из функции окна.
// Затем завершаем работу приложения
return msg.wParam;
}
// =======================================================================
// Функция WndProc , НЕ ВЫЗЫВАЕТСЯ ни из одной функции приложения.
// Эту функцию вызывает Windows в процессе обработки сообщений.
// Для этого адрес функции WndProc указывается при регистрации класса окна.
// Функция выполняет обработку сообщений для главного окна приложения
// =======================================================================
LRESULT WINAPI
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{ // Выполняем обработку сообщений. Идентификатор
// сообщения передается через параметр msg
switch (msg)
{
16
// Это сообщение приходит, когда вы поместили курсор мыши в область главного окна //приложения и нажали левую кнопку мыши
case WM_LBUTTONDOWN:
{ MessageBox(NULL, "Нажата левая кнопка мыши", "Сообщение WM_LBUTTONDOWN", MB_OK | MB_ICONINFORMATION); return 0;}
// Это сообщение приходит, когда вы поместили курсор мыши в область главного окна //приложения и нажали правую кнопку мыши
case WM_RBUTTONDOWN:
{MessageBox(NULL, "Нажата правая кнопка мыши", "Сообщение WM_RBUTTONDOWN", MB_OK | MB_ICONINFORMATION); return 0;}
// Это сообщение приходит, когда вы нажали клавишу на клавиатуре
case WM_KEYDOWN:
{MessageBox(NULL, "Нажата клавиша на клавиатуре", "Сообщение WM_KEYDOWN", MB_OK | MB_ICONINFORMATION); return 0;}
// Это сообщение приходит, когда вы завершаете работу приложения стандартным для //Windows способом
case WM_DESTROY:
{// Инициируем завершение работы приложения, помещая в очередь приложения сообщение
// WM_QUIT. Это приведет к завершению цикла обработки сообщений в функции WinMain
PostQuitMessage(0); return 0;}
}
// Все сообщения, которые не обрабатываются нашей функцией окна, ДОЛЖНЫ передаваться // функции DefWindowProc
return DefWindowProc(hwnd, msg, wParam, lParam);
}
Функция WinMain регистрирует класс окна при помощи функции RegisterClassEx, затем создает окно при помощи функции CreateWindow и отображает его на экране при помощи функции ShowWindow. После этого запускается цикл обработки сообщений. WinMain не проверяет, не запущено ли приложение повторно, так как запуск нескольких копий приложения является обычной практикой для многих приложений. Однако, иногда может возникнуть необходимость в запрете повторного запуска. Как уже говорилось выше, для этого в WIN32 нельзя воспользоваться параметром hPrevInstance функции WinMain. Ниже приведен фрагмент кода, который при запуске приложения пытается найти окно своего приложения, и если окно присутствует в системе активизирует его и выдвигает на передний план. Собственно именно такой реакции более всего ожидает пользователь, когда запускает приложение.
// Проверяем, не было ли это приложение запущено ранее
hwnd = FindWindow(szClassName, NULL);
if(hwnd)
{// Если окно приложения было свернуто в пиктограмму, восстанавливаем его
if(IsIconic(hwnd)) ShowWindow(hwnd, SW_RESTORE);
// Выдвигаем окно приложения на передний план
SetForegroundWindow(hwnd);
// Завершаем работу запущенной копии
return FALSE;}
Функция окна WndProc обрабатывает два сообщения от мыши (WM_LBUTTONDOWN и WM_RBUTTONDOWN), а также сообщение от клавиатуры (WM_KEYDOWN). Обработка этих сообщений заключается в отображении на экране стандартной информационной диалоговой панели при помощи функции API MessageBox. Кроме того, обрабатывается сообщение WM_DESTROY, поступающее в функцию окна, когда пользователь закрывает окно. Обработчик этого сообщения посылает в очередь сообщений приложения сообщение WM_QUIT, что приводит к выходу из цикла обработки сообщений и завершению приложения.
17
Обзор сообщений
Рассмотрим более подробно некоторые наиболее часто встречающиеся сообщения.
Сообщение WM_CREATE поступает в функцию окна при создании окна функцией CreateWindow и может обрабатываться приложением , например для инициализации структур данных , связанных с окном. Это сообщение сигнализирует приложению о том , что создание окна завершено. Параметр lParam этого сообщения содержит указатель на структуру CREATESTRUCT, члены который аналогичны параметрам функции CreateWindowEx (см. справочную документацию или электронный справочник WIN32 Programmers Reference системы программирования Borland C++ 5.01). Обработчик этого сообщения должен вернуть значение 0 для продолжения процедуры создания окна или 1 для прекращения этой процедуры (в этом случае CreateWindow вернет значение NULL).
Для того , чтобы сделать окно видимым , приложение вызывает функцию ShowWindow. Эта функция порождает множество сообщений, большинство из которых обычно направляется функции DefWindowProc. Исключение составляют сообщения WM_SIZE и WM_MOVE, которые довольно часто обрабатываются приложением.
Сообщение WM_SIZE информирует приложение о новых размерах окна и о способе , при помощи которого окно изменило свой размер. Параметр wParam содержит константу, информирующую о способе изменения размера окна :
SIZE_MAXHIDE максимизировано какое-либо другое окно (посылается временным окнам);
SIZE_MAXIMIZED максимизировано данное окно;
SIZE_MAXSHOW какое-либо другое окно восстановлено в прежних размерах (посылается временным окнам);
SIZE_MINIMIZED - минимизировано данное окно;
SIZE_RESTORED окно изменило размеры, но не было максимизировано или минимизировано.
Младшее слово параметра lParam задает новую ширину рабочей области окна (client area), а старшее слово этого параметра задает ее новую высоту. Обработчик сообщения должен вернуть нулевое значение.
Сообщение WM_MOVE поступает при перемещении окна по экрану. В ответ на эти сообщения приложение обычно изменяет значение собственных переменных , определяющих координаты окна , и выполняет другие действия , связанные с изменением размера и местоположения окна. Младшее слово параметра lParam данного сообщения задает новую горизонтальную, а старшее слово новую вертикальную координату верхнего левого угла рабочей области окна. Обработчик сообщения должен вернуть нулевое значение.
Сообщение WM_PAINT генерируется Windows при возникновении необходимости перерисовать окно (например , при увеличении его размера) или при вызове приложением некоторых функций API ( UpdateWindow ). Обычно , все операции по выводу на экран выполняются обработчиком сообщения WM_PAINT. Подробно об этом сообщении говорится в главе, посвященной интерфейсу графических устройств GDI.
Сообщение WM_COMMAND является, наверное, одним из самых широко используемых сообщений. Оно поступает в функцию родительского окна от его дочерних окон, когда пользователь выполняет определенные операции с дочерними окнами. Подробно это сообщение рассматривается в главе, посвященной органам управления.
Сообщение WM_DESTROY поступает в функцию окна , когда пользователь закрывает окно. Типичный обработчик этого сообщения для главного окна приложения вызывает функцию PostQuitMessage, которая генерирует сообщение WM_QUIT. В ответ на это сообщение , как указывалось выше , происходит выход из цикла обработки сообщений и завершение приложения.
18
От клавиатуры могут поступать четыре сообщения : WM_KEYDOWN , WM_KEYUP, WM_SYSKEYDOWN , WM_SYSKEYUP. При нажатии клавиши генерируется сообщение WM_KEYDOWN или WM_SYSKEYDOWN , в зависимости от того , какая нажата клавиша и была ли эта клавиша нажата в комбинации с <Alt>. Соответственно при отпускании клавиши генерируется сообщение WM_KEYUP или WM_SYSKEYUP. Параметр wParam этих сообщений содержит , так называемый , код виртуальной клавиши , соответствующий некоторой физической клавише. Коды виртуальных клавиш определены как константы с префиксом VK_ . Параметр lParam содержит OEM scan-код клавиши (тот самый код , который получают в регистре AH программы MS DOS при вызове INT 16h) , счетчик повторов (при удерживании нажатой клавиши несколько сообщений сливаются в одно с соответствующим счетчиком повторов), флаг расширенной клавиатуры (устанавливается для клавиш , имеющихся только на 101-клавишной клавиатуре) , код контекста ( 1 - комбинация с <Alt> , 0 - нет) , флаги предыдущего состояния клавиши и флаг изменения состояния клавиши (см. справочную документацию или электронный справочник WIN32 Programmers Reference системы программирования Borland C++ 5.01). Для получения информации о состоянии клавиш используются также функции GetKeyState , GetAsyncKeyState , GetKeyboardState , GetKeyboardType , GetKeyNameText.
Функция TranslateMessage преобразует клавиатурные сообщения в символьные сообщения WM_CHAR , WM_SYSCHAR , WM_DEADCHAR , WM_SYSDEADCHAR. Как правило, приложение обрабатывает только сообщение WM_CHAR. Образованные символьные сообщения помещаются в очередь сообщений приложения , причем оригинальные клавиатурные сообщения из этой очереди не удаляются. Символьные сообщения используются для непосредственного определения символа по нажатой клавише (код символа в стандарте ANSI заносится в параметр wParam сообщения, параметр lParam содержит ту же информацию, что и для рассмотренных выше оригинальных клавиатурных сообщений). Следует отметить , что Windows использует кодировку символов ANSI , отличную от кодировки OEM MS DOS. Для перекодировки используются функции CharToOem , CharToOemBuff , OemToChar , OemToCharBuff , CharLower , CharLowerBuff , CharUpper , CharUpperBuff , CharNext , CharPrev , VkKeyScanEx. Для более подробной информации следует обратиться к справочной документации или электронному справочнику WIN32 Programmers Reference системы программирования Borland C++ 5.01. Для использования функции TranslateMessage ее необходимо включить в цикл обработки сообщений :
while (GetMessage(&msg , 0 , 0 , 0 ) ) {TranslateMessage(&msg); DispatchMessage(&msg);}
Обработчики всех клавиатурных сообщений должны возвращать нулевое значение.
При любом перемещении мыши по экрану генерируется сообщение WM_NCHITEST. Старшее слово параметра lParam этого сообщения содержит вертикальную, а младшее слово горизонтальную координату курсора мыши в момент появления сообщения. Обычно это сообщение не обрабатывается приложением , а передается функции DefWindowProc . После обработки этого сообщения Windows анализирует положение курсора, а также наличие нажатия клавиши мыши и генерирует одно из нижеперечисленных сообщений. В случае, если курсор находится в рабочей области окна генерируются сообщения :
WM_LBUTTONDBLCLK двойной щелчок левой кнопкой мыши;
WM_LBUTTONDOWN нажата левая кнопка мыши;
WM_LBUTTONUP отпущена левая кнопка мыши;
WM_MBUTTONDBLCLK двойной щелчок средней кнопкой мыши;
WM_MBUTTONDOWN нажата средняя кнопка мыши;
WM_MBUTTONUP отпущена средняя кнопка мыши;
WM_RBUTTONDBLCLK двойной щелчок правой кнопкой мыши;
WM_RBUTTONDOWN нажата правая кнопка мыши;
19
WM_RBUTTONUP отпущена правая кнопка мыши;
WM_MOUSEMOVE перемещение курсора мыши;
WM_MOUSEACTIVATE нажата клавиша мыши над неактивным окном.
Для всех перечисленных сообщений параметр lParam содержит координаты курсора мыши (старшее слово вертикальную, младшее горизонтальную), а параметр wParam представляет логическую комбинацию следующих битовых флагов :
MK_CONTROL нажата клавиша CTRL;
MK_LBUTTON левая кнопка мыши находится в нажатом состоянии;
MK_MBUTTON средняя кнопка мыши находится в нажатом состоянии;
MK_RBUTTON правая кнопка мыши находится в нажатом состоянии;
MK_SHIFT нажата клавиша SHIFT.
Обработчики этих сообщений должны возвращать нулевое значение. Отметим также, что сообщения о двойных щелчках мышью будут поступать в функцию окна только в случае, если класс окна определен со стилем CS_DBLCLKS.
В случае, если курсор мыши находится во внешней области окна (пространстве между внешним контуром окна и его рабочей областью non-client area) генерируются следующие сообщения, смысл которых аналогичен только что рассмотренным : WM_NCLBUTTONDBLCLK, WM_NCLBUTTONDOWN, WM_NCLBUTTONUP, WM_NCMBUTTONDBLCLK, WM_NCMBUTTONDOWN, WM_NCMBUTTONUP, WM_NCMOUSEMOVE, WM_NCRBUTTONDBLCLK, WM_NCRBUTTONDOWN, WM_NCRBUTTONUP. Параметр wParam cообщений WM_NC* содержит значение, возвращаемое функцией DefWindowProc как результат обработки сообщения WM_NCHITEST, а параметр lParam координаты курсора в структуре типа POINTS :
typedef struct tagPOINTS { SHORT x; SHORT y; } POINTS;
Реакция на события от мыши во внешней области окна предопределена в Windows, так как в этом пространстве находятся такие элементы, как рамка окна, заголовок окна, строки меню и т.п. Поэтому в абсолютном большинстве случаев приложение не обрабатывает сообщения WM_NC*, поручая это Windows посредством вызова функции DefWindowProc. Если все же необходима обработка приложением названных сообщений, то их обработчик должен по окончании обработки вызвать функцию DefWindowProc для сохранения стандартной реакции Windows на соответствующее событие.
Мы кратко рассмотрели некоторые наиболее часто используемые сообщения. Еще целый ряд сообщений будет рассмотрен при изучении соответствующих тем. Для более полной информации следует обратиться к справочной документации или электронному справочнику WIN32 Programmers Reference системы программирования Borland C++ 5.01. Всего в WIN32 определено более 200 сообщений и, естественно, подробное их рассмотрение выходит за рамки данного пособия.
Макросы распаковщики сообщений
В файле windowsx.h системы программирования Borland C++ 5.01 определены макросы распаковщики сообщений, упрощающие обработку сообщений в функции окна. Как уже отмечалось выше, функция окна фактически представляет собой огромный оператор switch, который может занимать не одну сотню строк исходного текста. Написание подобных операторов противоречит принципам структурного программирования и может приводить к трудно обнаруживаемым ошибкам. Второй серьезной трудностью при программировании функции окна является необходимость обработки параметров сообщений, смысл которых меняется от сообщения к сообщению. Более того, возможно изменение смысла параметров одного и того же сообщения при переходе к новым версиям Windows, как это, например, произошло с параметрами сообщения WM_COMMAND при переходе от WIN16 к WIN32.
20
Легко обойти все эти трудности позволяют распаковщики сообщений. Рассмотрим некоторые из них.
Макрос HANDLE_MSG определен следующим образом :
#define HANDLE_MSG(hwnd, message, fn) \
case (message): return HANDLE_##message((hwnd), (wParam), (lParam), (fn))
Вот как в случае сообщения WM_LBUTTONDOWN после обработки препроцессором будет выглядеть строка HANDLE_MSG(hwnd, WM_LBUTTONDOWN, Cls_OnLButtonDown) :
case (WM_LBUTTONDOWN):
return HANDLE_WM_LBUTTONDOWN((hwnd), (wParam), (lParam), (Cls_OnLButtonDown));
Таким образом, макрос HANDLE_MSG применяется в функции окна и назначает обрабатываемому сообщению отдельную функцию обработчик.
Макросы HANDLE_WM* непосредственно вызывают указанную функцию, после чего производится возврат из функции окна. Вот как, например, определен макрос HANDLE_WM_LBUTTONDOWN:
#define HANDLE_WM_LBUTTONDOWN(hwnd, wParam, lParam, fn) \
((fn)((hwnd), FALSE, (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam), (UINT)(wParam)), 0L)
Результатом раскрытия препроцессором любого из макросов HANDLE_WM_* является вызов функции Cls_On*, которой передаются после соответствующих преобразований типов распакованные части параметров wParam и lParam. Чтобы использовать распаковщик для обработки сообщения следует найти в файле windowsx.h макрос HANDLE_WM* для сообщения, которое необходимо обработать. В строке-комментарии перед макросом указан прототип функции обработчика сообщения. В случае WM_LBUTTONDOWN эта строка выглядит следующим образом :
/* void Cls_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y,
UINT keyFlags) */
Макросы FORWARD_WM* решают обратную задачу принимают распакованные параметры сообщения, воссоздают по ним исходные параметры wParam и lParam, а затем вызывают функцию, указанную в качестве последнего параметра макроса, передавая ей воссозданные параметры. Вот как это выглядит в случае сообщения WM_LBUTTONDOWN:
#define FORWARD_WM_LBUTTONDOWN(hwnd, fDoubleClick, x, y, keyFlags, fn) \
(void)(fn)((hwnd), (fDoubleClick) ? WM_LBUTTONDBLCLK : WM_LBUTTONDOWN, (WPARAM)(UINT)(keyFlags), MAKELPARAM((x), (y)))
В качестве вызываемой функции может быть DefWindowProc, SendMessage или любая другая функция, имеющая аналогичный набор параметров.
Ниже приведена модифицированная функция окна из рассмотренного нами ранее примера, а также соответствующие функции-обработчики сообщений. Предлагаем читателю самостоятельно разобраться с использованием распаковщиков сообщений в этом примере.
// =======================================================================
// Функция WndProc , выполняет обработку сообщений для главного окна приложения
// =======================================================================
LRESULT WINAPI
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{ // Выполняем обработку сообщений. Идентификатор
// сообщения передается через параметр msg
switch (msg)
{ // Для сообщения WM_LBUTTONDOWN назначаем обработчик,
//расположенный в функции Cls_OnLButtonDown
HANDLE_MSG(hwnd, WM_LBUTTONDOWN, Cls_OnLButtonDown);
// Для сообщения WM_RBUTTONDOWN назначаем обработчик,
// расположенный в функции Cls_OnRButtonDown
21
HANDLE_MSG(hwnd, WM_RBUTTONDOWN, Cls_OnRButtonDown);
// Для сообщения WM_KEYDOWN назначаем обработчик,
// расположенный в функции Cls_OnKey
HANDLE_MSG(hwnd, WM_KEYDOWN, Cls_OnKey);
// Для сообщения WM_DESTROY назначаем обработчик,
// расположенный в функции Cls_OnDestroy
HANDLE_MSG(hwnd, WM_DESTROY, Cls_OnDestroy);
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
}
//========================================================================
// Обработчик сообщения WM_LBUTTONDOWN
//========================================================================
#pragma argsused
void Cls_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
{ MessageBox(NULL, "Нажата левая кнопка мыши",
"Сообщение WM_LBUTTONDOWN", MB_OK | MB_ICONINFORMATION); }
//========================================================================
// Обработчик сообщения WM_RBUTTONDOWN
//========================================================================
#pragma argsused
void Cls_OnRButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
{ MessageBox(NULL, "Нажата правая кнопка мыши",
"Сообщение WM_RBUTTONDOWN", MB_OK | MB_ICONINFORMATION); }
//========================================================================
// Обработчик сообщения WM_KEYDOWN
//========================================================================
#pragma argsused
void Cls_OnKey(HWND hwnd, UINT vk, BOOL fDown, int cRepeat, UINT flags)
{ MessageBox(NULL, "Нажата клавиша на клавиатуре",
"Сообщение WM_KEYDOWN", MB_OK | MB_ICONINFORMATION); }
//========================================================================
// Обработчик сообщения WM_DESTROY
//========================================================================
#pragma argsused
void Cls_OnDestroy(HWND hwnd) { PostQuitMessage(0);}
Ресурсы приложения
Загрузочный модуль приложения, кроме сегментов кода и данных, может также содержать дополнительные данные в специальном формате, называемые ресурсами приложения. К числу наиболее часто используемых типов ресурсов относятся следующие :
22
Для включения ресурсов в загрузочный модуль приложения необходимо создать текстовый файл описания ресурсов (*.rc) и включить его в состав создаваемого программного проекта. В процессе сборки загрузочного модуля этот файл обрабатывается компилятором ресурсов , в результате чего ресурсы включаются в состав exe-файла приложения , там же формируется таблица ресурсов , которая используется для поиска и загрузки ресурсов в оперативную память. Промежуточным результатом данного процесса является скомпилированный двоичный файл описания ресурсов (*.res).
Файл описания ресурсов может быть создан простым текстовым редактором. Кроме того подобный файл является результатом работы специальных приложений, например , Resource Workshop. Редактор ресурсов Resource Workshop встроен в систему программирования Borland C++ 5.01 и автоматически запускается при выборе пункта меню File|New|Resource project или при загрузке файла одного из типов, обрабатываемых редактором ресурсов. Кроме текстового файла описания ресурсов, Resource Workshop работает также с двоичным файлом описания ресурсов (.res), а также файлами, содержащими отдельные типы ресурсов (*.ico, *.cur, *.bmp). Более того, возможно редактирование ресурсов непосредственно в загрузочном модуле (*.exe, *.dll) без повторной компиляции. Это позволяет легко создавать локализованные (переведенные на национальный язык) версии приложений , редактировать графические изображения или любые другие ресурсы , даже не имея исходных текстов приложения.
Рассмотрим процесс включения некоторых из перечисленных выше типов ресурсов в состав приложения. Ресурсы типа меню, акселераторы меню и шаблон диалога будут рассмотрены позднее при изучении соответствующих тем.
Для создания таблицы текстовых строк файл описания ресурсов должен включать оператор STRINGTABLE :
STRINGTABLE [параметры загрузки] [тип памяти]
BEGIN
StringID , ASCIIZ - строка
. . .
END
Операторы BEGIN и END определяют границы таблицы строк в файле определения ресурсов. Между ними находятся строки с идентификаторами StringID . Идентификаторы строк, как и любых других ресурсов, являются целыми константами, заданными непосредственно в операторе STRINGTABLE или через #define (при этом обычно используется префикс IDS_ перед именем). В качестве параметров загрузки можно указывать значения PRELOAD (ресурс загружается в память одновременно с загрузкой приложения) или LOADONCALL (используется по умолчанию , ресурс загружается в память при обращении к нему со стороны приложения). Тип памяти, выделяемой при загрузке ресурса , может быть FIXED (ресурс будет находиться в памяти по постоянному адресу) или MOVEABLE (ресурс может перемещаться при выполнении Windows дефрагментации памяти). Дополнительно для перемещаемого ресурса можно указать тип DISCARDABLE (ресурс может быть размещен в виртуальной памяти). По умолчанию используется тип MOVEABLE DISCARDABLE. Возможно также указание параметра PURE, блокирующего возможность модификации участка памяти, занятого ресурсом (read only).
Для загрузки строки в оперативную память необходимо использовать функцию LoadString, имеющую следующий прототип :
int LoadString(
HINSTANCE hInstance, // идентификатор приложения, содержащего ресурс
UINT uID, // идентификатор ресурса
LPTSTR lpBuffer, // адрес буфера для ресурса
int nBufferMax // размер буфера );
23
Во всех функциях API, работающих с ресурсами, и, в частности, в функции LoadString, в качестве параметра идентификатора ресурса следует использовать константу, заданную символическим именем. Если же для идентификатора используется целое число, этот параметр должен быть обработан макросом MAKEINTRESOURCE :
#define MAKEINTRESOURCE(i) (LPSTR)((DWORD)((WORD)(i))).
Использование рассматриваемого типа ресурсов предпочтительнее прямого задания строковых констант в теле приложения, так как позволяет модифицировать эти константы без перекомпиляции приложения.
В загрузочный модуль приложения можно добавить ресурс - пиктограмму. Пиктограммы хранятся в файлах с расширением . ico , хотя может использоваться и любое другое расширение. Файл с пиктограммой должен быть включен в состав компилируемого программного проекта. Приложение Resource Workshop позволяет создавать пиктограммы различного размера , использующие различное число цветов. В одном файле *.ico можно хранить несколько пиктограмм различного размера и с разным количеством цветов. В этом случае Windows при выводе пиктограммы сделает правильный выбор для текущего режима работы видеоадаптера.
Для включения файла с пиктограммой в состав компилируемого проекта , необходимо включить в файл описания ресурсов оператор ICON :
IconID ICON [параметры загрузки] [тип памяти] имя файла
Идентификатор пиктограммы IconID можно указывать либо как символическое имя (рекомендуется использовать префикс IDI_ перед именем константы), либо как целое число - идентификатор ресурса . В качестве имени файла необходимо указать имя файла , содержащего пиктограмму. После сборки проекта файл пиктограммы будет встроен в exe-файл приложения. Остальные параметры оператора ICON аналогичны параметрам оператора STRINGTABLE .
Для загрузки пиктограммы из файла приложения используется функция LoadIcon :
HICON LoadIcon(
HINSTANCE hInstance, // идентификатор приложения, содержащего ресурс
LPCTSTR lpIconName // идентификатор ресурса );
Функция LoadIcon возвращает идентификатор загруженной пиктограммы типа HICON, который затем используется как параметр для функций , работающих с данной пиктограммой, или NULL при ошибке. Если первый параметр функции указан как NULL, функция загружает одну из пиктограмм, заданных вторым параметром и входящих в системные ресурсы :
IDI_APPLICATION пиктограмма приложения по умолчанию;
IDI_ASTERISK информационное сообщение ( i );
IDI_EXCLAMATION предупреждающее сообщение ( ! );
IDI_HAND критическое предупреждающее сообщение ( STOP );
IDI_QUESTION вопрос или запрос данных ( ? );
IDI_WINLOGO логотип Windows.
Функция LoadIcon используется для загрузки пиктограмм из ресурсов приложения или системных ресурсов при регистрации класса окна. Кроме того, пиктограмма может быть отображена в окне приложения при помощи функции DrawIcon :
BOOL DrawIcon(
HDC hDC, // контекст отображения
int X, // x-координата верхнего левого угла
int Y, // y-координата верхнего левого угла
HICON hIcon // идентификатор пиктограммы, возвращаемый LoadIcon ).
Предварительно следует получить контекст отображения. Операции с контекстом отображения будут рассмотрены при изучении интерфейса графических устройств GDI.
24
Вместо имени файла с пиктограммой в операторе ICON может в фигурных скобках быть задан непосредственно битовый образ пиктограммы в шестнадцатиричном виде.
Функция DestroyIcon используется для освобождения оперативной памяти , занятой пиктограммой :
BOOL DestroyIcon( HICON hIcon // идентификатор пиктограммы, возвращаемый LoadIcon );
В качестве еще одного ресурса приложение может использовать курсор мыши - битовое изображение , аналогичное пиктограмме. Изображение курсора формируется приложением Resource WorkShop в файле с расширением .cur . Аналогично пиктограмме , для включения курсора в файл описания ресурсов используется специальный оператор CURSOR , все параметры которого имеют тот же смысл , что и параметры оператора ICON (для констант идентификаторов ресурса рекомендуется использовать префикс IDC_ ):
CursorID CURSOR [параметры загрузки] [тип памяти] имя файла
Для загрузки курсора из файла приложения используется функция LoadCursor :
HCURSOR LoadCursor(
HINSTANCE hInstance, // идентификатор приложения, содержащего ресурс
LPCTSTR lpCursorName // идентификатор ресурса );
Функция LoadCursor возвращает идентификатор загруженного курсора типа HCURSOR или NULL при ошибке. Если первый параметр функции указан как NULL, функция загружает один из стандартных курсоров, заданных вторым параметром и входящих в системные ресурсы :
IDC_APPSTARTING стандартная стрелка и маленькие песочные часы;
IDC_ARROW - стандартная стрелка;
IDC_CROSS - перекрестие;
IDC_IBEAM текстовый курсор;
IDC_ICON пустая пиктограмма;
IDC_NO перечеркнутая окружность;
IDC_SIZE перекрестие из стрелок;
IDC_SIZEALL аналогично IDC_SIZE
IDC_SIZENESW, IDC_SIZENS, IDC_SIZENWSE, IDC_SIZEWE индикация изменения размера;
IDC_UPARROW вертикальная стрелка;
IDC_WAIT песочные часы.
Курсор, используемый приложением, определяется при регистрации класса окна через соответствующее поле структуры WNDCLASSEX. Функция SetCursor позволяет динамически изменять форму курсора , делая текущим загруженный в память курсор , идентификатор которого передан ей в качестве параметра:
HCURSOR SetCursor( HCURSOR hcur );
Функция ShowCursor позволяет делать курсор невидимым:
int ShowCursor( BOOL bShow // флаг видимости курсора );
Специальной функции для рисования курсора нет , так как эта операция выполняется Windows автоматически. Однако , можно отобразить курсор в окне приложения, передав функции DrawIcon идентификатор курсора , возвращаемый функцией LoadCursor. Эта возможность не документирована и может не работать в последующих версиях Windows.
Функция DestroyCursor используется для освобождения оперативной памяти , занятой курсором:
BOOL DestroyCursor( HCURSOR hCursor );
Вместо имени файла с курсором в операторе CURSOR может в фигурных скобках быть задан непосредственно битовый образ курсора в шестнадцатиричном виде.
К ресурсам приложения также относится графическое изображение типа bitmap , которое может быть сформировано ResourceWorkshop в файле с расширением .bmp .
25
Для включения bitmap в файл описания ресурсов используется оператор BITMAP , полностью аналогичный оператору ICON :
BitmapID BITMAP [параметры загрузки] [тип памяти] имя файла .
Для загрузки изображения bitmap используется функция LoadBitmap , назначение параметров которой аналогично назначению параметров функций LoadIcon и LoadCursor :
HBITMAP LoadBitmap( HINSTANCE hInstance, LPCTSTR lpBitmapName );
Если первый параметр функции указан как NULL, функция загружает один из стандартных битовых образов, заданных вторым параметром в виде константы с префиксом OBM_ и входящих в системные ресурсы. Этих констант более 30 и мы не будем их здесь подробно описывать. При необходимости соответствующую информацию можно получить из описания функции LoadBitmap в справочной документации или в электронном справочнике WIN32 Programmers Reference.
Перед завершением работы приложение должно удалить загруженное изображение , вызвав функцию DeleteObject , и передав ей в качестве параметра идентификатор изображения , полученный от функции LoadBitmap :
BOOL DeleteObject( HGDIOBJ hObject );
В API WIN32 отсутствует функция , позволяющая прямо отобразить изображение bitmap в окне приложения. Изображение bitmap является графическим объектом GDI и для его отображения используется ряд функций , работающих с контекстом отображения . Кроме того , следует учитывать наличие двух форматов битовых изображений в Windows (аппаратно-зависимый формат DDB и аппаратно-независимый формат DIB ). Эти вопросы будут рассмотрены при изучении графического интерфейса GDI в следующих главах. Здесь лишь напомним , что произвольное изображение bitmap может быть использовано в качестве кисти для фона окна. Идентификатор кисти возвращается функцией CreatePatternBrush, которой в качестве параметра передается идентификатор загруженного изображения bitmap :
HBRUSH CreatePatternBrush( HBITMAP hbmp );
Вместо имени файла с битовым образом в операторе BITMAP может в фигурных скобках быть задан непосредственно битовый образ в шестнадцатиричном виде.
Для загрузки из загрузочного модуля приложения пиктограммы, курсора или изображения bitmap в WIN32 введена универсальная функция LoadImage, обладающая более широкими возможностями, чем функции LoadIcon, LoadCursor и LoadBitmap:
HANDLE LoadImage(
HINSTANCE hinst, // идентификатор приложения, содержащего ресурс
LPCTSTR lpszName, // идентификатор ресурса
UINT uType, // тип образа
int cxDesired, // желаемая ширина
int cyDesired, // желаемая высота
UINT fuLoad // флаги загрузки );
Подробное описание параметров этой функции можно получить из справочной документации или в электронном справочнике WIN32 Programmers Reference.
В качестве еще одного ресурса приложения могут использоваться произвольные данные. Для этого файл описания ресурсов должен содержать оператор следующего вида :
RId [тип ресурса][параметры загрузки][тип памяти] имя файла
Параметры загрузки, тип памяти и идентификатор ресурса RId имеют тот же смысл, что и для ранее рассмотренных ресурсов. Тип ресурса указывается как произвольная строка символов (разумеется, нельзя использовать предопределенные типы ресурсов, например, ICON или BITMAP). Как вы уже наверное догадались, рассмотренные нами ранее операторы для включения ресурсов являются частными случаями данного оператора. Последним параметром указывается имя файла произвольного формата, содержащего ресурс.
26
Для загрузки ресурса произвольного типа в память вначале следует вызвать функцию FindResource :
HRSRC FindResource(
HMODULE hModule,// идентификатор приложения
// (тип HINSTANCE совместим с HMODULE)
LPCTSTR lpName, // указатель на строку с именем ресурса
LPCTSTR lpType // указатель на строку с типом ресурса );
Эта функция может быть использована для поиска ресурса любого типа в загрузочном модуле. Для стандартных ресурсов в качестве третьего параметра должна быть указана одна из констант с префиксом RT_ . Подробное описание параметров этой функции можно получить из справочной документации или в электронном справочнике WIN32 Programmers Reference.
Возвращаемое данной функцией значение используется в качестве второго параметра функции LoadResource для загрузки ресурса в память :
HGLOBAL LoadResource( HMODULE hModule, HRSRC hResInfo );
Возвращаемое функцией значение является идентификатором блока памяти в куче (heap) приложения, в который загружен ресурс. Windows95, в отличие от Windows NT, автоматически не освобождает память, занятую ресурсом. Поэтому, по завершении работы с ресурсом его следует выгрузить из памяти при помощи функции FreeResource :
BOOL WINAPI FreeResource (HGLOBAL hGlb);
Файл определения модуля
В состав проекта в системе программирования Borland C++ , кроме файлов с исходным текстом программы (*.cpp) и файла описания ресурсов (*.rc), может включаться файл определения модуля ( *. def ) , в котором указывается имя загрузочного модуля приложения , тип exe-файла , атрибуты сегментов кода и данных , объем оперативной памяти для стека и кучи. Если *.def файл не включен в проект, компилятор и компоновщик используют значения параметров, заданные по умолчанию. Ниже приведен пример файла определения модуля для рассмотренного выше примера приложения :
; =============================================================
; Файл определения модуля
; =============================================================
; Имя приложения
NAME HELLO1
; Описание приложения
DESCRIPTION 'Приложение HELLO1, (C) 1997, Sergey O. Derevenskov'
; Определение типа загрузочного модуля как приложения Windows
EXETYPE windows
; Программа-заглушка, которая будет записана в начало файла
; приложения. Эта программа получит управление
; при попытке запуска приложения в среде MS-DOS
STUB 'winstub.exe'
; Размер стека в байтах (минимальное значение 64К, по умолчанию 1М)
STACKSIZE 65536
; Размер кучи (heap) приложения в байтах (минимальное значение 64К, по умолчанию 1М )
HEAPSIZE 65536
; Атрибуты сегмента кода
CODE preload moveable discardable
; Атрибуты сегмента данных
DATA preload moveable multiple