Иллюстрированный самоучитель по Visual Studio .NET
Структура Windows-приложения
Рассмотренная модель выработки и прохождения сообщений поможет вам понять структуру, принятую для всех Windows-приложений. Последние два блока в рассмотренной схеме (рис. 3.1) определяют особенности строения любого Windows-приложения.
Простейшее из них должно состоять как минимум из двух функций:
- функции winMain, с которой начинается выполнение программы и которая «закручивает» цикл ожидания сообщений (message pump);
- оконной процедуры, которую вызывает система, направляя ей соответствующие сообщения.
Каждое приложение в системе, основанной на сообщениях, должно уметь получать и обрабатывать сообщения из своей очереди. Основу такого приложения в системе Windows представляет функция winMain, которая содержит стандартную последовательность действий. Однако обрабатывается большинство сообщений окном – объектом операционной системы Windows.
Примечание
C точки зрения пользователя, окно – это прямоугольная область экрана, соответствующая какому-то приложению или его части. Вы знаете, что приложение может управлять несколькими окнами, среди которых обычно выделяют одно главное окно-рамку (Frame Window). С точки зрения операционной системы, окно – это в большинстве случаев конечный пункт, которому направляются сообщения. С точки зрения программиста, окно – это объект, атрибуты которого (тип, размер, положение на экране, вид курсора, меню, зна-чек, заголовок) должны быть сначала сформированы, а затем зарегистрированы системой. Манипуляция окном осуществляется посредством специальной оконной функции, которая имеет вполне определенную, устоявшуюся структуру.
Функция winMain выполняется первой в любом приложении. Ее имя зарезервировано операционной системой. Она в этом смысле является аналогом функции main, с которой начинается выполнение С-программы для DOS-платформы. Имя оконной процедуры произвольно и выбирается разработчиком. Система Windows регистрирует это имя, связывая его с приложением.
Главной целью функции winMain является регистрация оконного класса, создание окна и запуск цикла ожидания сообщений.
Главная функция windows приложения
На этом шаге мы рассмотрим общую структуру Windows- приложения .
Опишем основные элементы архитектуры 32-разрядных ОС Windows .
Процессы и потоки
Windows -приложения состоят как минимум из одного процесса. Процесс — это экземпляр выполняющейся программы. Ему выделяются адресное пространство и ресурсы. В рамках одного процесса может быть запущено несколько потоков.
Поток — это минимальная программная единица, для выполнения которой планировщик выделяет процессорное время. Поток запускается в адресном пространстве процесса и использует его ресурсы.
Процесс имеет как минимум один исполняемый поток, называемый первичным потоком . Для выполнения фоновых задач можно создавать вторичные потоки, используя таким образом преимущества многозадачности. Приложения, применяющие несколько потоков, называются многопоточными .
Запуск приложения
При запуске приложения операционная система создает процесс и начинает выполнение его первичного потока. Когда поток заканчиает свою работу, заканчивается и процесс. Информация о первичном потоке передается операционной системе в виде адреса функции. Поэтому все Windows -приложения содержат вызываемую при запуске функцию WinMain() , адрес которой и передается в качестве адреса первичного потока.
Затем приложение создает окно. Но до его отображения на экране в операционной системе должны быть зарегистрированы оконные классы — шаблоны, содержащие информацию о свойствах окна. В процессе регистрации окно связывается с оконной процедурой, которая позволяет задать отображаемую этим окном информацию, а также его реакцию на действия пользователя посредством обработки сообщений системы.
Сообщения Windows
Окно служит для взаимодействия пользователя с приложением, а для взаимодействия операционной системы, приложения и его компонентов предназначены системные сообщения . Например, при создании экземпляра приложения операционная система посылает ему серию сообщений, отвечающих за его инициализацию. Клавиатура и мышь тоже генерируют сообщения и отправляют их в соответствующее приложение. Таким образом, основная задача программы — обработать получаемые сообщения , то есть передать их ожидающему окну и выполнить некоторые действия, зависящие от типа сообщения и его параметров. Разработчик должен связать сообщения с функциями, которые будут запускаться на них в ответ.
Обработка сообщений приложением
Каждый поток, создающий окно, имеет очередь сообщений — структуру данных, в которой операционная система хранит сообщения. У всех Windows -приложений есть главное окно, имеющее в свою очередь цикл обработки сообщений — часть программы, извлекающую сообщения из очереди и посылающую их в соответствующую оконную процедуру. Эта процедура обрабатывает сообщение или передает его стандартной оконной процедуре, содержащей стандартный обработчик сообщений. Например, сообщение о свертывании окна будет обработано почти всеми приложениями одинаково, так как для этого подходит стандартная оконная процедура.
На рисунке 1 изображено, как сообщения ставятся в очередь и обрабатываются приложением. Обратите внимание на то, как средствами функций PostMessage() и SendMessage() можно послать сообщение. Обратившись к этим функциям или их двойникам из библиотеки MFC — CWnd::PostMessage() и CWnd::SendMessage() , — приложение посылает или принимает сообщения.
Функция PostMessage() помещает сообщение в очередь, связанную с окном, и сразу же завершается, не дожидаясь его обработки ( асинхронное сообщение ). А функция SendMessage() после помещения сообщения в очередь ждет его обработки ( синхронное сообщение ).
Основные этапы построения Windows-приложения
Для создания Win32 -приложения необходимо:
- создать функцию WinMain() , которая является точкой входа в программу;
- зарегистрировать оконные классы и объявить связанные с ними оконные процедуры;
- создать экземпляр главного окна приложения;
- создать цикл обработки сообщений, передающий их в соответствующую оконную процедуру;
- реализовать оконную процедуру, обрабатывающую сообщения.
Создадим простое Windows -приложение без использования MFC .
- Создание Win32 -приложения.
- Запустите Visual C++ .
- В меню File щелкните пункт New .
- Выберите из списка Win32 Application .
- В качестве названия проекта введите MyWin32App .
Рис.2. Выбор типа проекта
Щелкните кнопку ОК . Появится мастер Win32 AppWizard . Выберите A typical «Hello World» application и щелкните кнопку Finish .
Рис.3. Первый шаг Мастера
Рис.4. Перечень исходных файлов
Найдите в файле MyWin32App.cpp :
- функцию WinMain() , в которой происходит инициализация приложения и реализуется цикл обработки сообщений;
- функцию MyRegisterClass() , которая регистрирует оконный класс главного окна приложения;
- оконную процедуру WndProc() , связанную с главным окном в функции MyRegisterClass() . Эта процедура обрабатывает сообщения WM_COMMAND (которые посылаются пунктами меню, элементами управления и «быстрыми» клавишами), WM_PAINT (посылается при запросе на перерисовку некоторой области окна) и WM_DESTROY (посылается при уничтожении окна). Остальные сообщения передаются в стандартную оконную процедуру;
- функцию InitInstance() , создающую и отображающую экземпляр главного окна приложения.
Со следующего шага мы начнем рассматривать структуру MFC- приложения .
Тема: Общая структура Windows-приложения
Программирование Windows-приложений построено на работе с окнами и обработке сообщений, что накладывает на структуру программы определенные ограничения.
Любая программа под Windows состоит минимум из двух функций — WinMain() и оконной функции.
Функция WinMain() должна выполнять следующие операции:
1. Определять класс окна
2. Регистрировать класс окна
3. Поиск уже запущенной копии приложения
4. Создавать окно данного класса
5. Отображать окно
6. Запускать цикл обработки сообщений
Оконная функция предназначена для обработки сообщений, относящихся к данному окну
Исходный текст приложения, которое создает главное окно. При нажатии левой клавишей мыши в клиентской области окна при помощи стандартной диалоговой панели сообщений выдается информация о приложении.
// — Обязательный включаемый файл
// — Прототип функции главного окна
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
// — Объявление глобальных переменных
HINSTANCE hInst; // Дескриптор экземпляра приложения
char ClassName[]=»Window»; // Название класса окна
char AppTitle[]=»Application Win32″; // Заголовок главн окна
// — Главная функция приложения WinMain
int APIENTRY _tWinMain(HINSTANCE hInstance,
LPSTR lpCmdLine, int nCmdShow)
WNDCLASS wc; // Структура для инф-ции о классе окна
HWND hWnd; // Дескриптор главного окна приложения
MSG msg; // Структура для хранения сообщения
// — Проверяем, было ли приложение запущено ранее.
// Если прилож. было запущено ранее, активизировать
// и выдвинуть на передний план его главное окно
if(IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE);
// Работа новой копии прекращается.
// Заполнение полей стр-ры WNDCLASS
memset(&wc, 0, sizeof(wc)); // Очистка полей структуры 0
wc.lpszClassName=ClassName; // Имя класса окон
wc.lpfnWndProc=(WNDPROC)WndProc; //Адр. окон. ф-ции
wc.style=CS_HREDRAW|CS_VREDRAW; // Стиль класса окон
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.cbClsExtra=0; // Дополнительная память
wc.cbWndExtra=0; // Дополнительная память
// Pегистрация класса окна.
// Создаем главное окно приложения.
ClassName, // Имя класса окон
AppTitle, // Заголовок окна
WS_OVERLAPPEDWINDOW, // Стиль окна
CW_USEDEFAULT, // Ширина окна
CW_USEDEFAULT, // Высота окна
NULL, // Дескриптор окна-родителя
NULL, // Дескриптор меню окна
hInst, // Дескриптор экз. приложения
NULL); // Дополнительная информация
// Окно не создано, выдается предупреждение.
MessageBox(NULL,»Window create error», AppTitle,
// Обновление содержимого клиентской области окна.
// Запуск цикла обработки очереди сообщений.
while(GetMessage(&msg, NULL, 0, 0))
// Преобразов. сообщ., получ с помощью клавиатуры
// Отправление сообщения оконной функции
return TRUE; // Завершение работы приложения
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
// Нажата левая кнопка мыши в клиентской обл. окна.
// Вывод информации о приложении с помощью
// диалоговой панели сообщений MessageBox()
MessageBox(hWnd, «Win32 aplication», «Window»,
// Пользователь удалил окно.
// Если это оконная функция главного окна, то следует
// в очередь сообщений послать сообщение WM_QUIT
// Необработанные сообщ. передаются в стандартную
// функцию обработки сообщений по умолчанию.
return DefWindowProc(hWnd, msg, wParam, lParam);
Пояснения к тексту программы
Главная функция приложения WinMain
Точкой входа для любой Windows-программы является функция WinMain, которая всегда определяется следующим образом:
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE
hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
Эта функция использует последовательность вызовов APIENTRY (соглашение о передаче аргументов при вызове функций WINAPI) и, по своему завершению, возвращает операционной системе Windows целое число.
Назначение параметров функции WinMain:
· hInstance называется дескриптором экземпляра приложения. Это уникальное число, идентифицирующее программу, когда она запущена в Windows. Каждая копия одной и той же запущенной несколько раз программы имеет свое значение hInstance.
· hPrevInstance ранее использовался в Win16 и в Win32 всегда равен NULL.
· lpCmdLine является указателем на оканчивающуюся нулем строку, в которой содержаться параметры, переданные программе из командной строки.
· nCmdShow определяет, как приложение первоначально отображается на дисплее: пиктограммой (SW_SHOWMINNOACTIVE) или в виде открытого окна (SW_SHOWNORMAL).
Поиск работающей копии приложения
Для проверки того было ли приложение запущено ранее, используется функция FindWindow, которая позволяет найти окно верхнего уровня по имени класса или по заголовку окна:
HWND FindWindow(LPCTSTR lpClassName,
· lpClassName – это указатель на текстовую строку, в которой задается имя класса искомого окна. На базе одного и того же класса может быть создано несколько окон.
· lpWindowName – это указатель на текстовую строку, содержащую искомый заголовок окна. Если же подойдет любое окно, то параметр lpWindowName имеет значение NULL.
В приведенном тексте функции WinMain, проверяется, было ли ранее приложение запущено и при положительном ответе выдвигается на передний план главное окно ранее запущенной копии с помощью функции SetForegroundWindow(hWnd);
Замечание. Если необходимо, чтобы каждая копия приложения работала как отдельный процесс, то указанный фрагмент кода следует не включать в функцию WinMain.
Регистрация класса окна
Класс окна определяет общее поведение нового типа окон, включая адрес новой оконной процедуры. Новый класс окна регистрируется при вызове приложением следующей функции:
ATOM RegisterClass(const WNDCLASS *lpwc);
Параметр lpwc указывает на структуру типа WNDCLASS, описывающую тип нового окна. Возвращаемое значение является атомом Windows – 16-разрядным значением, идентифицирующим уникальную символьную строку в таблице Windows.
Затем приложение должно создать само окно с помощью функции CreateWindow, которая возвращает дескриптор созданного окна типа HWND:
HWND CreateWindow(LPCSTR lpClassName, LPCSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HANDLE hInstance, LPVOID *lpParam);
У каждого окна в Windows имеется уникальный дескриптор типа HWND. Дескриптор окна – это один из важнейших описателей, которыми оперирует программа для Windows. Для многих функций Windows требуется дескриптор окна, благодаря которому Windows знает, к какому окну применить функцию.
Параметры функции CreateWindow:
· lpClassName указывает имя класса, поведение которого наследует данное окно. Этот класс должен быть зарегистрирован с помощью фунцкии RegisterClass или может быть одним из предопределенных классов элементов управления с именами “button”, “combobox”, “listbox”, “edit”, “scrollbar” и “static”.
· lpWindowName определяет строку, которая выводится в заголовке создаваемого окна.
· dwStyle определяет стиль окна. (см. тему «Классы и стили окон»).
· x, y, nWidth и nHeight задают начальные геометрические размеры окна. Если при задании параметров x, y, nWidth и nHeight использовать константу CW_USEDEFAULT, то Windows установит расположение и размеры окна самостоятельно.
· hWndParent задает для создаваемого окна дескриптор его окна-родителя. Если окно является главным окном приложения, то этому параметру присваивается значение NULL.
· hMenu определяет для окна дескриптор меню. Значение NULL на месте дескриптора меню говорит о том, что у окна будет только меню класса, общее для всех окон этого класса.
· hInstance задает дескриптор экземпляра приложения, которое создает окно.
· lpParam используется для передачи окну дополнительных данных (если их нет, то он должен быть равен NULL).
Для отображения окна на экране используется функция:
BOOL ShowWindow(HWND hwnd, int nCmdShow);
· hwnd задает дескриптор окна.
· nCmdShow задает начальный вид окна на экране (для главного окна приложения используется величина, передаваемая в качестве параметра функции WinMain). Для всплывающих и дочерних окон nCmdShow задает, как окно первоначально отображается: пиктограммой (SW_SHOWMINNOACTIVE) или в виде открытого окна (SW_SHOWNORMAL)..
Для перерисовки рабочей области производится вызов функции:
void UpdateWindow(HWND hwnd);
Функция UpdateWindow передает функции окна сообщение WM_PAINT. Получив его, функция окна обновляет содержимое экрана.
Цикл обработки очереди сообщений
Windows поддерживает очередь сообщений (message queue) для каждой программы, работающей в данный момент в системе. Когда происходит какое-либо событие (нажатие клавиши, щелчок мыши), Windows преобразовывает его в сообщение, которое помещается в очередь сообщений приложения.
Программа извлекает сообщения из очереди сообщений, выполняя блок команд, известный как цикл обработки сообщений (message loop).
Простейший цикл обработки сообщений имеет следующий вид:
while(GetMessage(&msg, NULL, 0, 0))
// преобразование сообщений от клавиат. в символьные
// отправка сообщения окну-адресату приложения
Извлечение сообщений из очереди производится при помощи функции:
BOOL GetMessage(MSG FAR* lpmsg, HWND hwnd, UINT uMsgFilterMin, UINT uMsgFilterMax);
· lpmsg является дальним указателем на структуру сообщения типа MSG (см. тему «Сообщения Windows. Типы сообщений»).
· hwnd определяет дескриптор окна – источника сообщений. Если задать этому аргументу NULL, то программа будет получать сообщения от всех окон, созданных программой.
· uMsgFilterMin и uMsgFilterMax позволяют задать диапазон значений для фильтрации сообщений, получаемых программой. Если на их месте задать 0, то программа будет получать все сообщения.
Если поле message сообщения msg, извлеченного из очереди сообщений, равно любому значению, кроме WM_QUIT, то функция GetMessage возвращает ненулевое значение. Если из очереди извлечено сообщение WM_QUIT, то GetMessage возвращает нуль, что прерывает цикл обработки сообщений.
Каждое получаемое приложением сообщение (за исключением WM_QUIT) направлено одному из окон приложения. Поскольку приложение не должно прямо вызывать функцию обработки окна, для передачи сообщения нужному окну используется функция:
LONG DispatchMessage(const MSG FAR* lpmsg);
Эта функция передает msg обратно в Windows, которая отправляет его для обработки соответствующей оконной процедуре.
Перед вызовом функции DispatchMessage помещена специальная функция, производящая преобразование кодов комбинаций некоторых клавиш в символьные сообщения:
BOOL TranslateMessage(const MSG FAR* lpmsg);
Завершение работы приложения
Приложение завершает свою работу тогда, когда функция WinMain передает управление Windows. Передать управление можно в любом месте WinMain, в том числе и до входа в цикл обработки очереди сообщений.
Однако после входа в цикл обработки очереди сообщений единственным способом завершить приложение является посылка в очередь приложения сообщения WM_QUIT. Сообщение помещается в очередь сообщений только функцией главного окна данного приложения вызовом функции PostQuitMessage.
Реальная работа приложения происходит в оконных функциях, которые определяют, что выводится в рабочую область окна и как окно реагирует на пользовательский ввод. Оконной функции можно назначать любое имя, в Windows-программе может содержаться более одной оконной функции.
Оконная функция всегда связана с определенным классом окна, который регистрируется при помощи функции RegisterClass. На основе одного и того же класса можно создать несколько окон.
Оконная функция определяется следующим образом:
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg,
WPARAM wParam, LPARAM lParam);
Все четыре параметра оконной процедуры идентичны первым четырем полям структуры MSG.
Функция окна вызывается непосредственно Windows и не может вызываться приложением напрямую. В функции окна используется соглашение о связях языка Паскаль, поэтому объявление функции CALLBACK обязательно.
Обработка сообщений в оконной функции
Функция окна получает сообщение из двух источников: из цикла обработки сообщений и от Windows:
· Из цикла обработки сообщений поступают сообщения ввода: перемещение и нажатие клавиш мыши, нажатие и отпускание клавиш клавиатуры и, если установлен генератор событий таймера, сообщения от таймера.
· Windows посылает функции окна сообщения поддержки окна напрямую, минуя очередь приложения и цикл обработки сообщений. Эти сообщения обычно вызваны событиями, требующими немедленной реакции по изменению вида окна.
Каждое получаемое окном сообщение идентифицируется номером, который содержится в параметре msg оконной функции. Если оконная процедура обрабатывает сообщение, то ее возвращаемым значением должен быть 0.
Все сообщения, не обрабатываемые оконной процедурой, должны передаваться функции Windows функцией DefWindowProc. При этом значение, возвращаемое DefWindowProc, должно быть возвращаемым значением оконной функции.
Удаление окна, сообщение WM_DESTROY
Сообщение WM_DESTROY показывает, что Windows находится в процессе ликвидации окна в ответ на полученную от пользователя команду (пользователь вызывает поступление этого сообщения, если нажмет мышью на пиктограмме “Close”, выберет пункт “Close” из системного меню или нажмет комбинацию клавиш Alt+F4).
Главное окно стандартно реагирует на это сообщение, вызывая функцию:
Эта функция ставит сообщение WM_QUIT в очередь сообщений приложения, что заставляет функцию WinMain прервать цикл обработки сообщений и выйти в систему, завершив работу приложения.