Технология разработки программных систем
..pdf141
struct SWT {
HANDLE hevBeg; // событие начала
HANDLE hevEnd; // событие завершения
CRITICAL_SECTION cs; // дополнительные поля
};
Структура, помимо полей, определяемых решаемой задачей, содержит два дескриптора событий и критическую секцию.
В главном потоке объявляются две статические переменные swt и hThread, а также проводится инициализация критической секции и создание объектов событий для управления рабочим потоком. Оба события используют "ручное" изменение состояния и первоначально установлены в "сброшено".
static |
SWT |
swt; |
static |
HANDLE |
hThread; |
swt.hevBeg = CreateEvent(NULL, TRUE, FALSE, NULL); swt.hevEnd = CreateEvent(NULL, TRUE, FALSE, NULL); InitializeCriticalSection(&swt.cs);
Затем запускается рабочий поток с обслуживающей функцией ThreadFunc(), которой в качестве параметра передается указатель на структуру swt.
hThread = CreateThread(NULL,0,
ThreadFunc,&swt,0,&idThread);
После этого, возможно в другом обработчике функции главного окна приложения, устанавливается событие hevBeg, что является сигналом рабочему потоку к началу исполнения своего кода.
SetEvent(swt.hevBeg); // Событие установлено
Теперь рассмотрим функцию рабочего потока
DWORD WINAPI ThreadFunc (LPVOID pData)
{
//преобразуем указатель
SWT* pswt = (SWT*)pData;
//бесконечно ожидаем наступления события
/*1*/ WaitForSingleObject(pswt->hevBeg, INFINITE);
|
// продолжаем цикл пока не установлено |
/*2*/ |
// события завершения рабочего потока |
while (WaitForSingleObject(pswt->hevEnd, 0) |
|
|
!= WAIT_OBJECT_0) { |
|
// вводим критическую секцию и ждем когда |
|
// остальные потоки будут заблокированы |
|
EnterCriticalSection(&pswt->cs); |
142
//некие действия
//в "монопольном" режиме
//сбрасываем критическую секцию
LeaveCriticalSection(&pswt->cs);
}
//сбрасываем событие
ResetEvent(pswt->hEvEnd);
//выход из функции автоматически
//завершает рабочий поток
return 0;
}
В цикле, помеченном через /*1*/, функция рабочего потока будет находиться до тех пор, пока главный поток не установит событие hevBeg. Цикл, помеченный /*2*/, организован иным образом. Функция ожидания только проверяет состояние события hevEnd. Если оно не установлено, то выполняется тело цикла, в противном случае – цикл заканчивается, что ведет к выходу из функции рабочего потока и завершению самого потока.
Вернемся в главный поток. Для того, чтобы завершить рабочий поток устанавливается событие hevEnd, после чего процесс ожидает момента его сброса в рабочем потоке.
SetEvent(swt.hevEnd);
while (WaitForSingleObject(swt.hevEnd, 200) == WAIT_OBJECT_0);
// в данный момент рабочий поток завершен
Затем выполняется закрытие объектов
CloseHandle(hThread);
CloseHandle(swt.hevBeg);
CloseHandle(swt.hevEnd);
DeleteCriticalSection(&swt.cs);
Используете функцию ожидания Sleep(), только для рабочих потоков. При ее применении в главном потоке он блокируется и не обрабатывает поступающие сообщения.
143
13. Приложение "Тестер файлов"
В заключение рассмотрим исходный текст приложения, предназначенного для проверки целостности файлов на магнитном носителе, например, на дискете. Конечно, операционная система включает в свой состав служебную программу проверки диска. Однако она выполняет проверку для всей поверхности, что порой требует достаточно много времени. Данная утилита проверяет – путем чтения – только дисковые файлы, значит, в некоторых случаях, может быть более эффективной по затратам компьютерных ресурсов. С другой стороны, автор создавал данное приложение с учебными целями и не претендует, чтобы утилита считалась полностью системной.
Перечислим основные идеи и особенности приложения:
1.Для задания имени драйвера, на котором проводится тестирование файлов, используется командная строка приложения. Так как информация из командной строки потребуется в функции окна, определен указатель, объявленный как глобальная переменная.
2.Функция WinMain() обычна. Цикл обработки сообщений тоже не
содержит каких-либо особенностей.
3.В обработчике WM_CREATE функции главного окна создается Win32 элемент управления ListView, который позволяет отображать различную информацию в колонках каждой строки. Приложение использует четыре колонки: имя файла, статус проверки, длина файла и время его создания. Здесь же создаются две кнопки для запуска процесса проверки и его остановки.
4.При нажатии кнопки начала проверки или нажатии клавиши "Enter", главный процесс запускает рабочий поток, который выбирает все файлы на указанном разделе и выполняет их проверку обычным чтением. Если встречается директория, то выполняется рекурсивный вызов функции проверки для этой директории. Результаты проверки отображаются в виде новой строки элемента ListView.
5.При нажатии кнопки останова, процесс сбрасывает дескриптор окна
внулевое значение, что является сигналом для рабочего потока, к прекращению своей работы. Одновременно в главном потоке запускается таймер, при обработке которого приложение проверяет, не закончился ли рабочий поток. Аналогичная проверка с использованием таймера выполняется при закрытии приложения.
Проект включает три файла:
TestDisk.cpp |
исходный код приложения; |
Resource.h |
файл идентификаторов; |
TestDisk.rc |
файл ресурсов приложения. |
144
Создайте файл Resource.h и определите в нем следующие идентификаторы (числовые значения могут быть иными):
IDS_ERROR 1
IDS_APP_NAME 2
ID_BUTT_BEG 101
ID_BUTT_END 102
ID_LISTVIEW 103
Файл TestDisk.rc для этого проекта содержит только один ресурс – строковый:
STRINGTABLE DISCARDABLE BEGIN
IDS_ERROR "Ошибка"
IDS_APP_NAME "Тестер файлов на диске " ID_BUTT_BEG "Проверить"
ID_BUTT_END "Прервать"
END
Далее приводится полный текст файла TestDisk.cpp.
13.1. Функция WinMain()
#define STRICT
#define WIN32_LEAN_AND_MEAN #include <Windows.h>
// Заголовочный файл для Win32 элементов управления
#include <CommCtrl.h> #include "Resource.h"
// Используем два таймера
#define TIMER_CLOSE 4444 #define TIMER_WAIT 4445
//Структура для отображения информации
//о тестируемом файле в ListView struct NEW_FILE_DATA {
TCHAR szName [MAX_PATH]; TCHAR szLen [16]; TCHAR szDate [32]; TCHAR szCheck[16];
};
//Структура для параметров
//функции рабочего потока
struct THREADPARAM {
HWND |
hWnd; |
HWND |
hwndList; |
DWORD |
idThread; |
TCHAR |
szRoot[MAX_PATH]; |
}; |
|
145
////////////////////////////////////////////////////////
// Глабальные переменные
HINSTANCE g_hInst;
LPCSTR g_szCmd;
////////////////////////////////////////////////////////
//Прототипы функций
int |
InitApplication |
(LPCTSTR szClass); |
void |
ViewError |
(HWND hWnd); |
LPTSTR |
StrFromRC |
(int IDstr); |
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
//Приложение использует командную строку,
//через которую можно задать диск для проверки.
//Например, "TestDisk.exe D:\"
//В этом случае тестируются файлы на диске D:
//Если параметр не задан, используется A:\
//Сохраняем глобальные переменные
g_hInst = hInstance; g_szCmd = lpCmdLine; if (!*g_szCmd)
g_szCmd = TEXT("a:\\");
//Подключаем библиотеку Win32 стандартных элементов,
//но испотльзовать будем только ListView
INITCOMMONCONTROLSEX commItems; commItems.dwSize = sizeof(commItems); commItems.dwICC = ICC_LISTVIEW_CLASSES; InitCommonControlsEx(&commItems);
// Регистрируем класса главного окна
LPCTSTR szClass = TEXT("FileTester32"); if ( !InitApplication(szClass) ) {
// Возникла ошибка
ViewError(NULL); return -1;
}
// Создание главного окна int proc = 20;//%
int xW = ::GetSystemMetrics(SM_CXSCREEN); int yW = ::GetSystemMetrics(SM_CYSCREEN); int x = (proc*xW)/100;
int y = (proc*yW)/100;
//Шаблон имени окна хранится в ресурсах.
//Добавляем имя тестируемого диска. TCHAR szTitle[128];
lstrcpy(szTitle, StrFromRC(IDS_APP_NAME));
146
lstrcat(szTitle, g_szCmd);
HWND hWnd = ::CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, szClass, szTitle,
WS_OVERLAPPEDWINDOW, x, y, ((100-2*proc)*xW)/100, ((100-2*proc)*yW)/100,
NULL, NULL, g_hInst, NULL); if (!hWnd) {
// Возникла ошибка
ViewError(NULL); return -1;
}
::ShowWindow(hWnd, nCmdShow); ::UpdateWindow(hWnd);
// Цикл обработки сообщений
MSG msg;
while (::GetMessage(&msg, NULL, 0, 0)) { ::TranslateMessage(&msg); ::DispatchMessage(&msg);
}
return 0;
}
13.2.Функция главного окна
//Прототипы функций, используемые в WndProc() HWND CreateListView (HWND hwndParent);
void InitListView (HWND hwndList);
void ResizeListView (HWND hwndList, HWND hwndParent);
void |
SwitchButton |
(HWND hWnd, TCHAR chMode); |
DWORD |
WINAPI ThreadFunc (LPVOID pData); |
|
LRESULT CALLBACK WndProc(HWND hWnd,
UINT message, WPARAM wParam, LPARAM lParam)
{
//Используем две статические переменные
//для дескриптора рабочего потока
//и структуры параметров для него.
static |
HANDLE |
hThread; |
static |
THREADPARAM |
parm; |
switch (message) {
case WM_CREATE :
// Создаем элемент управления ListView. parm.hwndList = CreateListView(hWnd); if (parm.hwndList) {
//На одном месте создаем две кнопки
//с разными идентификаторами.
147
//Первая будет запускать процесс проверки,
//а вторая - останавливать.
//Пряча одну и показывая другую,
//организуем эффект переключения.
HWND hButt = ::CreateWindow(TEXT("BUTTON"),
StrFromRC(ID_BUTT_BEG),
WS_CHILD | WS_TABSTOP | WS_VISIBLE, 10, 6, 80, 24,
hWnd, (HMENU)ID_BUTT_BEG, g_hInst, NULL);
if (hButt) {
//Заменяем шрифт для кнопки на тот,
//который используется для ListView. WPARAM pFont = ::SendMessage(parm.hwndList,
WM_GETFONT, 0, 0); ::SendMessage(hButt, WM_SETFONT, pFont,
MAKELPARAM(TRUE, 0));
}
hButt = ::CreateWindow(TEXT("BUTTON"), StrFromRC(ID_BUTT_END), WS_CHILD | WS_TABSTOP,
10, 6, 80, 24,
hWnd, (HMENU)ID_BUTT_END, g_hInst, NULL);
if (hButt) {
WPARAM pFont = ::SendMessage(parm.hwndList, WM_GETFONT, 0, 0);
SendMessage(hButt, WM_SETFONT, pFont, MAKELPARAM(TRUE, 0));
}
}
break;
case WM_SIZE :
// Изменяем размеры ListView. ResizeListView(parm.hwndList, hWnd); return 0;
case WM_SETFOCUS :
// Передаем фокус на элемент ListView. ::SetFocus(parm.hwndList);
break;
case WM_COMMAND : ::SetFocus(parm.hwndList);
if (LOWORD(wParam) == ID_BUTT_BEG) { // Запускаем рабочий поток. InitListView(parm.hwndList); lstrcpy(parm.szRoot, g_szCmd); parm.hWnd = hWnd;
if (hThread) ::CloseHandle(hThread);
148
hThread = ::CreateThread(NULL, 0, ThreadFunc, &parm, 0, &parm.idThread);
SwitchButton(hWnd, TEXT('E'));
}
else if (LOWORD(wParam) == ID_BUTT_END) {
//Останавливаем рабочий поток
//и запускаем таймер ожидания. parm.hWnd = 0;
::SetTimer(hWnd, TIMER_WAIT, 100, NULL); SwitchButton(hWnd, TEXT('B'));
}
return 0;
case WM_NOTIFY : {
NMHDR& nm = *(NMHDR*)lParam;
if (nm.hwndFrom == parm.hwndList && nm.code == LVN_KEYDOWN) {
// Определяем какая клавиша нажата. LV_KEYDOWN& km = *(LV_KEYDOWN*)lParam; if (km.wVKey == VK_RETURN) {
//Нажата клавиша "Enter".
//Имитируем нажатие соответствующей кнопки. ::PostMessage(hWnd, WM_COMMAND,
MAKEWPARAM(((parm.idThread) ? ID_BUTT_END : ID_BUTT_BEG), BN_CLICKED), 0);
}
else if (km.wVKey == VK_ESCAPE)
//Нажата клавиша "Esc".
//Имитируем нажатие соответствующей кнопки. ::PostMessage(hWnd, WM_CLOSE, 0, 0);
return 0;
}
break; }
case WM_TIMER : ::KillTimer(hWnd, wParam); if (parm.idThread) {
// Продолжаем ждать окончания рабочего потока. ::SetTimer(hWnd, wParam, 100, NULL);
}
else {
// Поток завершен. ::CloseHandle(hThread); hThread = NULL;
if (wParam == TIMER_CLOSE) ::PostMessage(hWnd, WM_CLOSE, 0, 0);
}
return 0;
case WM_CLOSE :
if (parm.idThread) {
// Рабочий поток еще активен.
149
// Запускаем таймер ожидания окончания. parm.hWnd = 0;
::SetTimer(hWnd, TIMER_CLOSE, 100, NULL); return 0;
}
break;
case WM_DESTROY : if (hThread)
::CloseHandle(hThread);
::PostQuitMessage(0); return 0;
}
return ::DefWindowProc(hWnd, message, wParam, lParam);
}
13.3. Вспомогательные функции
int InitApplication (LPCTSTR szClass) // Регистрация класса главного окна
{
WNDCLASS wc; ::ZeroMemory(&wc, sizeof(wc));
wc.style |
= 0; |
|
wc.lpfnWndProc |
= WndProc; |
|
wc.hInstance |
= g_hInst; |
|
wc.hIcon |
= |
::LoadIcon(NULL, IDI_APPLICATION); |
wc.hCursor |
= |
::LoadCursor(NULL, IDC_ARROW); |
wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1); wc.lpszClassName = szClass;
return ::RegisterClass(&wc);
}
LPTSTR StrFromRC (int IDstr)
// Загружает строку из ресурсов приложения.
{
static TCHAR szBuff[256]; ::LoadString(g_hInst, (UINT)IDstr, szBuff,
sizeof(szBuff));
return szBuff;
}
void ViewError (HWND hWnd)
// Отображает сообщение о любой системной ошибке.
{
TCHAR szErrs[16*1024]; ::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
NULL, ::GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), szErrs, sizeof(szErrs), NULL);
150
::MessageBox((hWnd) ? hWnd : GetFocus(), szErrs, StrFromRC(IDS_ERROR), MB_OK | MB_ICONSTOP);
}
void SwitchButton (HWND hWnd, TCHAR chMode)
//"Переключает" кнопки,
//скрывая одну и показывая другую.
{
HWND hButBeg = ::GetDlgItem(hWnd, ID_BUTT_BEG); HWND hButEnd = ::GetDlgItem(hWnd, ID_BUTT_END); if (!hButBeg || !hButEnd) return;
if (chMode == TEXT('B')) { ::ShowWindow(hButBeg, SW_SHOW); ::ShowWindow(hButEnd, SW_HIDE);
}
else {
::ShowWindow(hButBeg, SW_HIDE); ::ShowWindow(hButEnd, SW_SHOW);
}
}
HWND CreateListView (HWND hwndParent) // Создает элемент ListView
{
DWORD dwStyle = WS_CHILD | WS_BORDER | WS_TABSTOP | WS_VISIBLE | LVS_REPORT | LVS_NOSORTHEADER;
HWND hwndList = CreateWindowEx(WS_EX_CLIENTEDGE, WC_LISTVIEW, // class name
TEXT(""), dwStyle, 0, 0, 0, 0, hwndParent,
(HMENU)ID_LISTVIEW, g_hInst, NULL); if(!hwndList)
return NULL;
ResizeListView(hwndList, hwndParent);
// Создаем 4 колонки. LV_COLUMN lvColumn;
int |
col |
= |
0; |
lvColumn.mask |
= |
LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | |
|
LVCF_SUBITEM;
lvColumn.fmt = LVCFMT_LEFT; lvColumn.cx = 240; lvColumn.pszText = TEXT("Файл");
ListView_InsertColumn(hwndList, col++, &lvColumn);
lvColumn.fmt = LVCFMT_LEFT; lvColumn.cx = 60; lvColumn.pszText = TEXT("Статус");
ListView_InsertColumn(hwndList, col++, &lvColumn);
