Простейшая программа

В первых частях книги мы достаточно часто будем использовать шаблон Win32 Application , который создает нам Visual C++. Поэтому сейчас необхо­димо подробнее рассмотреть некоторые вещи, которые нам пригодятся уже в ближайшем будущем. В специализированной литературе по Visual C++ вы сможете найти более детальное описание, а сейчас мы поговорим о самом необходимом.

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

Итак, откройте проект ctest и обратите внимание на панель Solution Explorer . Здесь расположено дерево, в котором по разделам сгруппировано все, что входит в проект. Мое дерево вы можете увидеть на рис. 1.10, и у вас должно быть что-то подобное. 

Рис.  Дерево проекта

В нашем проекте есть следующие разделы:

Ресурсы проекта

Давайте посмотрим содержание ресурсов. Щелкните дважды по имени файла ctest.rc, и в этой же панели появится вкладка Resource View . В ней в виде дерева представлены ресурсы, разбитые по разделам.

В разделе Accelerator находится список горячих клавиш для программы. Там может быть несколько ресурсов, но мастер пока создал один с именем IDC_CTEST. Мы не будем использовать горячие клавиши, поэтому из дан­ного раздела можно все удалить. Для этого щелкните правой кнопкой по ресурсу и в появившемся меню выберите пункт Delete. С одной стороны, в программе ничего лишнего не должно быть, а с другой — много места мы не выиграем.

Раздел Dialog содержит диалоговые окна. Если программа будет невидима, то и никакие окна ей не нужны. В нашем шаблоне, по умолчанию находится окно IDD_ABOUTBOX — окно с информацией о программе. Для удале­ния окна нужно также щелкнуть правой кнопкой и выбрать в меню пункт Delete.

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

Чтобы пользователь запустил программу, иконка должна быть знакома и не вызывать подозрений. Например, для шуточной программы можно назна­чить иконку программы MS Word . Если в системе у пользователя прячутся расширения для стандартных файлов (используется по умолчанию), то он подумает, что перед ним Word -документ. А если еще и название файла за­манчивое, то он обязательно его запустит.

Чтобы изменить иконку и нарисовать нечто оригинальное, вы должны два­жды щелкнуть на ее названии, и в главном окне откроется простой графи­ческий редактор (рис. 1.11), в котором можно изменить изображение. Ни­чего серьезного в нем нарисовать невозможно, поэтому лучше вставить уже готовое изображение (например, через буфер обмена). Что-то подходящее всегда можно найти в Интернете.

В разделе Menu можно создавать меню для программы. С этим мы познако­мимся на практике немного позже, и для демонстрации примеров мы будем очень часто использовать меню.

В разделе String Table можно хранить строки в виде констант. По умолча­нию там находятся названия заголовков окон. Эти строки много места не занимают, поэтому их можно и оставить. 


Код программы

Теперь познакомимся с кодом программы, который нам сгенерировал мас­тер. Он находится в файле ctest.cpp , и вы можете его увидеть в листинге 1.1. Весь код мы, конечно же, рассматривать не будем. Данная книга не являет­ся самоучителем по языку C++, хотя необходимые вещи рассматриваются очень подробно. Если вы уже знакомы с этим языком программирования, то для вас код файла должен быть понятен. Если нет, то достаточно того, что мы сейчас рассмотрим.

Листинг 1.1. Исходный код файла ctest.cpp
#include "stdafx.h"
#include "ctest.h"
#define MAX_LOADSTRING 100
// Global Variables:
// (Глобальные переменные):
HINSTANCE hInst; // current instance
// (текущий интерфейс)
TCHAR szTitle[MAX_LOADSTRING]; // The title bar text
// (Заголовок окна)
TCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name
// (Имя класса главного окна)
// Forward declarations of functions included in this code module:
// Описание процедур, используемых в этом модуле:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM);

int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
// TODO: Place code here.
//(Поместите свой код здесь)
MSG msg;
HACCEL hAccelTable;

// Initialize global strings
// (Инициализация глобальных строк)
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_CTEST, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);

// Perform application initialization:
// (Инициализация приложения:)
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}

hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_CTEST);

// Main message loop:
//(Главный цикл обработки сообщений:)
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}

return (int) msg.wParam;
}

// FUNCTION (Функция): MyRegisterClass()
// PURPOSE (Предназначение): Registers the window class
// (Регистрация класса окна)
// COMMENTS (Комментарии):
// This function and its usage are only necessary if you want this code
// to be compatible with Win32 systems prior to the 'RegisterClassEx'
// function that was added to Windows 95.
// It is important to call this function so that the application
// will get 'well formed' small icons associated with it.
// (Эта функция и ее использование необходимы, только если вы хотите,
// чтобы этот код был совместим с системой Win32 до функции
// 'RegisterClassEx', которая была добавлена в Windows 95.
// Это важно, вызвать эту функцию так, чтобы приложение получило
// 'хорошо отфарматированную' маленькую иконку, ассоциированную с ним.)

ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;

