Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

лекции / Shchupak_Yu._Win32_API_Razrabotka_prilozheniy_dlya_Windows

.pdf
Скачиваний:
0
Добавлен:
11.02.2026
Размер:
13.15 Mб
Скачать

Рисование в реальном времени

551

 

 

Листинг 12.3 (продолжение)

//====================================================================

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

static HMENU hMenu; // дескриптор главного меню RECT rect, rcSB;

int aWidths[4];

static MMRESULT mmTimer;

switch (uMsg)

{

case WM_CREATE:

hMenu = GetMenu(hWnd); SetClassLong(hWnd, GCL_HBRBACKGROUND,

(LONG)CreateSolidBrush(RGB(100, 100, 100)));

sweepSpeedDue = 90; CheckMenuRadioItem(GetSubMenu(hMenu, 0), IDM_SPEED_15,

IDM_SPEED_90, IDM_SPEED_90, MF_BYCOMMAND);

// Создание строки состояния

hwndStatusBar = CreateStatusWindow(WS_CHILD | WS_VISIBLE, "", hWnd, ID_STATUSBAR);

aWidths [0] = 205; aWidths [1] = 355; aWidths [2] = 610; aWidths [3] = -1;

SendMessage(hwndStatusBar, SB_SETPARTS, 4, (LPARAM)aWidths); SendMessage(hwndStatusBar, SB_SIMPLE, FALSE, 0);

digitWeight = 360.0 / 4096;

speedWithPackPeriod = digitWeight / (PACK_PERIOD / 1000.); sieveCoeff = ceil(sweepSpeedDue / speedWithPackPeriod);

UpdateStatusBar();

GetClientRect(hWnd, &rect);

GetWindowRect(hwndStatusBar, &rcSB);

X0 = rect.right / 2;

Y0 = rect.bottom - (rcSB.bottom - rcSB.top);

// Контекст устройства для рисования развертки g_hDC = GetDC(hWnd);

SetColorBeamForth();

break;

case WM_SIZE:

SendMessage (hwndStatusBar, WM_SIZE, wParam, lParam); break;

case WM_COMMAND:

switch (LOWORD(wParam))

{

case IDM_SPEED_15: sweepSpeedDue = 15;

CheckMenuRadioItem(GetSubMenu(hMenu, 0), IDM_SPEED_15, IDM_SPEED_90, LOWORD(wParam), MF_BYCOMMAND);

break;

552

Глава 12. Специальные приложения

 

 

 

case IDM_SPEED_30:

 

 

sweepSpeedDue = 30;

 

 

CheckMenuRadioItem(GetSubMenu(hMenu,

0),

IDM_SPEED_15,

IDM_SPEED_90, LOWORD(wParam), MF_BYCOMMAND); break;

case IDM_SPEED_60: sweepSpeedDue = 60;

CheckMenuRadioItem(GetSubMenu(hMenu, 0), IDM_SPEED_15, IDM_SPEED_90, LOWORD(wParam), MF_BYCOMMAND);

break;

case IDM_SPEED_90: sweepSpeedDue = 90;

CheckMenuRadioItem(GetSubMenu(hMenu, 0), IDM_SPEED_15, IDM_SPEED_90, LOWORD(wParam), MF_BYCOMMAND);

break;

case IDM_START: timer.Start();

mmTimer = timeSetEvent(PACK_PERIOD, 0, TimerFunc, reinterpret_cast<DWORD>(hWnd), TIME_PERIODIC);

if (!mmTimer)

MessageBox(hWnd, "Error of timeSetEvent!", NULL, MB_OK | MB_ICONSTOP);

break;

case IDM_STOP: timeKillEvent(mmTimer); break;

}

sieveCoeff = ceil(sweepSpeedDue / speedWithPackPeriod); UpdateStatusBar();

break;

case WM_DESTROY: ReleaseDC(hWnd, g_hDC); PostQuitMessage(0); break;

default:

return DefWindowProc(hWnd, uMsg, wParam, lParam);

}

return 0;

}

//==================================================================== void UpdateStatusBar()

