Скелет программы для Windows 2000

Содержание

Теперь, когда представлена вся необходимая предварительная информация, можно приступить к разработке минимального приложения для Windows 2000. Как уже говорилось, все программы для Windows 2000 имеют некоторые общие атрибуты. Скелет программы для Windows 2000, разработанный в этой главе, имеет все необходимые функциональные свойства. В мире Windows-программирования скелеты приложений (другими словами — программы-заготовки) используются довольно часто, поскольку «входная плата» при создании Windows-программ довольно значительна. В качестве примера, сравните, следующие показатели. В отличие от DOS-программ, у которых минимальный размер программы уложится всего в пять строк кода, минимальная программа для Windows составляет примерно пятьдесят строк.

Минимальная программа для Windows 2000 содержит две функции: WinMain() и функцию окна. Функция WinMain() должна выполнить следующие общие действия:


  1. Описать класс окна;
  2. Зарегистрировать этот класс в Windows 2000;
  3. Создать окно данного класса;
  4. Отобразить это окно;
  5. Запустить выполнение цикла обработки сообщений.

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

Перед тем как перейти к подробному обсуждению отдельных вопросов, рассмотрим следующую программу, которая представляет собой минимальный скелет программы для Windows 2000. Эта программа-заготовка создает стандартное окно, содержащее заголовок, кнопки системного меню, а также стандартные кнопки свертывания, развертывания и закрытия окна. Благодаря этому окно можно будет свернуть, развернуть, перемещать по экрану, изменять его размеры и, наконец, закрыть.

/* Минимальный скелет программы для Windows 2000. */

#include <windows.h>

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

char szWinName[] = "MyWin"; /* имя класса окна */

int WINAPI WinMain(HINSTANCE hThisInst, HINSTANCE hPrevInst, 
                   LPSTR lpszArgs, int nWinMode)
{
  HWND hwnd;
  MSG msg;
  WNDCLASSEX wcl;

  /* Определим класс окна. */
  wcl.cbSize = sizeof(WNDCLASSEX); 

  wcl.hInstance = hThisInst;     /* дескриптор даннонго экземпляра */
  wcl.lpszClassName = szWinName; /* имя класса окна */
  wcl.lpfnWndProc = WindowFunc;  /* функция окна */
  wcl.style = 0;                 /* стиль по умолчанию */

  wcl.hIcon = LoadIcon(NULL, IDI_APPLICATION); /* большая пиктограмма */
  wcl.hIconSm = NULL; /* использовать уменьшенный вариант большой
                         пиктограммы */
  wcl.hCursor = LoadCursor(NULL, IDC_ARROW);  /* стиль курсора */

  wcl.lpszMenuName = NULL; /* класс меню отсутствует */
  wcl.cbClsExtra = 0;      /* дополнительная память не требуется */
  wcl.cbWndExtra = 0; 

  /* Сделаем белым цвет фона окна. */
  wcl.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); 

  /* Зарегистрируем класс окна. */
  if(!RegisterClassEx(&wcl)) return 0;

  /* Поскольку класс окна уже зарегистрирован, теперь может
     быть создано окно. */
  hwnd = CreateWindow(
    szWinName, /* имя класса окна */
    "Windows 2000 Skeleton", /* заголовок */
    WS_OVERLAPPEDWINDOW, /* стиль окна - стандартный */
    CW_USEDEFAULT, /* Координата X - пусть решает Windows */
    CW_USEDEFAULT, /* Координата Y - пусть решает Windows */
    CW_USEDEFAULT, /* Ширина - пусть решает Windows */
    CW_USEDEFAULT, /* Высота - пусть решает Windows */
    NULL,          /* Дескриптор родительского окна - родительское
                      окно отсутствует */
    NULL,          /* Дескриптор меню - меню отсутствует */
    hThisInst,     /* Дескриптор экземпляра */
    NULL           /* Дополнительные аргументы отсутствуют */
  );

  /* Отобразим окно. */
  ShowWindow(hwnd, nWinMode);
  UpdateWindow(hwnd);

  /* Создадим цикл оббработки сообщений. */
  while(GetMessage(&msg, NULL, 0, 0))
  {
    TranslateMessage(&msg); /* трансляция клавиатурных сообщений */
    DispatchMessage(&msg);  /* возвратить управление Windows 2000 */
  }
  return msg.wParam;
}