wcex.cbSize = sizeof(WNDCLASSEX);

wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, (LPCTSTR)IDI_CTEST);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = (LPCTSTR)IDC_CTEST;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, (LPCTSTR)IDI_SMALL);

return RegisterClassEx(&wcex);
}

// FUNCTION (Функция): InitInstance(HANDLE, int)
// PURPOSE (Предназначение): Saves instance handle and creates main
// window
// (Функция сохраняет указатель экземпляра и создает окно)
// COMMENTS (Комментарии):
// In this function, we save the instance handle in a global variable
// and create and display the main program window.
// (В этой функции мы сохраняем указатель экземпляра в глобальной
// переменной, создаем и отобажаем главное окно.)
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;

hInst = hInstance; // Store instance handle in our global variable
// (Сохраняем указатель экземпляра в глобальной переменной)

hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

if (!hWnd)
{
return FALSE;
}

ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

return TRUE;
}

// FUNCTION (Функция): WndProc(HWND, unsigned, WORD, LONG)
// PURPOSE (Предназначение): Processes messages for the main window
// (Обработка сообщений главного окна)
// WM_COMMAND — process the application menu
// (обработка меню приложения)
// WM_PAINT - Paint the main window
// (Прорисовка окна)
// WM_DESTROY — post a quit message and return
// (отправка сообщения о выходе из программы)
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;

switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
// Проверка выбранного меню:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}

// Message handler for about box
// (Обработчик сообщения для окна "О программе")
// Мы окно о программе удалили, поэтому следующий код можно удалять
LRESULT CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_INITDIALOG:
return TRUE;

case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return TRUE;
}
break;
}
return FALSE;
}

Мы уже знаем, что у нашей программы не должно быть никаких окон. Из проекта в разделе Dialog окно О программе мы уже удалили (см. разд. 1.3.1). Но в коде еще остались ссылки на него, поэтому вы не сможете выполнить программу. Чтобы проект запустился, удалите все, что находится после сле­дующей строки:

// Мы окно о программе удалили, поэтому следующий код можно удалять

Этот код отображает окно О программе, и его можно удалять полностью.

Теперь перейдите в процедуру wndProc и удалите здесь вызов процедуры About . Для этого нужно найти и удалить следующие три строчки:

case IDM_ABOUT:
DialogBox(hInst, (LPCTSTR)IDD_ABOUTBOX, hWnd, (DLGPROC)About);
break;

Это обработчик события для пункта меню Help/About нашей программы. Все обработчики находятся в функции WndProc и имеют следующую структуру:

case Идентификатор
Действия
break;

Здесь Идентификатор — это константа, которая назначена элементу управления (например, пункту меню). Оператор case проверяет, если пришло событие от элемента управления с указанным идентификатором, то выпол­няется последующий код до оператора break. Чуть позже мы познакомимся с событиями на практике.

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

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

ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);

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

Первая строка кода показывает созданное в данной программе окно, а вторая — обновляет его содержимое. Вы можете закомментировать строки, поставив перед ними две косые черты (//). Попробуйте теперь скомпилировать и запустить программу. Вы ничего не увидите ни на экране, ни по нажатию клавиш <Ctrl>+<Alt>+<Del>. Если у вас Windows 2000/XP , то только на вкладке Процессы окна Диспетчер задач Windows вы сможете найти в списке свою программу (рис. 1.12).

Если вы не имели опыта программирования на Visual C++ и сейчас чего-то не поняли, не расстраивайтесь. Постепенно все встанет на свои места. В дальнейшем мы рассмотрим достаточно много из того, что вы видите в исходном коде.

Рассмотрим подробнее некоторые части представленного кода. Программа начинает выполнение с функции _tWinMain (листинг 1.2).


Листинг 1.2. Стартовая функция
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
// Дальше идет объявление двух переменных
MSG msg;
HACCEL hAccelTable;

// Инициализация строковых переменных
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hlnstance, IDC_CTEST, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// Выполнение инициализации приложения
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, (LPCTSTR)IDC_CTEST);
// Главный цикл обработки сообщений Windows
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}

Мы уже много раз использовали слово "функция", но не дали ей определение. Если у вас есть опыт программирования в C++, то вы должны быть знакомы с этим понятием. Некоторые вещи выходят за рамки данной книги, и я их буду опускать. Более подробную информации о языке C++ вы можете узнать в специализированной литературе. И все же мы рассмотрим максимальное количество информации, необходимой для понимания примеров.

