Создание хранителя экрана на C++

Стоит заметить, что наш хранитель экрана будет работать в фоновом режиме, при этом он не должен мешать работе других приложений, и потреблять минимум рессурсов. Технически хранитель экрана является обычным исполняемым файлом Windows (*.exe), переименованным в *.scr, полностью управляемый сообщениями Windows.
При разработке примера я использовал среду Microsoft Visual C++, так как довольно долго с ней работаю, хотя для нашей задачи можно было использовать любой другой компилятор, например Borland C++ Builder или Watcom C++. Для уменьшения объема исполняемого файла в описанной программе не используется библиотека высокого уровня MFC или CLX(VCL), вся работа выполняется только средствами Win32 API. Также не используются объектно-ориентированные расширения языка. В результате удается уменьшить размер программы до 36.8K.

Для создания хранителей экрана в комплект Visual C++ входит заголовочный файл srcnsave.h (D:Program FilesMicrosoft Visual StudioVC98Includescrnsave.h), в котором находятся определения констант и функций на все случаи жизни, необходимых для функционирования screensaver'а в среде Windows 9x/NT, и статическая библиотека scrnsave.lib. Точка входа в программу (функция WinMain) находится в самой scrnsave.lib, что очень сильно облегчает нам жизнь. Наш хранитель пишется ориентировочно для Windows NT (другого у меня нету), хотя должен работать на всех платформах. Различие состоит в том, что для Windows 9x приходится писать еще одну функцию, отвечающую за смену пароля. В NT и выше эту роль выполняет системный процесс Winlogon. Если ключ HKEY_CURRENT_USERControl PanelDesktopScreenSaverIsSecure в системном реестре Windows не равен нулю, то Winlogon будет запрашивать пароль перед выходом из скринсейвера. Хотя без этой функции можно и обойтись.

Итак, приступим к написанию самого кода. Загружаем среду Visual C++ (я использую 6.0). Создаем проект "Win32 Application" (File->New->Projects->Win32 Application). В Project Name вводим ssaver, в Location выбираем папку где будет хранится наш проект, у меня это D:PROJECTS). Жмем Ok. Появится окошко "Win32 Application - Step 1 of 1. Оставляем все без изменений, жмем Fisnish. Ок. Имеем пустой проект. Добавляем новый файл исходного кода в проект (меню File->New->Files->C++ source files). В File name пишем ssaver, жмем Ок. Имеем файл ssaver.cpp. Перед Вами откроется пустое окно, в котором, собственно и будет писаться программа. Настраиваем среду. В меню Build->Set active configuration выбираем "ssaver - Win32 Release", ок. Подключаем бибилиотеку scrnsave.lib к проекту: меню Project->Settings, вкладка Link. Сдесь в строке "Object library/modules" перечислены библиотеки, подключаемые по умолчанию к Вашему проекту, нам надо дописать scrnsave.lib:

Для работы хранителя необходимо написать всего 3 функции (фактически только одну):

  1. LRESULT WINAPI ScreenSaverProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); - является "функцией окна" хранителя. Она получает все сообщения системы (аналог функции WinMain в чистом Windows-приложении). Первый параметр hWnd - идентефикатор окна нашего хранителя. message - код сообщения, которое получил хранитель от системы, wParam и lParam - параметры сообщения. В данной функции программист должен перехватить все интересующие его сообщения, а неперехваченные передать на обработку функции LRESULT WINAPI DefScreenSaverProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
  2. BOOL WINAPI ScreenSaverConfigureDialog (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam); - функция вызывается системой всякий раз когда пользователь нажимает кнопку "настройка..." в окне настройки хранителей экрана (Пуск->Настройка->Панель управления->Экран->Заставка).
  3. BOOL WINAPI RegisterDialogClasses (HANDLE hInst); - вызывается системой для регистрации в ней дополнительных классов (мы ее не будем использовать).

Итак, в новом ранее созданном окне пишем следующий код:

#include
#include