/* Эта функция вызывается Windows 2000 и пересылает 
   сообщения из очереди сообщений.
*/
LRESULT CALLBACK WindowFunc(HWND hwnd, UINT message,
                            WPARAM wParam, LPARAM lParam)
{
  switch(message) {
    case WM_DESTROY: /* завершить программу */
      PostQuitMessage(0);
      break;
    default:
      /* Пусть Windows 2000 обрабатывает все сообщения, не
         перечисленные в предыдущем операторе switch. */
      return DefWindowProc(hwnd, message, wParam, lParam);
  }
  return 0;
}

Давайте тщательно, пункт за пунктом, проанализируем эту программу. Во-первых, все Windows-программы должны содержать заголовочный файл WINDOWS.H, Как уже упоминалось, этот файл (вместе с сопутствующими файлами) содержит прототипы функций API и всевозможные типы, макросы и описания, используемые самой Windows. Например, в файле WINDOWS.H (или в его придаточных файлах) определены типы данных HWND и WNDCLASSEX.

Функция окна, используемая данной программой, называется WindowFunc(). Она объявлена как функция обратного вызова, поскольку именно эту функцию Windows вызывает для взаимодействия с данной программой.

Как уже говорилось, работа программы начинается с выполнения WinMain(). Функции WinMain() передается четыре параметра. Из них hThisInst и hPrevInst — дескрипторы. Дескриптор hThisInst относится к текущему экземпляру программы. Помните, что Windows 2000 является многозадачной системой, поэтому одновременно может выполняться более одного экземпляра вашей программы. Для Windows 2000 дескриптор hPrevInst всегда принимает значение NULL. Параметр lpszArgs является указателем на строку, которая содержит аргументы командной строки, указанные при запуске приложения. В Windows 2000 эта строка содержит всю командную строку, в том числе и имя программы. Параметр nWinMode содержит значение, которое определяет то, как будет отображаться окно в момент, когда программа начнет выполняться.

При выполнении данной функции в ней будут созданы три переменные. Переменная hwnd будет содержать дескриптор окна программы. Структурная переменная msg будет содержать сообщение окна, а структурная переменная wcl будет использоваться для описания класса окна.

Определение класса окна


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


UINT cbSize;            /* размер структуры WNDCLASSEX */
UINT style;             /* тип окна */
WNDPROC lpfnWndProc;    /* адрес функции окна */
int cbClsExtra;         /* дополнительная память класса */
int cbWndExtra;         /* дополнительная память окна */
HINSTANCE hInstance;    /* дескриптор данного экземпляра */
HICON hIcon;            /* дескриптор большой пиктограммы */
HICON hIconSm;          /* дескриптор маленькой пиктограммы */
HCURSOR hCursor;        /* дескриптор указателя мыши */
HBRUSH hbrBackground;   /* цвет фона */
LPCSTR lpszMenuName;    /* имя главного меню */
LPCSTR lpszClassName;   /* имя класса окна */

Как видно из приведенного листинга, cbSize задает размер структуры WNDCLASSEX. Элемент hInstance определяет дескриптор текущего экземпляра и устанавливается в соответствии со значением дескриптора hThisInst. Имя класса окна указывается с помощью поля lpszClassName, которое в нашем случае указывает на строку «MyWin». Адрес функции окна устанавливается в lpfnWndProc. В данной программе не назначается стиль по умолчанию, не требуется никакой дополнительной информации и не определяется главное меню. Хотя большинство программ содержат главное меню, в нем нет никакой необходимости для скелета приложения.

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

Любое приложение для Windows 2000 имеет две связанные с ним пиктограммы: одна большая и одна маленькая. Маленькая пиктограмма используется в том случае, когда окно приложения свернуто. Эта же пиктограмма используется для отображения значка системного меню программы. Большая пиктограмма отображается на экране в том случае, когда вы перемещаете или копируете свое приложение на рабочий стол Windows. Как правило, большие пиктограммы представляют собой растровые изображения размером 32×32 пикселя, а маленькие — размером 16×16 пикселей. Большая пиктограмма загружается посредством API-функции LoadIcon(), чей прототип приведен ниже:

