Задачи и порядок выполнения работы Работа состоит из двух частей. В первой части необходимо приложение, разработанное в лабораторной работе № 5 преобразовать следующим образом: для обеспечения движения геометрических фигур в окне вместо таймера использовать потоки. Для каждой геометрической фигуры использовать свой поток. Потоковая функция должна быть одна, в качестве параметра в потоковую функцию передается указатель на объект «Фигура». При необходимости использовать синхронизацию. Здесь используется приложение Windows с графическим интерфейсом пользователя (Проект Win32 или Win32 Project).
Во второй части необходимо обеспечить синхронизацию двух приложений.
Первое приложение. Приложение с потоками преобразовать так, чтобы движение фигур в потоках начиналось не сразу, а после получения сигнала от второго приложения. При получения сигнала потоки начинают работать до тех пор, пока от второго приложения не придет другой сигнал, при получении этого сигнала потоки завершают свою работу.
Второе приложение – консольное приложение Windows (запускается только при запущенном первом приложении). После нажатия клавиши посылается сигнал для начала работы потоков в первом приложении. После следующего нажатия клавиши посылается сигнал на завершение работы потоков в первом приложении.
Продемонстрировать совместную работу двух приложений.
Внешний вид главного окна первого приложения аналогичен окну, представленному на рисунке 3 (лабораторная работа 5).
Условие задачи:
Создать абстрактный класс – «геометрическая фигура» (на экране). Класс содержит следующие поля: координаты геометрического центра фигуры на экране; поле, задающее размер фигуры (например, расстояние от центра о вершины или радиус окружности в пикселях); поле, задающее угловое положение фигуры; поле, задающее угловую скорость вращения фигуры; поле, определяющее направление движения (возможно два варианта: движение по вертикали и движение по горизонтали); поле, определяющее скорость движения; поле, определяющее цвет фигуры; поле, содержащее хэндл окна для рисования; при необходимости можно включить другие поля. Класс включает: конструктор для инициализации полей, функцию, изменяющую угловое положение фигуры и положение на экране во время движения за один такт времени, и чистую виртуальную функцию (или функции) для рисования и стирания фигуры на экране.
На основе абстрактного класса «фигура» разработать программу, содержащую описание трех графических классов: правильный многоугольник, отрезок (линия), половина окружности. Создать несколько объектов каждого из трех классов (не менее трех) для этого использовать один массив указателей типа базового класса «фигура». Реализуя механизм полиморфизма, привести объекты классов в одновременное вращение вокруг их геометрических центров с различными угловыми скоростями и в движение с отскоком от краев окна в заданном режиме (по горизонтали или по вертикали). При этом для анимации каждой фигуры использовать свой поток.
Пример выполнения работы
В листинге, представленном ниже, приведен только код файла MyGrahics.cpp. В среде Microsoft Visual Studio 2013 было создано стандартное оконное приложение (проект типа Проект Win32 или Win32 Project) с именем MyGrahics (имя может быть другим), все остальные файлы: stdafx.h, MyGrahics.h и другие – стандартные, сгенерированы автоматически при создании проекта. В файл stdafx.h необходимо в конце добавить 2 строчки:
#define _USE_MATH_DEFINES
#include <math.h>
Листинг программы с комментариями (часть 1):
// MyGraphics.cpp: определяет точку входа для приложения.
//
#include "stdafx.h"
#include "MyGraphics.h"
#define MAX_LOADSTRING 100
// Глобальные переменные:
HINSTANCE hInst; // текущий экземпляр
TCHAR szTitle[MAX_LOADSTRING]; // Текст строки заголовка
TCHAR szWindowClass[MAX_LOADSTRING]; // имя класса главного окна
// Отправить объявления функций, включенных в этот модуль кода:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: разместите код здесь.
MSG msg;
HACCEL hAccelTable;
// Инициализация глобальных строк
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_MYGRAPHICS, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// Выполнить инициализацию приложения:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_MYGRAPHICS));
// Цикл основного сообщения:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
//
// ФУНКЦИЯ: MyRegisterClass()
//
// НАЗНАЧЕНИЕ: регистрирует класс окна.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MYGRAPHICS));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCE(IDC_MYGRAPHICS);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassEx(&wcex);
}
//
// ФУНКЦИЯ: InitInstance(HINSTANCE, int)
//
// НАЗНАЧЕНИЕ: сохраняет обработку экземпляра и создает главное окно.
//
// КОММЕНТАРИИ:
//
// В данной функции дескриптор экземпляра сохраняется в глобальной переменной, а также
// создается и выводится на экран главное окно программы.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // Сохранить дескриптор экземпляра в глобальной переменной
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;
}
// Абстрактный класс Фигура
class Figure
{
protected: // Поля наследуются должен быть доступ в производном классе
int x, y; // Координаты геометр центра
int R; // Расстояние от центра до вершины
int Ang; // Угловое положение в градусах
int VAng; // Угловая скорость (градусов за интервал срабатывания таймера)
int V; // Скорость (пикселей за интервал таймера)
int Napr; // Направление движения 0 - вертикально 1- горизонтально
COLORREF col; // Цвет фигуры
HWND hWnd; // Хэндел окна, где рисуется фигура
int N_Reg; // Направление перемещение
// (1 - вправо или вниз; -1 - влево или вверх)
HDC hdc;
public:
// Конструктор для инициализации всех параметров
Figure(int R, int VAng, int V, int Napr, COLORREF col, HWND hWnd);
// Метод меняет положение фигура за 1 такт таймера
virtual void step();
// Чистая виртуальная функция для рисования (стирания) фигуры
virtual void draw(int Reg) = 0;
// деструктор для освобождения контекста устройства
virtual ~Figure();
};
// Определение конструктора и функций за пределами класса
Figure::Figure(int R, int VAng, int V, int Napr, COLORREF col, HWND hWnd)
{
this->R = R;
this->VAng = VAng;
this->V = V;
this->Napr = Napr;
this->col = col;
this->hWnd = hWnd;
Ang = 0;
N_Reg = 1; // Координаты увеличиваются
RECT rect; // Размеры окна
GetClientRect(hWnd, &rect); // Получаем размеры окна
// Начальное положение фигуры в центре окна
x = rect.right / 2;
y = rect.bottom / 2;
// Получаем контекст устройства
hdc = GetDC(hWnd);
}
// Определение деструктора для освобождения контекста устройства
Figure::~Figure()
{
ReleaseDC(hWnd, hdc);
}
// Определение метода меняющего положение фигуры за 1 такт таймера
void Figure::step()
{
// Меняем угловое положение фигуры
Ang += VAng;
if (Ang >= 360) Ang -= 360;
// Получаем размеры окна
RECT rect;
GetClientRect(hWnd, &rect);
// Меняем положение центра фигуры
if (Napr == 1) // Движение горизонтально меняется x
{
x += V*N_Reg;
if (N_Reg == 1) // Движемся вправо
{
if (x + R >= rect.right) // Достигли правой границы окна
N_Reg = -1; // Меняем направление движения
}
else // Движемся влево
{
if (x - R <= 0) // Достигли левой границы
N_Reg = 1; // Меняем направление движения
}
}
else // Движение вертикально меняется y
{
y += V*N_Reg;
if (N_Reg == 1) // Движемся вниз
{
if (y + R >= rect.bottom) // Достигли нижней границы окна
N_Reg = -1; // Меняем направление движения
}
else // Движемся вверх
{
if (y - R <= 0) // Достигли верхней границы
N_Reg = 1; // Меняем направление движения
}
}
}
// Класс многоугольник
class MyPolygon : public Figure
{
protected:
int N; // Число вершин
POINT *p; // Массив координат вершин
public:
// Заголовок конструктора
MyPolygon(int R, int VAng, int V, int Napr, COLORREF col, HWND hWnd, int N);
void step(); // Метод дополнительно считает новые координаты вершин
void draw(int Reg); // Метод рисования фигуры
};
// Определение конструктора
MyPolygon::MyPolygon(int R, int VAng, int V, int Napr, COLORREF col, HWND hWnd, int N) :
// Вызов конструктора базового класса
Figure(R, VAng, V, Napr, col, hWnd)
{
this->N = N;
p = new POINT[N]; // Создаем массив координат вершин
// Расчет координат вершин
double A = Ang*M_PI / 180; // Угол в градусах переводим в радианы
double A1 = 2 * M_PI / N; // Угол между направлениями на соседние вершины
// из центра фигуры
for (int i = 0; i<N; i++, A += A1)
{
p[i].x = x + R*cos(A);
p[i].y = y - R*sin(A);
}
}
void MyPolygon::step() // Метод дополнительно считает новые координаты вершин
{
Figure::step(); // Вызов метода базового класса
// Расчет координат вершин многоугольника
double A = Ang*M_PI / 180; // Угол в градусах переводим в радианы
double A1 = 2 * M_PI / N; // Угол между направлениями на соседние вершины из
// центра фигуры
for (int i = 0; i<N; i++, A += A1)
{
p[i].x = x + R*cos(A);
p[i].y = y - R*sin(A);
}
}
void MyPolygon::draw(int Reg) // Метод рисования фигуры
{
HPEN pen;
if (Reg == 1) // Режим рисования фигуры
pen = CreatePen(PS_SOLID, 1, col);
else // Режим стирания (белое перо)
pen = CreatePen(PS_SOLID, 1, RGB(255, 255, 255));
SelectObject(hdc, pen); // Загрузка пера в контекст устройства
MoveToEx(hdc, p[0].x, p[0].y, 0); // Графический курсор в первую вершину
for (int i = 1; i<N; i++)
LineTo(hdc, p[i].x, p[i].y);
LineTo(hdc, p[0].x, p[0].y); // Последнею вершину соединяем с первой
DeleteObject(pen); // Удаляем перо
}
// Класс отрезок
class MyOtrezok : public Figure
{
protected:
int x1, y1, x2, y2; // Координаты концов отрезка
public:
// Заголовок конструктора
MyOtrezok(int R, int VAng, int V, int Napr, COLORREF col, HWND hWnd);
void step(); // Метод дополнительно считает новые координаты концов отрезка
void draw(int Reg); // Метод рисования фигуры
};
// Определение конструктора
MyOtrezok::MyOtrezok(int R, int VAng, int V, int Napr, COLORREF col, HWND hWnd) :
// Вызов конструктора базового класса
Figure(R, VAng, V, Napr, col, hWnd)
{
// Расчет координат вершин
double A = Ang*M_PI / 180; // Угол в градусах переводим в радианы
x1 = x + R*cos(A);
y1 = y - R*sin(A);
x2 = x - R*cos(A);
y2 = y + R*sin(A);
}
void MyOtrezok::step() // Метод дополнительно считает новые координаты вершин
{
Figure::step(); // Вызов метода базового класса
// Расчет координат вершин многоугольника
double A = Ang*M_PI / 180; // Угол в градусах переводим в радианы
x1 = x + R*cos(A);
y1 = y - R*sin(A);
x2 = x - R*cos(A);
y2 = y + R*sin(A);
}
void MyOtrezok::draw(int Reg) // Метод рисования фигуры
{
HPEN pen;
if (Reg == 1) // Режим рисования фигуры
pen = CreatePen(PS_SOLID, 1, col);
else // Режим стирания (белое перо)
pen = CreatePen(PS_SOLID, 1, RGB(255, 255, 255));
SelectObject(hdc, pen); // Загрузка пера в контекст устройства
MoveToEx(hdc, x1, y1, 0); // Графический курсор в первую вершину
LineTo(hdc, x2, y2); // Последнею вершину соединяем с первой
DeleteObject(pen); // Удаляем перо
}
// Класс половина круга
class MyPoluKrug : public Figure
{
public:
// Заголовок конструктора
MyPoluKrug(int R, int VAng, int V, int Napr, COLORREF col, HWND hWnd);
void draw(int Reg); // Метод рисования фигуры
};
// Определение конструктора
MyPoluKrug::MyPoluKrug(int R, int VAng, int V, int Napr, COLORREF col, HWND hWnd) :
// Вызов конструктора базового класса
Figure(R, VAng, V, Napr, col, hWnd)
{
}
void MyPoluKrug::draw(int Reg) // Метод рисования фигуры
{
HPEN pen;
if (Reg == 1) // Режим рисования фигуры
pen = CreatePen(PS_SOLID, 1, col);
else // Режим стирания (белое перо)
pen = CreatePen(PS_SOLID, 1, RGB(255, 255, 255));
SelectObject(hdc, pen); // Загрузка пера в контекст устройства
MoveToEx(hdc, x, y, 0); // Курсор помещаем в центр круга
AngleArc(hdc, x, y, R, Ang, 180); // Рисуем полуокружность
LineTo(hdc, x, y); // Соединяем полуокружность с центром
DeleteObject(pen); // Удаляем перо
}
// Структура, в которой передаются данные для потоковой функции
struct ForThread
{
Figure *pFig; // Указатель на объект- фигура
HANDLE hEvent; // Хэндел события для синхронизации потоков
};
HANDLE hEvents[9]; // Хэнделы событий для синхронизации потоков
Figure *pF[9]; // Будет создано 9 объектов
ForThread Data[9]; // Данные для потоковых функций для каждого из 9 потоков
int flag = 1; // Условие работы цикла в потоках
// Потоковая функция, параметр - указатель на структуру данных ForThread
DWORD WINAPI ThreadFun(LPVOID par)
{
ForThread *pData = (ForThread *)par; // Данные, которые переданы
// в потоковую функцию
while (flag)
{
// Рисуем фигуру
pData->pFig->draw(1); // Рисуем фигуру
Sleep(20); // Задержка
pData->pFig->draw(0); // Стираем старую фигуру
pData->pFig->step(); // Меняем положение фигуры
}
SetEvent(pData->hEvent); // Сигнал событие о завершении потока
return 1;
}
//
// ФУНКЦИЯ: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// НАЗНАЧЕНИЕ: обрабатывает сообщения в главном окне.
//
// WM_COMMAND - обработка меню приложения
// WM_PAINT -Закрасить главное окно
// WM_DESTROY - ввести сообщение о выходе и вернуться.
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_CREATE: // Создание окна
// Создаем объекты
pF[0] = new MyPolygon(100, 1, 10, 0, RGB(255, 0, 0), hWnd, 3);
pF[1] = new MyPolygon(150, 2, 5, 0, RGB(0, 255, 0), hWnd, 3);
pF[2] = new MyPolygon(70, 3, 3, 1, RGB(0, 0, 255), hWnd, 3);
pF[3] = new MyOtrezok(50, 4, 2, 1, RGB(255, 0, 255), hWnd);
pF[4] = new MyOtrezok(100, 2, 1, 1, RGB(0, 255, 255), hWnd);
pF[5] = new MyOtrezok(150, 1, 2, 0, RGB(0, 0, 255), hWnd);
pF[6] = new MyPoluKrug(50, 2, 2, 0, RGB(255, 0, 255), hWnd);
pF[7] = new MyPoluKrug(100, 1, 3, 0, RGB(0, 255, 255), hWnd);
pF[8] = new MyPoluKrug(150, 3, 4, 1, RGB(0, 0, 255), hWnd);
for (int i = 0; i<9; i++)
{
// Заполняем данные для потока
Data[i].pFig = pF[i]; // Указатель на объект- фигура
Data[i].hEvent = hEvents[i] = CreateEvent(0, // Создать объект –
// событие для синхронизации потоков
false, // TRUE событие со сбросом вручную
// FALSE — событие с автосбросом
false, // свободное (TRUE) занятое (FALSE).
0);
CreateThread(0, 0, ThreadFun, &Data[i], 0, 0); // Создание потоков
}
break;
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Разобрать выбор в меню:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
flag = 0; // Завершение всех потоков (выход из цикла и из потоковой
// функции)
// Ждем пока завершатся все потоки (наступят события)
WaitForMultipleObjects(9, hEvents, TRUE, INFINITE);
for (int i = 0; i<9; i++) {
delete pF[i]; // Удаляем все объекты (фигуры)
CloseHandle(hEvents[i]); // Закрываем события
}
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: добавьте любой код отрисовки...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
flag = 0; // Завершение всех потоков (выход из цикла и из потоковой
// функции)
// Ждем пока завершатся все потоки (наступят события)
WaitForMultipleObjects(9, hEvents, TRUE, INFINITE);
for (int i = 0; i<9; i++) {
delete pF[i]; // Удаляем все объекты (фигуры)
CloseHandle(hEvents[i]); // Закрываем события
}
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// Обработчик сообщений для окна "О программе".
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
Во второй части необходимо обеспечить синхронизацию двух приложений. Первое приложение будет создано на основе приложения, исходный код которого приведен выше, приведем только необходимые изменения исходного кода. Необходимо объявить глобально до потоковой функции и до функции окна хэндл объекта события для синхронизации приложений:
// Для синхронизации 2-х приложения используем объект- событие
HANDLE hEvent; // Объявляется глобально, и все потоки имеют доступ
В функции окна при обработке сообщения WM_CREATE создаем объект – событие:
case WM_CREATE: // Создание окна
hEvent=CreateEvent(0, // Создать объект - событие
true, // TRUE событие со сбросом вручную
false, // занятое (FALSE).
L"MyEvent1"); // Имя события для открытия в другом приложении
// Создаем объекты
pF[0]=new MyPolygon(100, 1, 10, 0, RGB(255, 0, 0), hWnd, 3);
pF[1]=new MyPolygon(150, 2, 5, 0, RGB(0, 255, 0), hWnd, 3);
pF[2]=new MyPolygon(70, 3, 3, 1, RGB(0, 0, 255), hWnd, 3);
………………………………
Потоковая функция примет вид:
// Потоковая функция, параметр - указатель на структуру данных ForThread
DWORD WINAPI ThreadFun(LPVOID par)
{
ForThread *pData=(ForThread *)par; // Данные, которые переданы
// в потоковую функцию
WaitForSingleObject(hEvent, // Ждать пока произойдет событие
INFINITE); // ждите сколько нужно, пока другое приложение не освободит
while(WaitForSingleObject(hEvent, 0)==WAIT_OBJECT_0) // Цикл работает пока
// объект событие свободен
{
// Рисуем фигуру
pData->pFig->draw(1); // Рисуем фигуру
Sleep(20); // Задержка
pData->pFig->draw(0); // Стираем старую фигуру
pData->pFig->step(); // Меняем положение фигуры
}
SetEvent(pData->hEvent); // Сигнал событие о завершении потока
return 1;
}
Внутри потоковой функции поток останавливается и ждет наступления события, которое будет установлено в другом приложении. Когда наступит событие, начинает работать цикл для анимации фигуры, условие продолжения цикла – пока объект – событие свободен. Когда объект событие будет занят в другом приложении, цикл в потоковой функции завершается и осуществляется выход из потоковой функции.
Исходный код второго приложения:
#include <Windows.h>
#include <conio.h>
#include <iostream>
#include <locale.h>
using namespace std;
int main()
{
HANDLE hEvent; // Объявляется глобально, и все потоки имеют доступ
setlocale(LC_ALL, "rus");
hEvent=OpenEvent( // Открываем объект- событие,
// созданное в другом приложении
EVENT_ALL_ACCESS, // флаги доступа
TRUE, // режим наследования
L"MyEvent1" // имя события
);
if (hEvent==0)
{
cout<<"Ошибка открытия объекта- события, созданного в другом приложении";
_getch();
return 0;
}
cout<<"Для запуска потоков в другом приложении нажмите любую клавишу"<<endl;
_getch();
SetEvent(hEvent); // Объект-событие свободен
cout<<"Для завершения потоков в другом приложении нажмите любую клавишу"<<endl;
_getch();
ResetEvent(hEvent); // Объект-событие занят
cout<<"Для выхода из приложения нажмите любую клавишу"<<endl;
_getch();
return 0;
}
|