{

char text[100];

sprintf(text, "Скорость сканирования: %.0f °/с", sweepSpeedDue); SendMessage(hwndStatusBar, SB_SETTEXT, 0, (LPARAM)text);

sprintf(text, "Коэфф. размнож. лучей: %d", sieveCoeff); SendMessage(hwndStatusBar, SB_SETTEXT, 1, (LPARAM)text);

sweepTimeDue = 2.*(RIGHT_MARGIN-LEFT_MARGIN) *

 

 

(PACK_PERIOD/1000.) / sieveCoeff;

 

 

sprintf(text, "Расчетное время просмотра сектора: %.2f с",

 

 

sweepTimeDue);

продолжение

 

 

Рисование в реальном времени

 

553

 

 

 

Листинг 12.3 (продолжение)

 

 

SendMessage(hwndStatusBar, SB_SETTEXT, 2, (LPARAM)text);

 

sprintf(text, "Фактическое время просмотра сектора:

%.2f

ñ",

sweepTimeFact);

 

 

SendMessage(hwndStatusBar, SB_SETTEXT, 3, (LPARAM)text);

 

 

}

 

 

//==================================================================== void CALLBACK TimerFunc(UINT wTimerID, UINT msg, DWORD dwUser, DWORD dw1,

DWORD dw2)

{

// Дескриптор окна-владельца

HWND hwndOwner = reinterpret_cast<HWND>(dwUser);

//(в модели имитатора не используется, но может понадобиться

//в реальном имитаторе для отправки сообщений окну-владельцу)

GetPacket(); // получение пакета из канала связи

DrawBeam(); // вывод луча из полученного пакета

// Размножение лучей

for (int i = 1; i < sieveCoeff; ++i) { GetNextAngle();

DrawBeam();

}

}

//==================================================================== void GetPacket() {

GetNextAngle();

}

//==================================================================== void GetNextAngle() {

char text[100];

if (direction == FORTH)

if (++scanAngle >= RIGHT_MARGIN) { direction = BACK; SetColorBeamBack();

sweepTimeFact = timer.GetTime() / 1000; timer.Start();

sprintf(text, "Фактическое время просмотра сектора: %.2f с", sweepTimeFact);

SendMessage(hwndStatusBar, SB_SETTEXT, 3, (LPARAM)text);

}

if (direction == BACK)

if (--scanAngle <= LEFT_MARGIN) { direction = FORTH; SetColorBeamForth();

}

}

//==================================================================== void SetColorBeamForth() {

for (int i = 0; i < 512; ++i) {

554

Глава 12. Специальные приложения

 

 

if (i < 128)

cell[i] = RGB(255, 0, 0);

else if (i < 256)

cell[i] = RGB(0, 255, 0);

else if (i < 384)

cell[i] = RGB(255, 255, 0);

else

cell[i] = RGB(0, 0, 255);

}

 

}

 

//====================================================================

void SetColorBeamBack()

{

 

for (int i = 0; i < 512; ++i) {

if (i < 128)

 

cell[i] = RGB(0, 0, 255);

else if (i < 256)

cell[i] = RGB(255, 255, 0);

else if (i < 384)

cell[i] = RGB(0, 255, 0);

else

 

cell[i] = RGB(255, 0, 0);

}

 

 

}

//==================================================================== void DrawBeam() {

x = X0; y = Y0;

double angle = scanAngle * digitWeight; double C = cos(Pi * (90 - angle) / 180); double S = sin(Pi * (90 - angle) / 180);

for (int k

= 0;

k < 512; ++k) {

x += C;

y

-= S;

SetPixel(g_hDC, (int)x, (int)y, cell[k]);

}

}

//////////////////////////////////////////////////////////////////////

Следует обратить внимание на некоторые детали реализации модели. Имитатор приемника информации от метеорадиолокатора должен опрашивать

цифровой канал связи с периодом PACK_PERIOD, равным 5 мс. Эта подзадача реа лизована с помощью мультимедийного таймера, который, как было показано в главе 10, можно успешно использовать в миллисекундном диапазоне. Таймер запускается по команде меню Старт и может быть остановлен в любой момент ко мандой Стоп.

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

Алгоритм функции TimerFunc не требует особых пояснений благодаря проду манной функциональной декомпозиции и выразительным именам вызываемых функций.

А вот функция GetPacket нуждается в таких пояснениях. Если бы предметом нашего рассмотрения был реальный имитатор приемника информации от метеора диолокатора, то «реальная» функция GetPacket должна была бы получать очередной пакет из канала связи и обеспечивать его распаковку. В результате такой распаков ки эта функция извлекала бы значение кода угла сканирования и формировала бы массив cell. Но так как рассматриваемая программа является всего лишь моделью