HICON LoadIcon(HINSTANCE hInst, LPCSTR lpszName);

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



Макрос пиктограммыКартинка
IDI_APPLICATIONПиктограмма по умолчанию
IDI_ERRORСимвол ошибки
IDI_INFORMATIONСимвол информации
IDI_QUESTIONЗнак вопроса
IDI_WARNINGВосклицательный знак
IDI_WINLOGOЛоготип Windows

При загрузке пиктограмм следует обратить особое внимание на два важных момента. Во-первых, если ваше приложение не определяет маленькую пиктограмму, будет исследован ресурсный файл большой пиктограммы. Если в нем содержится маленькая пиктограмма, то именно она и будет использована. В противном случае при необходимости маленькая пиктограмма будет получена в результате уменьшения (пропорционального сжатия) большой пиктограммы. Если вы не хотите определять маленькую пиктограмму, присвойте значение NULL параметру hIconSm — именно так и поступает наша программа-заготовка. Во-вторых, функция LoadIcon(), вообще говоря, может применяться только для загрузки большой пиктограммы. Для загрузки пиктограмм произвольного размера можно воспользоваться функцией LoadImage().

Чтобы загрузить указатель мыши, используйте API-функцию LoadCursor(). Данная функция имеет следующий прототип:

HCURSOR LoadCursor(HINSTANSE hInst, LPCSTR lpszName);

Эта функция возвращает дескриптор ресурса курсора или NULL в случае аварийного завершения. В данном примере hInst определяет дескриптор модуля, содержащего курсор мыши, а его имя указывается в параметре lpszName. Чтобы воспользоваться одним из встроенных указателей мыши, необходимо использовать NULL в качестве первого параметра и задать макрос одного из встроенных курсоров мыши в качестве второго параметра. Ниже приведено несколько встроенных курсоров:



Макрос курсора мышиФорма
IDC_ARROWУказатель-стрелка по умолчанию
IDC_CROSSПерекрестие
IDC_HANDРука
IDC_IBEAMВертикальная двутавровая балка
IDC_WAITПесочные часы

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

HGDIOBJ GetStockObject(int object);

Данная функция возвращает дескриптор объекта, определенного параметром object. В случае аварийного завершения возвращается значение NULL. (Тип HGDIOBJ относится к GDI-дескрипторам). Ниже приведено несколько встроенных кистей, доступных вашей программе:



Имя макросаТип фона
BKACK_BRUSHТемно серый
DKGRAY_BRUSHПолупрозрачный (видно сквозь окно)
HOLLOW_BRUSHЧерный
LTGRAY_BRUSHСветло серый
WHITE_BRUSHБелый

Для получения кисти можно использовать эти макросы в качестве параметров функции GetStockObject().

После того как класс окна полностью определен, он регистрируется в Windows 2000 с помощью API-функции RegisterClassEx(), прототип которой приведен ниже:

ATOM RegisterClassEx(CONST WNDCLASSEX *lpWClass);

Эта функция возвращает значение, которое идентифицирует класс окна. ATOM является typedef-описанием типа, которое подразумевает тип WORD. Каждый класс окна принимает уникальное значение. Параметр lpWClass должен содержать адрес структуры WNDCLASSEX.

Создание окна


После того, как класс окна определен и зарегистрирован, ваше приложение может на самом деле создать окно этого класса с помощью API-функции CreateWindow(), прототип которой выглядит следующим образом:

HWND CreateWindow(
  LPCSTR lpszClassName,    /* название класса окна */
  LPCSTR lpszWinName,      /* заголовок окна */
  Dword dwStyle,           /* тип окна */
  int X, int Y,            /* координаты верхней левой точки */
  int Width, int Height,   /* размеры окна */
  HWDN hParent,            /* дескриптор родительского окна */
  HMENU hMenu,             /* дескриптор главного меню */
  HINSTANCE hThisInst,     /* дескриптор создателя */
  LPVOID lpszAdditional,   /* указатель на дополнительную
                              информацию */
);