LRESULT WINAPI ScreenSaverProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
return 0;
}

BOOL WINAPI ScreenSaverConfigureDialog (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
return true;
}

BOOL WINAPI RegisterDialogClasses (HANDLE hInst)
{
return true;
}

В первых двух строках подключаются заголовочные файлы с прототипами функций (windows.h - объявления Win32 API, scrnsave.h - функции для работы с хранителем экрана). Далее объявляются 3 основных функции, которые и обеспечивают работу хранителя экрана. Сейчас у нас ScreenSaverProc ничего не делает (будем постепенно ее наращивать). Так как мы не используем никаких специальных настроек, то вторая функция тоже пуста. Нам не нужно создавать дополнительных системных классов, поэтому третья функция должна возвратить true. Жмем F7, среда Visual C++ скомпилирует программу и если не было ошибок, мы получим полноценный хранитель экрана, правда он у нас пока ничего не делает. Зайдите в папку с Вашим проектом, а затем в папку Release. Переименуйте ssaver.exe в ssaver.scr. Теперь поместите ssaver.scr в системную папку Windows (В NT/2000 это C:WINNTSystem32, в 9x С:WINDOWSSYSTEM). Зайдите на панель управления, запустите апплет "экран", дальше вкладка "заставка". В списке появится наш хранитель под именем aver (если файл начинается с "ss", то эти две буквы не показываются):

При нажатии кнопки "Просмотр" ничего не происходит, так и должно быть, ведь функция ScreenSaverProc пуста. Наш хранитель запускается и тут же завершает свою работу. Давайте наполним ее каким-нибудь полезным кодом.

Впишем, например, в функцию ScreenSaverProc код, рисующий два вложенных квадрата, вращающихся в противоположные стороны. Для этого подключим еще один заголовочный файл math.h для работы с математикой: #include . В начале файла после деректив #include определим глобально число pi, и 2*pi, pi/2, чтобы каждый раз их не вычислять:

double pi = 3.1415926;
double pi2 = 2*pi;
double hpi = pi/2;

А функцию ScreenSaverProc наполним следующим:

static PAINTSTRUCT ps = {NULL};
static HDC hDC = NULL;
static HPEN hPen1;
static UINT uTimer = 0;
static int x_max, y_max;
static double step = 0.01, angle = 0;
static int center_x, center_y;

