<Программирование для Windows NT© Александр Фролов, Григорий ФроловТом 26, часть 1, М.: Диалог-МИФИ, 1996, 272 стр. Рецензия PC WEEK Приложение MultiSDIНаше первое мультизадачное приложение называется MultiSDI. В его главном окне (рис. 2.3) рисуют четыре задачи, одна из которых является главной и соответствует функции WinMain. Рис. 2.3. Главное окно приложения MultiSDI Немного позже мы приведем исходные тексты MDI-приложения MultiMDI, в котором для каждого дочернего MDI-окна создается отдельная задача. Главная задача приложения MultiSDI рисует в главном окне приложения во время обработки сообщения WM_PAINT. Кроме того, она создает еще три задачи, которые рисуют в окне приложения, соответственно, эллипсы, прямоугольники и текстовую строку TEXT. Цвет и размеры фигур, а также цвет текстовой строки и цвет фона, на котором отображается эта строка, выбираются случайным образом, поэтому содержимое главного окна изменяется хаотически. Это выглядит достоточно забавно. Так как все задачи выполняют рисование в одном окне, нам пришлось использовать простейшее средство синхронизации задач - критическую секцию. Проблемы синхронизации задач, работающих параллельно, мы рассмотрим в отдельной главе нашей книги. Исходные тексты приложенияГлавный файл исходных текстов приложения MultiSDI представлен в листинге 2.1. Заметим, что для сборки проекта мультизадачного приложения необходимо использовать мультизадачный вариант библиотеки времени выполнения. Об этом мы уже говорили в разделе “Функция _beginthread XE "_beginthread" ”. Листинг 2.1. Файл multisdi/multisdi.c #define STRICT #include <windows.h> #include <windowsx.h> #include <process.h> #include <stdio.h> #include "resource.h" #include "afxres.h" #include "multisdi.h" HINSTANCE hInst; char szAppName[] = "MultiSDI"; char szAppTitle[] = "Multithread SDI Application"; // Критическая секция для рисования в окне CRITICAL_SECTION csWindowPaint; // Признак завершения всех задач BOOL fTerminate = FALSE; // Массив идентификаторов запущенных задач HANDLE hThreads[3]; // ----------------------------------------------------- // Функция WinMain // ----------------------------------------------------- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hWnd; MSG msg; // Сохраняем идентификатор приложения hInst = hInstance; // Преверяем, не было ли это приложение запущено ранее hWnd = FindWindow(szAppName, NULL); if(hWnd) { // Если было, выдвигаем окно приложения на // передний план if(IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE); SetForegroundWindow(hWnd); return FALSE; } // Регистрируем класс окна memset(&wc, 0, sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.hIconSm = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICONSM), IMAGE_ICON, 16, 16, 0); wc.style = 0; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wc.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU); wc.lpszClassName = szAppName; if(!RegisterClassEx(&wc)) if(!RegisterClass((LPWNDCLASS)&wc.style)) return FALSE; // Создаем главное окно приложения hWnd = CreateWindow(szAppName, szAppTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInst, NULL); if(!hWnd) return(FALSE); // Отображаем окно и запускаем цикл // обработки сообщений ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while(GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } // ----------------------------------------------------- // Функция WndProc // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate); HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy); HANDLE_MSG(hWnd, WM_PAINT, WndProc_OnPaint); HANDLE_MSG(hWnd, WM_COMMAND, WndProc_OnCommand); default: return(DefWindowProc(hWnd, msg, wParam, lParam)); } } // ----------------------------------------------------- // Функция WndProc_OnCreate // ----------------------------------------------------- BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct) { // Инициализируем критическую секцию InitializeCriticalSection(&csWindowPaint); // Сбрасываем флаг завершения задач fTerminate = FALSE; // Запускаем три задачи, сохраняя их идентификаторы // в массиве hThreads[0] = (HANDLE)_beginthread(PaintEllipse, 0, (void*)hWnd); hThreads[1] = (HANDLE)_beginthread(PaintRect, 0, (void*)hWnd); hThreads[2] = (HANDLE)_beginthread(PaintText, 0, (void*)hWnd); return TRUE; } // ----------------------------------------------------- // Функция WndProc_OnDestroy // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnDestroy(HWND hWnd) { // Устанавливаем флаг завершения задач fTerminate = TRUE; // Дожидаемся завершения всех трех задач WaitForMultipleObjects(3, hThreads, TRUE, INFINITE); // Перед удаляем критическую секцию DeleteCriticalSection(&csWindowPaint); // Останавливаем цикл обработки сообщений, расположенный // в главной задаче PostQuitMessage(0); return 0L; } // ----------------------------------------------------- // Функция WndProc_OnPaint // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnPaint(HWND hWnd) { HDC hdc; PAINTSTRUCT ps; RECT rc; // Входим в критическую секцию EnterCriticalSection(&csWindowPaint); // Перерисовываем внутреннюю область окна hdc = BeginPaint(hWnd, &ps); GetClientRect(hWnd, &rc); DrawText(hdc, "SDI Window", -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint(hWnd, &ps); // Выходим из критической секции LeaveCriticalSection(&csWindowPaint); return 0; } // ----------------------------------------------------- // Функция WndProc_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify) { switch (id) { case ID_FILE_EXIT: { // Завершаем работу приложения PostQuitMessage(0); return 0L; break; } case ID_HELP_ABOUT: { MessageBox(hWnd, "Multithread SDI Application\n" "(C) Alexandr Frolov, 1996\n" "Email: frolov@glas.apc.org", szAppTitle, MB_OK | MB_ICONINFORMATION); return 0L; break; } default: break; } return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify, DefWindowProc); } // ----------------------------------------------------- // Функция задачи PaintEllipse // ----------------------------------------------------- void PaintEllipse(void *hwnd) { HDC hDC; RECT rect; LONG xLeft, xRight, yTop, yBottom; short nRed, nGreen, nBlue; HBRUSH hBrush, hOldBrush; // Инициализация генератора случайных чисел srand((unsigned int)hwnd); // Задача работает в бесконечном цикле, // который будет прерван при завершении приложения while(!fTerminate) { // Входим в критическую секцию EnterCriticalSection(&csWindowPaint); // Отображаем эллипс, имеющий случайный цвет, // форму и расположение // Получаем контекст отображения hDC = GetDC(hwnd); // Получаем случайные цветовые компоненты nRed = rand() % 255; nGreen = rand() % 255; nBlue = rand() % 255; // Получаем случйные размеры эллипса GetWindowRect(hwnd, &rect); xLeft = rand() % (rect.left + 1); xRight = rand() % (rect.right + 1); yTop = rand() % (rect.top + 1); yBottom = rand() % (rect.bottom + 1); // Создаем кисть на основе случайных цветов hBrush = CreateSolidBrush(RGB(nRed, nGreen, nBlue)); // Выбираем кисть в контекст отображения hOldBrush = SelectObject(hDC, hBrush); // Рисуем эллипс Ellipse(hDC, min(xLeft, xRight), min(yTop, yBottom), max(xLeft, xRight), max(yTop, yBottom)); // Выбираем старую кисть в контекст отображения SelectObject(hDC, hOldBrush); // Удаляем созданную кисть DeleteObject(hBrush); // Освобождаем контекст отображения ReleaseDC(hwnd, hDC); // Выходим из критической секции LeaveCriticalSection(&csWindowPaint); // Выполняем задержку на 500 мс Sleep(500); } } // ----------------------------------------------------- // Функция задачи PaintRect // ----------------------------------------------------- void PaintRect(void *hwnd) { HDC hDC; RECT rect; LONG xLeft, xRight, yTop, yBottom; short nRed, nGreen, nBlue; HBRUSH hBrush, hOldBrush; srand((unsigned int)hwnd + 1); while(!fTerminate) { EnterCriticalSection(&csWindowPaint); hDC = GetDC(hwnd); nRed = rand() % 255; nGreen = rand() % 255; nBlue = rand() % 255; GetWindowRect(hwnd, &rect); xLeft = rand() % (rect.left + 1); xRight = rand() % (rect.right + 1); yTop = rand() % (rect.top + 1); yBottom = rand() % (rect.bottom + 1); hBrush = CreateSolidBrush(RGB(nRed, nGreen, nBlue)); hOldBrush = SelectObject(hDC, hBrush); Rectangle(hDC, min(xLeft, xRight), min(yTop, yBottom), max(xLeft, xRight), max(yTop, yBottom)); SelectObject(hDC, hOldBrush); DeleteObject(hBrush); ReleaseDC(hwnd, hDC); LeaveCriticalSection(&csWindowPaint); Sleep(500); } } // ----------------------------------------------------- // Функция задачи PaintText // ----------------------------------------------------- void PaintText(void *hwnd) { HDC hDC; RECT rect; LONG xLeft, xRight, yTop, yBottom; short nRed, nGreen, nBlue; srand((unsigned int)hwnd + 2); while(!fTerminate) { EnterCriticalSection(&csWindowPaint); hDC = GetDC(hwnd); GetWindowRect(hwnd, &rect); xLeft = rand() % (rect.left + 1); xRight = rand() % (rect.right + 1); yTop = rand() % (rect.top + 1); yBottom = rand() % (rect.bottom + 1); // Устанавливаем случайный цвет текста nRed = rand() % 255; nGreen = rand() % 255; nBlue = rand() % 255; SetTextColor(hDC, RGB(nRed, nGreen, nBlue)); // Устанавливаем случайный цвет фона nRed = rand() % 255; nGreen = rand() % 255; nBlue = rand() % 255; SetBkColor(hDC, RGB(nRed, nGreen, nBlue)); TextOut(hDC, xRight - xLeft, yBottom - yTop,"TEXT", 4); ReleaseDC(hwnd, hDC); LeaveCriticalSection(&csWindowPaint); Sleep(500); } } Файл multisdi.h (листинг 2.2) содержит прототипы функций, определенных в приложении MultiSDI. Это функция главного окна приложения WndProc, функции обработки сообщений WM_CREATE XE "WM_CREATE" , WM_DESTROY XE "WM_DESTROY" , WM_PAINT XE "WM_PAINT" , WM_COMMAND XE "WM_COMMAND" (с именами, соответственно, WndProc_OnCreate, WndProc_OnDestroy, WndProc_OnPaint и WndProc_OnCommand), а также функции задач PaintEllipse, PaintRect и PaintText. Листинг 2.2. Файл multisdi/multisdi.h // ----------------------------------------------------- // Описание функций // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct); void WndProc_OnDestroy(HWND hWnd); void WndProc_OnPaint(HWND hWnd); void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify); void PaintEllipse(void *hwnd); void PaintRect(void *hwnd); void PaintText(void *hwnd); Файл resource.h (листинг 2.3) создается автоматически и содержит определения констант для файла описания ресурсов приложения. Листинг 2.3. Файл multisdi/resource.h //{{NO_DEPENDENCIES}} // Microsoft Developer Studio generated include file. // Used by MultiSDI.RC // #define IDR_APPMENU 102 #define IDI_APPICON 103 #define IDI_APPICONSM 104 #define ID_FILE_EXIT 40001 #define ID_HELP_ABOUT 40003 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 121 #define _APS_NEXT_COMMAND_VALUE 40004 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif В файле описания ресурсов приложения MultiSDI (листинг 2.4), который тоже создается автоматически, определено главное меню приложения IDR_APPMENU и две пиктограммы (стандартного и уменьшенного размера), а также таблица строк, которая не используется. Листинг 2.4. Файл multisdi/multisdi.rc //Microsoft Developer Studio generated resource script. // #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h" ////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // English (U.S.) resources #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) #ifdef _WIN32 LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US #pragma code_page(1252) #endif //_WIN32 ////////////////////////////////////////////////////////////// // Menu // IDR_APPMENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", ID_FILE_EXIT END POPUP "&Help" BEGIN MENUITEM "&About...", ID_HELP_ABOUT END END #ifdef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END 2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END 3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END #endif // APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // Icon // // Icon with lowest ID value placed first to ensure application icon // remains consistent on all systems. IDI_APPICON ICON DISCARDABLE "multisdi.ico" IDI_APPICONSM ICON DISCARDABLE "multissm.ico" ////////////////////////////////////////////////////////////// // String Table // STRINGTABLE DISCARDABLE BEGIN ID_FILE_EXIT "Quits the application" END #endif // English (U.S.) resources ////////////////////////////////////////////////////////////// #ifndef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 3 resource. // ////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED Определения и глобальные переменныеПомимо обычных include-файлов, характерных для наших приложений, в приложении MultiSDI используется файл process.h. Этот файл содержит прототип функции _beginthread XE "_beginthread" , с помощью которой наше приложение создает задачи. Для синхронизации задач, выполняющих рисование в окне приложения, мы определили глобальную структуру csWindowPaint типа CRITICAL_SECTION, которая будет использоваться в качестве критической секции: CRITICAL_SECTION csWindowPaint; В глобальной переменной fTerminate хранится флаг, установка которого в состояние TRUE вызывает завершение всех трех задач рисования. И, наконец, в массиве hThreads хранятся идентификаторы запущенных задач. Этот массив будет использован, в частности, для того, чтобы перед удалением критической секции убедиться, что все задачи рисования завершили свою работу. Описание функцийПриведем описание функций, определенных в нашем приложении. Функция WinMainФункция WinMain не имеет никаких особенностей. Сразу после того как она получит управление, функция проверяет, не было ли данное приложение уже запущено. Если было, окно запущенного приложения выдвигается на передний план. Далее функция WinMain выполняет регистрацию класса окна приложения, создает главное окно, отображает его и запускает цикл обработки сообщений. Словом, все как всегда. Функция WndProcФункция главного окна приложения WndProc обрабатывает сообщения WM_CREATE, WM_DESTROY XE "WM_DESTROY" , WM_PAINT и WM_COMMNAD. Для этого с помощью макрокоманды HANDLE_MSG она вызывает, соответственно, функции WndProc_OnCreate, WndProc_OnDestroy, WndProc_OnPaint и WndProc_OnCommand. Необработанные сообщения передаются фукнции DefWindowProc. Функция WndProc_OnCreateПри создании главного окна приложения функция WndProc_OnCreate инициализирует структуру критической секции csWindowPaint, что необходимо для использования этого средства синхронизации задач: InitializeCriticalSection(&csWindowPaint); После выполнения такой инициализации в глобальную переменную fTerminate записывается значение FALSE, разрешающее работу задач. Содержимое этой глобальной переменной проверяется функциями задач, рисующих в главном окне приложения. Последнее, что делает функция WndProc_OnCreate перед возвращением управления, это запуск задач PaintEllipse, PaintRect и PaintText. Для запуска в приложении MultiSDI мы использовали функцию _beginthread XE "_beginthread" : hThreads[0] = (HANDLE)_beginthread(PaintEllipse, 0, (void*)hWnd); hThreads[1] = (HANDLE)_beginthread(PaintRect, 0, (void*)hWnd); hThreads[2] = (HANDLE)_beginthread(PaintText, 0, (void*)hWnd); Через первый параметр мы передаем фукнции _beginthread имя функции задачи. Второй параметр, определяющий начальный размер стека задачи, равен нулю, поэтому этот размер равен начальному размеру стека главной задачи приложения. Через третий парметр передается идентификатор главного окна приложения, в котором функции задач будут выполнять рисование. В приложении с многооконным интерфейсом MultiMDI мы воспользуемся другой функцией предназначенной для запуска задач - функцией CreateThread XE "CreateThread" . Функция WndProc_OnDestroyКогда наше приложение завершает свою работу, функция WndProc_OnDestroy, обрабатывающая сообщение WM_DESTROY XE "WM_DESTROY" , устанавливает содержимое глобальной переменной fTerminate в состояние TRUE. Функции задач периодически проверяют эту переменную, и как только ее содержимое станет равным TRUE, они завершают свою работу. Так как в нашем приложении была создана критическая секция, ее следует удалить перед завершением приложения. Однако прежде чем это сделать, необходимо убедиться, что все фукнции задач завершили свою работу. Это можно сделать с помощью функции WaitForMultipleObjects XE "WaitForMultipleObjects" , которая будет описана позже в разделе, посвященном синхронизации задач: WaitForMultipleObjects(3, hThreads, TRUE, INFINITE); В качестве первого параметра этой функции передается количество задач. Через второй параметр передается адрес массива с идентификаторами этих задач. Третий параметр, имеющий значение TRUE, указывает, что нужно дождаться завершения именно всех задач, а не одной из них. И, наконец, последний параметр определяет, что ожидание может длиться неограниченно долго. Как только все три задачи завершат свою работу, функция WndProc_OnDestroy удалит критическую секцию с помощью функции DeleteCriticalSection XE "DeleteCriticalSection" : DeleteCriticalSection(&csWindowPaint); После этого вызывается функция PostQuitMessage, в результате чего завершается цикл обработки сообщений. Функция WndProc_OnPaintФункция WndProc_OnPaint выполняет очень простую вещь - она рисует в центре главного окна приложения текстовую строку SDI Window, используя для этого известную вам из программирования для Microsoft Windows версиии 3.1 функцию DrawText XE "DrawText" . Обратите внимание, что перед получением контекста отображения мы входим в критическую секцию, вызывая функцию EnterCriticalSection XE "EnterCriticalSection" . После завершения рисования выполняется выход из критической секции с помощью фукнции LeaveCriticalSection XE "LeaveCriticalSection" : EnterCriticalSection(&csWindowPaint); hdc = BeginPaint(hWnd, &ps); GetClientRect(hWnd, &rc); DrawText(hdc, "SDI Window", -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint(hWnd, &ps); LeaveCriticalSection(&csWindowPaint); Функции задач, исходный текст которых мы скоро рассмотрим, выполняют рисование в окне аналогичным способом, используя для синхронизации критическцю секцию csWindowPaint. Не вдаваясь в подробности (которыми мы займемся позже), скажем, что в результате в любой момент времени в главном окне приложения будет рисовать только одна задача. Почему это важно? Дело в том, что для повышения производительности графическая система Microsoft Windows NT не содержит средств синхронизации задач. Поэтому для того чтобы функции графического интерфейса работали правильно, такую синхронизацию должно выполнять само приложение. Функция WndProc_OnCommandЭта функция обрабатывает сообщение WM_COMMAND, поступающее от главного меню приложения. Она не имеет никаких особенностей. Функция задачи PaintEllipseФункция задачи PaintEllipse (наряду с другими двумя функциями задач PaintRect и PaintText) запускается при помощи функции _createthread во время инициализации главного окна приложения. Прежде всего функция PaintEllipse выполняет инициализацию генератора случайных чисел, передавая функции инициализации srand в качестве начального значения идентификатор окна приложения. Вы можете, разумеется, использовать здесь любое другое 32-разрядное значение. Затем функция PaintEllipse входит в цикл, который будет завершен при установке глобальной переменной fTerminate в состояние TRUE. Это произойдет при уничтожении главного окна приложения. Перед рисованием эллипса функция PaintEllipse входит в критическую секцию csWindowPaint, что необходимо для синхронизации с другими задачами, выполняющими рисование в окне приложения. Способ рисования эллипса не имеет никаких особенностей. Соответствующая функция Ellipse была нами описана в 14 томе “Библиотеки системного программиста”, который называется “Графичский интерфейс GDI в MS Windows”. После того как эллипс будет нарисован, мы покидаем критическую секцию, вызывая функцию LeaveCriticalSection, и с помощью функции Sleep XE "Sleep" выполняем небольшую задержку. Функция Sleep приостанавливает выполнение задачи на количество миллисекунд, указанное в единственном параметере. Во время задержки задача не получает квантов времени, поэтому ожидание, выполняемое с помощью фукнции Sleep, не снижает производительности системы. Функция задачи PaintRectФункция задачи PaintRect аналогична только что рассмотренной функции PaintEllipse, за исключением того что она рисует прямоугольники. Для инициализации генератора случайных чисел используется другое значение, а рисование прямоугольника выполняется функцией Rectangle XE "Rectangle" . Эта функция имеет такие же параметры, что и функция Ellipse. Для синхронизации задача PaintRect использует все ту же критическую секцию csWindowPaint. Функция задачи PaintTextЗадача PaintText рисует текстовую строку TEXT, используя для этого функцию TextOut, описанную в 11 томе “Библиотеки системного программсита”. Синхронизация задачи выполняется с помощью критической секции csWindowPaint. |