Как видно из листинга скелета программы, многим параметрам функции СrеateWindow() значения могут присваиваться по умолчанию или же в качестве значения им можно присвоить NULL. В действительности, в качестве параметров X, Y, Width и Height чаще всего используется макрос CW_USEDEFAULT; в этом случае Windows 2000 выбирает подходящий размер и местоположение окна. Если данное окно не имеет родительского окна (а именно этот случай имеет место в нашем скелете программы), то в качестве параметра hParent может быть указан NULL. (Для указания значения этого параметра можно также использовать HWND_DESKTOP.) Если окно не содержит главного меню или использует главное меню, которое определено посредством класса окна, то параметр hMenu должен иметь значение NULL. (Параметр hMenu имеет также и другие применения.) К тому же, если никакая дополнительная информация не требуется, что характерно для большинства случаев, то параметру lpszAdditional можно присвоить значение NULL. (Тип LPVOID переопределяется оператором typedef как void*. Исторически сложилось так, что LPVOID обозначает длинный указатель на void.)

Значения остальных четырех параметров должны быть явно установлены прикладной программой. Во-первых, параметр lpszClassName должен указывать на имя класса окна. (Это то имя, которое вы дали окну при его регистрации.) Заголовок окна — это последовательность символов, на которую указывают посредством lpszWinName. Это может быть и пустая строка, но, как правило, окну следует давать какой-то заголовок. Стиль (или тип) окна, созданного в действительности, определяется значением параметра dwStyle. Макрос WS_OVERLAPPEDWINDOW определяет стандартное окно, которое имеет системное меню, обрамление и кнопки свертывания, развертывания и закрытия окна. Хотя чаще всего используется именно такой стиль окна, вы можете построить окно, удовлетворяющее вашим собственным критериям. Для этого просто объедините с помощью оператора OR макросы различных необходимых вам стилей. Ниже приведены некоторые часто встречающиеся стили:



Макрос стиляФункция Windows
WS_OVERLAPPEDПерекрывающееся окно с обрамлением
WS_MAXIMIZEBOXКнопка развертывания
WS_MINIMIZEBOXКнопка свертывания
WS_SYSMENUСистемное меню
WS_HSCROLLГоризонтальная полоса прокрутки
WS_VSCROLLВертикальная полоса прокрутки

Параметр hThisInst игнорируется операционной системой Windows 2000, но для Windows 95/98 он должен содержать дескриптор текущего экземпляра приложения. Поэтому, чтобы обеспечить совместимость с этими средами, а заодно и предотвратить проблемы в будущем, параметру hThisInst рекомендуется присваивать значение дескриптора текущего экземпляра, как это сделано в нашем скелете программы.

Функция CreateWindow() возвращает дескриптор созданного ею окна или NULL, если окно не может быть создано.

Даже после создания окна оно все еще не отображается на экране дисплея. Чтобы отобразить окно, надо вызвать API-функцию ShowWindow(). Эта функция имеет следующий прототип:

BOOL ShowWindow(HWND hwnd, int nHow);

Дескриптор отображаемого дисплеем окна указывается в параметре hwndM. А параметром nHow определяется режим отображения. Если окно выводится на экран в первый раз, целесообразно в качестве параметра nHow указать значение параметра nWinMode функции WinMain(). Значение nWinMode определяет способ отображения окна сразу после запуска программы на выполнение. Последующие вызовы могут при необходимости отобразить окно в нужном виде или вовсе удалить его. Некоторые общеупотребительные значения параметра nHow приведены ниже:



Макрос отображенияПолучаемый эффект
SW_HIDEУдаляет окно с экрана
SW_MINIMIZEСвертывает окно в пиктограмму
SW_MAXIMIZEРазвертывает окно
SW_RESTOREВозвращает окну обычный размер

Функция ShowWindow() возвращает статус предыдущего режима отображения окна. Если окно выводилось на экран, то возвращается ненулевое значение. А если окно не отображалось на экране, то возвращается нуль.

Хотя с формальной точки зрения для скелета программы это и не обязательно, все же в его текст включен вызов функции UpdateWindow(), поскольку она необходима практически для всех Windows 2000-приложений. По существу, она указывает, что Windows 2000 должна послать вашему приложению сообщение о том, что его главное окно необходимо обновить.

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