switch(message)
{
case WM_CREATE:
x_max = GetSystemMetrics(SM_CXSCREEN);
y_max = GetSystemMetrics(SM_CYSCREEN);
center_x = x_max / 2;
center_y = y_max / 2;
uTimer = SetTimer(hWnd, 1, 10, NULL);
hPen1 = (HPEN)GetStockObject(WHITE_PEN);
break;

case WM_DESTROY:
if(uTimer) KillTimer(hWnd, uTimer);
PostQuitMessage(0);
break;

case WM_TIMER:
angle += step;
if(angle > pi2) angle = 0;

RECT lpr;
lpr.left = center_x - 102;
lpr.top = center_y - 102;
lpr.right = center_x + 102;
lpr.bottom = center_y + 102;;
InvalidateRect(hWnd, &lpr, true);
break;

case WM_PAINT:
x_max = GetSystemMetrics(SM_CXSCREEN);
y_max = GetSystemMetrics(SM_CYSCREEN);
center_x = x_max / 2;
center_y = y_max / 2;

hDC = BeginPaint(hWnd, &ps);

if(fChildPreview)
{
SetBkColor(hDC, RGB(0, 0, 0));
SetTextColor(hDC, RGB(255, 255, 0));
char szPreview[] = "Мой хранитель :-)";
TextOut(hDC, 15, 45, szPreview, strlen(szPreview));
}
else
{
SetBkColor(hDC, RGB(1, 0, 0));
SetTextColor(hDC, RGB(120, 120, 120));

SelectObject(hDC, hPen1);

           MoveToEx(hDC, center_x + (int)(cos(angle)*(double)100), 
                           center_y + (int)(sin(angle)*(double)(-100)),   NULL);

           LineTo(hDC, center_x + (int)(cos(hpi + angle)*(double)100), center_y + (int)(sin(hpi 
	                  + angle)*(double)(-100)));

           LineTo(hDC, center_x + (int)(cos(pi + angle)*(double)100), center_y + (int)(sin(pi 
                     + angle)*(double)(-100)));

           LineTo(hDC, center_x + (int)(cos(hpi + pi + angle)*(double)100), center_y + 
                        (int)(sin(hpi + pi + angle)*(double)(-100)));

           LineTo(hDC, center_x + (int)(cos(angle)*(double)100), 
                        center_y + (int)(sin(angle)*(double)(-100)));

MoveToEx(hDC, center_x + (int)(cos(-angle)*(double)50), center_y + (int)(sin(-angle)*(double)(-50)), NULL);
LineTo(hDC, center_x + (int)(cos(hpi - angle)*(double)50), center_y + (int)(sin(hpi - angle)*(double)(-50)));
LineTo(hDC, center_x + (int)(cos(pi - angle)*(double)50), center_y + (int)(sin(pi - angle)*(double)(-50)));
LineTo(hDC, center_x + (int)(cos(hpi + pi - angle)*(double)50), center_y + (int)(sin(hpi + pi - angle)*(double)(-50)));
LineTo(hDC, center_x + (int)(cos(-angle)*(double)50), center_y + (int)(sin(-angle)*(double)(-50)));

static char szAuthor[] = "Programmed by Ivan Gavrilyuk. mailto: ivg@hotbox.ru";
TextOut(hDC, 0, y_max - 20, szAuthor, strlen(szAuthor));
}
EndPaint(hWnd, &ps);
break; default:
return DefScreenSaverProc(hWnd, message, wParam, lParam);
}

 

А теперь подробно что делает каждая строчка кода. В первых семи определяются переменные для дальнейшего использования. ps - экземпляр структуры PAINTSTRUCT (рассмотрим далее), hDC - идентефикатор контекста дисплея, hPen1 - кисть для рисования, uTimer - идентефикатор таймера (используется для анимации квадратов), x_max, y_max - в этих переменных будет хранится разрешение экрана. step, angle - приращение угла поворота и сам угол поворота квадрата. center_x, center_y - координаты центра квадрата. Далее с помощью функции switch() организуется ветвление в зависимости от того, какое сообщение пришло от системы (message).

WM_CREATE: это сообщение приходит один раз при создании приложения. При помощи Win32 API - функции GetSystemMetrix получаем разрешение экрана Вашего монитора и помещаем в переменные x_max и y_max. Находим координаты цента экрана обычным делением на 2 предыдущих параметров и помещаем в center_x и center_y. Устанавливаем при помощи функции SetTimer виртуальный таймер в систему. Здесь hWnd - идентефикатор окна, которое будет получать сообщения от таймера. Второй параметр - порядковый номер таймера в нашем проложении (можно установить несколько), третий - время в милисикундах, через которое приложение должно получать сообщения от таймера, устанавливаем на 10 милисекунд. Четвертый - функция таймера, она будет получать управление через к-во милисек., заданное в третьем параметре. Так так мы написали NULL, то таймер будет извещать окно приложения, посылая ему сообщение WM_TIMER. Осталось разобраться с функцией GetStockObject(). Она извлекает графический объект из стандартного репозитория Windows. В нашем случае мы достаем белую кисть (WHITE_PEN), которой будем рисовать в дальшейшем.

WM_DESTROY: приходит тоже один раз при уничтожении окна нашего приложения. Сдесь мы при помощи функции KillTimer() снимаем таймер с нашего окна. hWnd - идентефикатор нашего окна, uTimer - указатель на таймер, полученный функцией SetTimer. И Функцией PostQuitMessage посылаем сообщение системе о выходе из приложения. Если этого не сделать, то окно будет уничтожено, но программа будет продолжать работать.