Рисование в реальном времени

555

 

 

реального имитатора, то «модельная» реализация функции GetPacketпросто вызыва ет функцию GetNextAngle.

Функция GetNextAngle обеспечивает формирование следующего значения кода угла сканирования. При каждом вызове этой функции знак приращения кода угла scanAngle зависит от направления сканирования. При движении по часовой стрелке (FORTH), код угла увеличивается на единицу, при движении против часовой стрелки (BACK) — уменьшается на единицу. На границах сектора сканирования переменная direction изменяет свое значение. Помимо этого вызывается функция

SetColorBeamForth или SetColorBeamBack, чтобы заполнить массив cell цветовыми кодами для точек дальности локационного луча. В результате при движении впе ред рисуются кольца красного, зеленого, желтого и синего цветов (перечисление идет от центра к периферии). При движении назад рисуются кольца синего, жел того, зеленого и красного цветов.

Вреальном имитаторе вызовы функций SetColorBeamForth и SetColorBeamBack от сутствуют, поскольку массив cell формируется функцией GetPacket.

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

ляется с помощью объекта timer класса KTimer, который рассматривался

вглаве 10. Измеренный интервал выводится в четвертое поле строки состояния приложения.

Рисование луча развертки реализовано в теле функции DrawBeam. Точки дально сти локационного луча из массива cell выводятся на экран с помощью функции SetPixel. Для рисования используется контекст устройства с глобальным дескрип тором g_hDC. Приложение получает этот контекст один раз при своем старте

вблоке обработки сообщения WM_CREATE и освобождает его при завершении рабо ты в блоке обработки сообщения WM_DESTROY.

После компиляции проекта перейдем к его тестированию.

На этапе тестирования ресурсоемких приложений очень важно наблюдать за степенью загрузки центрального процессора (ЦП). Операционная система содер жит удобный инструмент — системное приложение Диспетчер задач Windows (Task Manager), позволяющее вести такое наблюдение.

Для вызова диспетчера задач используйте комбинацию клавиш Ctrl+Shift+Esc. В строке состояния окна диспетчера задач отображается текущая загрузка цент рального процессора в процентах. Это суммарная загрузка ЦП от всех запущенных процессов. Если переключиться на вкладку Процессы, то можно наблюдать за про центом загрузки ЦП от каждого отдельного процесса.

Теперь запустите приложение ArincReceiverBad. По умолчанию в приложении установлена скорость сканирования 90 °/с. Выполните команду меню Старт. Ими татор начнет воспроизводить «радиолокационную картинку» примерно так, как показано на рис. 12.2.

Здесь приводятся результаты тестирования программы на компьютере с про цессором Intel Celeron CPU 2,0 ГГц и операционной системой Microsoft Windows 2000.

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

556

Глава 12. Специальные приложения

 

 

Рис. 12.2. Окно приложения ArincReceiverBad

Очевидно, что для скорости сканирования 90 °/с наш имитатор не справляется с рисованием в реальном времени. Об этом же свидетельствует и превышение фак тического времени просмотра сектора (2,52 с) над расчетным временем просмотра сектора (2,28 с).

При тестировании программы со скоростью сканирования 60 °/с загрузка ЦП падает до 60 %, и приложением уже можно пользоваться. Но не будем забывать, что в рассматриваемой модели имитатора многие подзадачи не решаются,

ав настоящем имитаторе нагрузка на процессор возрастет.

Вчем же причина низких эксплуатационных качеств, которые продемонстри ровала первая версия нашей программы? — В использовании функции SetPixel!

Вглаве 2 мы уже говорили, что реализация функции SetPixel такова, что процесс вывода пиксела занимает более 1000 тактов работы процессора. Поэтому для быст рого рисования нужны другие технологии.

Одним из возможных решений данной проблемы является применение DIB секций для рисования. Использование этой технологии мы покажем во второй версии программы.

Вторая версия модели программного имитатора

Основы применения DIB секций были изложены в главе 3, но без демонстрации на программных примерах. В данном разделе этот пробел будет восполнен.

Напомним, что DIB секцией называется DIB растр, который обеспечивает непосредственные чтение и запись как со стороны приложения, так и со стороны GDI. Массив пикселов DIB секции хранится обычно в виртуальной памяти прило жения.