Финальная часть функции WinMain() заготовки прикладной программы относится к циклу обработки сообщений. Цикл обработки сообщений является составной частью всех Windows-приложений. Его назначение — принять и обработать сообщение, посланное Windows 2000. Во время выполнения прикладной программы ей постоянно посылаются сообщения. Все эти сообщения сохраняются в очереди сообщений приложения и находятся там до тех пор, пока они не будут извлечены и обработаны. Всякий раз, когда приложение готово извлечь следующее сообщение, оно должно вызвать API-функцию GetMessage(), имеющую следующий прототип:

BOOL GetMessage(LPMSG msg, HWND hwnd, UINT min, UINT max);

Сообщение будет записано в структуру, на которую указывает параметр msg. Все сообщения Windows имеют тип структуры MSG, представленный ниже:

/* Структура сообщения */
typedef stuct tagMSG
{
  HWND hwnd;     /* окно, для которого предназначено сообщение */
  UINT message;  /* сообщение */
  WPARAM wParam; /* информация, обусловленная сообщением */
  LPARAM lParam; /* дополнительная информация, обусловленная
                    сообщением */
  DWORD time;    /* время, когда было отправлено сообщение */
  POINT pt;      /* координаты X и Y местоположения указателя мыши */
} MSG;

В структуре MSG дескриптор окна, которому предназначается сообщение, содержится в hwnd. Все сообщения в Windows 2000 являются 32-разрядными целыми числами, а само сообщение содержится в поле message. В зависимости от конкретного сообщения обусловленная им дополнительная информация передается в wParam и lParam. Оба типа WPARAM и LPARAM являются 32-разрядными значениями.

Время, когда было отправлено (зарегистрировано) сообщение, определяется в миллисекундах в поле time.

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

typedef stuct tagPOINT {
  LONG x, y;
} POINT;

Если в очереди сообщений приложения отсутствуют сообщения, то вызов функции GetMessage() приведет к передаче управления обратно Windows 2000.

Параметр hwnd функции GetMessage() определяет, для какого окна будут получены сообщения. Ведь часто приложение имеет несколько окон, и иногда необходимо принимать сообщения только для конкретного окна. Ну а если этому параметру присвоить значение NULL, то в ваше приложение будут направляться все сообщения.

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

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

Внутри цикла обработки сообщений осуществляется вызов двух функций. Первая — это API-функция TranslateMessage(). Эта функция транслирует генерируемые операционной системой Windows 2000 виртуальные коды клавиш в символьные сообщения. Хотя ее применение необязательно во всех приложениях, в большинстве из них все же используется вызов функции TranslateMessage(), поскольку она необходима для осуществления полной интеграции клавиатуры в вашу прикладную программу.

После того как сообщение было извлечено и преобразовано, оно отсылается обратно в Windows 2000 с помощью API-функции DispatchMessage(). Затем Windows 2000 хранит это сообщение до тех пор, пока не сможет передать его функции окна вашей программы.

Как только завершается цикл обработки сообщений, выполнение функции WinMain() заканчивается, при этом она возвращает Windows 2000 значение msg.wParam. Это значение содержит код возврата, генерируемый при завершении выполнения прикладной программы.

Функция окна


Второй функцией в скелете приложения является его функция окна. В нашем случае эта функция названа WindowFunc(), хотя она может носить любое понравившееся вам имя. Сообщения передаются функции окна посредством Windows 2000. Первые четыре элемента структуры MSG являются его параметрами. Из них единственным параметром, который используется скелетом программы, является собственно сообщение.

Функция окна нашей программы-заготовки реагирует явным образом только на одно сообщение: WM_DESTROY. Такое сообщение посылается тогда, когда пользователь прекращает работу приложения. После получения такого сообщения программа должна осуществить вызов API-функции PostQuitMessage(). Аргументом этой функции является код возврата, который помещается в msg.wParam внутри функции WinMain(). Вызов PostQuitMessage() приводит к передаче приложению сообщения WM_QUIT, что заставляет функцию GetMessage() возвратить значение ЛОЖЬ (false) и, следовательно, прекратить работу вашей программы.

Все остальные сообщения, принимаемые функцией WindowFunc(), передаются операционной системе Windows 2000 посредством вызова DefWindowProc() для стандартной обработки по умолчанию. Этот этап необходим, поскольку все сообщения так или иначе должны быть обработаны.

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