WM_TIMER: это сообщение будет приходить от установленного нами виртуального таймера каждые 10 милисеккунд. Сдесь мы увеличиваем угол поворота наших квадратов на step, проверяя не больше ли он чем число 2*пи (полный оборот), если да то обнуляем. Затем мы посылаем сообщение нашему приложению при помощи функции InvalidateRect() о необходимости перерисовать область окна, размеры которого задаются во втором параметре (структурой lpr), предварительно проинициализировав ее. Обновляется квадратная область размером 204x204 в центе экрана. Третий параметр в функции InvalidateRect() указывает на необходимость очищать область перед обновлением (true - да, false - нет). hWnd - указатель на окно, которое нужно обновлять.

WM_PAINT: это сообщение происходит при перерисовке окна. Первыми 4-мя строками мы снова узнаем разрешение экрана и высчитываем центр. Далее вызываем функцию BeginPaint(). Она подготавливает определенное окно для рисования (hWnd), заполняет структуру типа PAINTSTRUCT (ps) информацией о рисовании, и возвращает в hDC указатель на контекст устройства, в нашем случае дисплея. В условном операторе определяем находимся ли мы в режиме просмотра или окно развернуто на полный экран, это можно проверить при помощи флага fChildPreview (true - просмотр). Если в режиме просмотра, то выводим текстовую строку "Мой хранитель :-)". Для этого сначала функцией SetBkColor() устанавливаем цвет фона для нашего контекста устройства (hDC), макрос RGB(r, g, b) преобразует интенсивность красного (r), зеленого (g) и желтого (b) цветов в тип COLORREF, переменную такого типа принимает в качестве второго параметра функция SetBkColor(). Аналогично ф-ей SetTextColor() устанавливаем цвет текста. И выводим строку szPreview при помощи ф-и TextOut() на дисплей. Она принимает в качестве параметров указатель контекста, координаты строки, саму строку и ее длину (вычисляем при помощи встроенной ф-и strlen()). У Вас должна получиться следующая картинка:

Теперь обрабатываем случай, когда окно развернуто на весь экран (после else). Первые две строчки Вам знакомы - установка цвета фона и текста. SelectObject() выбирает в контекст дисплея (hDC) белую кисть hPen1, которую мы достали, когда приходило сообщение WM_CREATE. Далее используем стандартные GDI-функции ядра Windows для вывода наших вложенных квадратов, повернутых на угол angle. Здесь используются две функции: MoveToEx и LineTo. Первая служит для перемещения графического курсора на контексте, заданном в первом параметре, в точку с координатами во 2-м и 3-м параметрах. 4-й параметр обычно не используется (NULL). По умолчанию ось OX проходит слева направо, OY - сверху вниз, а отсчет ведется в пикселях. LineTo() рисует линию на контексте hDC из текущей позиции курсора в точку, заданную 2-м и 3-м параметром текущей кистью (у нас она белая), дополнительно передвигая графический курсор. Одна вершина квадрата вычисляется по формулам x = cos(angle), y = sin(angle), остальные поворачиваются на углы pi/2, pi, 3*pi/2 относительно нее, тем самым они оказываются в вершинах квадрата. И сдвигаем центр поворота из начала координат в центр экрана. Вершины второго квадрата вычисляются аналогично, но поворачиваются на -angle, чтобы он вращался в противоположную сторону. angle изменяется от 0 до 2*pi, пробегая при этом полную окружность. B конце выводим строку в нижней часте экрана. Вот и все. Осталось сообщить системе, что мы закончили рисовать, это делается при помощи функции EndPaint(), она принимает параметры, аналогичные BeginPaint().

Теперь компилируйте проект (F7), переименовывайте ssaver.exe в ssaver.scr, пихайте в системную папку Windows и наслаждайтесь :) Должно получиться что-то вроде этого:

Замечания и предложения mailto: ivg@hotbox.ru



Опубликовал admin
31 Окт, Воскресенье 2004г.



Программирование для чайников.