Рисование в реальном времени

557

 

 

Для создания DIB секции используется функция CreateDIBSection, например:

HBITMAP hBmp = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &pBits, NULL, 0);

Функции передается адрес информационного блока bmi, содержащего характе ристики создаваемого растра. После выполнения функции в аргумент pBitsзаписы вается указатель на массив пикселов DIB секции. Быстрое рисование на поверхно сти DIB секции основано на прямом доступе к пикселам в массиве pBits. Чтобы вычислить адрес для доступа к байтам, представляющим пиксел с координатами (x, y), нужно знать количество байт, занимаемых каждой строкой изображения. Эта величина вычисляется с помощью следующего выражения:

bytePerLine = ((width * bmi.bmiHeader.biBitCount + 31) / 32) * 4;

Ниже будет рассмотрен класс KDibSection, в котором инкапсулированы опера ции рисования на DIB секции. Определение и использование класса KDibSection демонстрируется во второй версии программного имитатора.

Создайте новый проект с именем ArincReceiver. Скопируйте из папки проекта ArincReceiverBad (см. листинг 12.3) в папку проекта ArincReceiver файлы с расшире ниями .h, .cpp è .rc, скорректировав их имена заменой подстроки ArincReceiverBad на ArincReceiver. Добавьте скопированные файлы в состав проекта. Добавьте к на стройкам проекта на вкладке Link имена библиотек comctl32.lib и winmm.lib.

Добавьте к проекту файлы KDibSection.h и KDibSection.cpp с текстом, содержа щимся в листинге 12.4. Отредактируйте файл ArincReceiver.cpp, приведя его текст в соответствие с листингом 12.4.

Листинг 12.4. Проект ArincReceiver

//////////////////////////////////////////////////////////////////////

// KDibSection.h #include <windows.h>

class KDibSection { public:

KDibSection() : pBits(0) {}

BOOL Create(HDC hdc, int _width, int _height);

inline void SetPixel(int x, int y, COLORREF color) { int ind = 3 * x;

((BYTE*)pBits)[y*bytePerLine + ind] = GetBValue(color); ((BYTE*)pBits)[y*bytePerLine + ind + 1] = GetGValue(color); ((BYTE*)pBits)[y*bytePerLine + ind + 2] = GetRValue(color);

}

inline void Draw(HDC hdc) { StretchDIBits(hdc, 0, 0, width, height,

0, 0, width, height, pBits, &bmi, DIB_RGB_COLORS, SRCCOPY);

}

private:

HBITMAP hBmp; BITMAPINFO bmi; int width;

int height;

int bytePerLine; PVOID pBits;

} ;

558

Глава 12. Специальные приложения

 

 

//////////////////////////////////////////////////////////////////////

// KDibSection.cpp #include "KDibSection.h"

BOOL KDibSection::Create(HDC hdc, int _width, int _height) { width = _width;

height = _height;

bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = width; bmi.bmiHeader.biHeight = -height; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 24; bmi.bmiHeader.biCompression = BI_RGB;

bytePerLine = ((width * bmi.bmiHeader.biBitCount + 31) / 32) * 4;

hBmp = CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &pBits, NULL, 0); return (hBmp != NULL);

}

//////////////////////////////////////////////////////////////////////

// ArincReceiver.cpp #include <windows.h> #include <commctrl.h> #include <Mmsystem.h> #include <stdio.h> #include <math.h>

#include "resource.h" #include "KWndEx.h" #include "KTimer.h" #include "KDibSection.h"

/* Определения констант и переменных – такие же, как в листинге 12.3 */ KDibSection dibSect;

/* Объявления прототипов функций – такие же, как в листинге 12.3 */

//==================================================================== int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPSTR lpCmdLine, int nCmdShow)

{

MSG msg;

KWndEx mainWnd("ArincReceiver", hInstance, nCmdShow, WndProc, MAKEINTRESOURCE(IDR_MENU1), 100, 100, 900, 600);

while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg);

}

return msg.wParam;

}

//==================================================================== LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