Все функции в C++ объявляются следующим образом:

 Тип Имя (Параметры)
{
}

У нашей главной функции после возвращаемого типа стоит ключевое слово APIENTRY, которое указывает на точку входа программы.

Теперь посмотрите на листинг 1.1. Здесь я расставил комментарии, чтобы вы понимали, что происходит. Как мы уже знаем, комментарии начинаются с двойной косой черты (//). Текст, который стоит после этих черточек, не влияет на работу программы, а только поясняет код.

В самом начале функции объявляются две переменные:

MSG msg;
HACCEL hAccelTable ;

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

Работа с простыми переменными (строка, число, структура) никаких допол­нительных действий не требует. Но если это объект или указатель, то им нужно выделить память. Объекты используются при программировании с использованием MFC , а указатели — это переменные, которые указывают на определенную область памяти, выделенную для хранения данных.

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

Второй способ — использование глобальных переменных, что не рекомендуется делать. Глобальные переменные видны в любой функции. Их принято определять в заголовочном файле (файл с расширением h) или до описания функций, в самом начале файла.

Локальные переменные объявляются внутри функции, и к ним можно обратиться только в ней. Такие переменные автоматически создаются при запуске функции в специальной области памяти (стеке) и автоматически уничтожаются при выходе из нее. Автоматическое создание/удаление относится только к простым переменным, но не к указателям, которые желательно освобождать вручную.

После объявления переменных идут следующие две строки:

LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_CTEST, szWindowClass, MAX_LOADSTRING);

Эти две функции с именем LoadString загружают текст из ресурсов строк. Функция — это часть кода, которая имеет имя и может вызываться из дру­гих мест программы. В данном случае выполнится код загрузки ресурса.

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

После этого идет вызов функции MyRegisterClass(hInstance). В ней происходит заполнение структуры WNDCLASSEX. Что такое структура? Это особая пе­ременная, которая хранит в себе набор переменных любого типа. Например, структура может хранить одну переменную с именем Age числового типа и одну строкового — с именем Name. Чтобы прочитать или изменить значение этих переменных, нужно написать Структура.Переменная. Структура — это имя структурной переменной, а переменная — это имя переменной.

WNDCLASSEX — это структура, которая используется при создании нового класса окна. Для минимального приложения нам понадобится заполнить следующие поля (основные):

Все, структура готова, и мы можем зарегистрировать новый класс будущего окна. Для этого вызывается функция WinAPI RegisterClassEx(&wcex). После этого в системе есть описание вашего будущего окна. Почему будущего? Да потому, что само окно мы еще не создали. Для этого нужно еще вызвать функцию CreateWindow (это происходит в функции InitInstance, которая в свою очередь вызывается в _tWinMain после вызова MyRegisterClass):

hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

У нее достаточно много параметров, и давайте посмотрим на них внимательнее.

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

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

И последняя подготовительная функция — UpdateWindow. Это просто отрисовка созданного окна.

Теперь разберемся с циклом обработки сообщений. Функция GetMessage ожидает пользовательского или системного сообщения, и как только оно наступает, возвращает true (истина). Полученное сообщение преобразуется

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

В каждой программе должна быть процедура обработки сообщений. Какая именно? Мы указали ее при создании класса окна в свойстве WindowClass.Lpfnwndproc. В Visual C++ принято называть ее wndProc - стандартное имя, используемое по умолчанию. Сама же процедура должна выглядеть приблизительно как в листинге 1.1.

В процедуре-обработчике событий желательно всегда делать вызов функции defwindowproc. Эта функция ищет в системе обработчик полученного сообщения, установленный по умолчанию. Это очень важно, тогда вам не придется без особой необходимости самому писать то, что может сделать ОС. Обработка полученного сообщения происходит с помощью сравнения параметра message со стандартными событиями. Например, если message равен wm_destroy, то это значит, что программа хочет уничтожиться, и тогда в обработчике можно освободить выделенную под программу память.

Вот и все, с шаблоном мы разобрались. Если вы сейчас запустите созданную программу, то перед вами появится пустое окно. Чтобы его закрыть, просто нажмите <Alt>+<F4> или кнопку закрытия окна.

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

Чуть позже мы еще встретимся с процедурой ShowWindow, и не один раз.

Для компиляции проекта выберите команду меню Build/Build Solution. Таким образом, вы соберете проект и создадите запускаемый файл. Чтобы запустить программу, выберите команду меню Debug/Start.

free-templates.ru
Сайт управляется системой uCoz