{

static HMENU hMenu; // дескриптор главного меню

 

 

RECT rect, rcSB;

продолжение

 

 

Рисование в реальном времени

559

 

 

Листинг 12.4 (продолжение)

 

int aWidths[4];

 

static MMRESULT mmTimer;

 

switch (uMsg)

 

{

 

case WM_CREATE:

 

/* Здесь такой же текст, как в листинге 12.3

*/

// Создание DIB-секции

 

dibSect.Create(g_hDC, rect.right, Y0);

 

break;

 

case WM_SIZE:

SendMessage (hwndStatusBar, WM_SIZE, wParam, lParam); break;

case WM_COMMAND:

/* Здесь такой же текст, как в листинге 12.3 */

break;

case WM_DESTROY: ReleaseDC(hWnd, g_hDC); PostQuitMessage(0); break;

default:

return DefWindowProc(hWnd, uMsg, wParam, lParam);

}

return 0;

}

//==================================================================== void UpdateStatusBar()

{

/* Здесь такой же текст, как в листинге 12.3

*/

}

//==================================================================== void CALLBACK TimerFunc(UINT wTimerID, UINT msg, DWORD dwUser, DWORD dw1,

DWORD dw2)

{

// Дескриптор окна-владельца

HWND hwndOwner = reinterpret_cast<HWND>(dwUser);

//(в модели имитатора не используется, но может понадобиться

//в реальном имитаторе для отправки сообщений окну-владельцу)

POINT ppt[4];

// массив точек для создания региона отсечения

 

 

 

 

 

// Определяем точки ppt[0] и ppt[3]

 

 

 

 

 

 

 

ppt[0].y =

ppt[3].y =

Y0;

 

 

 

 

 

 

 

if

(direction

==

FORTH)

{ ppt[0].x = X0 -

1;

ppt[3].x

=

X0

+

1;

}

if

(direction

==

BACK)

{ ppt[0].x = X0 +

1;

ppt[3].x

=

X0

-

1;

}

GetPacket(); // получение пакета из канала связи

 

 

 

 

 

DrawBeam();

 

//

вывод

луча из полученного

пакета

 

 

 

 

 

560

 

 

 

 

 

 

 

 

 

Глава 12. Специальные приложения

 

 

 

 

 

 

 

 

//

Определяем

точку

ppt[1]

 

 

 

 

ppt[1].y = y;

 

 

 

 

 

 

 

 

 

if

(direction == FORTH) ppt[1].x = x - 1;

if

(direction

 

==

BACK)

ppt[1].x

=

x

+

1;

//

Размножение

лучей

 

 

 

 

 

 

for (int i =

1;

i <

sieveCoeff;

++i)

{

 

 

GetNextAngle();

 

 

 

 

 

 

 

DrawBeam();

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

//

Определяем

точку

ppt[2]

 

 

 

 

ppt[2].y = y;

 

 

 

 

 

 

 

 

 

if

(direction == FORTH) ppt[2].x = x + 1;;

if

(direction

 

==

BACK)

ppt[2].x

=

x

-

1;

//

Создание

региона

отсечения

 

 

 

 

HRGN hRgn = CreatePolygonRgn(ppt, 4, WINDING);

SelectClipRgn(g_hDC,

 

hRgn);

 

 

 

 

// Копирование изображения на экран (с учетом региона отсечения) dibSect.Draw(g_hDC);

DeleteObject(hRgn);

}

//==================================================================== void GetPacket() {

GetNextAngle();

}

//====================================================================

void GetNextAngle()

{

 

/* Здесь такой

же текст, как в листинге 12.3

*/

}

//====================================================================

void

SetColorBeamForth() {

 

/*

Здесь такой же текст, как в листинге 12.3

*/

}

//====================================================================

void

SetColorBeamBack()

{

 

/*

Здесь такой же

текст, как в листинге 12.3

*/

}

 

 

 

//====================================================================

void

DrawBeam()

{

 

 

 

 

 

x

=

X0;

 

 

 

 

 

 

 

 

y

=

Y0;

 

 

 

 

 

 

 

 

double

angle,

C,

 

S;

 

 

 

angle

=

scanAngle

*

digitWeight;

C

=

cos(Pi

*

(90 - angle) / 180);

S

=

sin(Pi

*

(90

 

-

angle)

/

180);

for

(int

k

=

0;

k

<

512;

++k) {

 

 

x

+=

C;

 

y

-= S;

 

 

 

 

// Вывод точки на поверхность DIB-секции

 

 

 

dibSect.SetPixel((int)x,

(int)y, cell[k]);

}

 

 

 

 

 

 

 

 

 

 

 

}

//////////////////////////////////////////////////////////////////////