Программирование для Windows NT© Александр Фролов, Григорий ФроловТом 26, часть 1, М.: Диалог-МИФИ, 1996, 272 стр. Приложение MutexSDIДля демонстрации способор работы с объектами Mutex мы переделали исходные тексты приложения MultiSDI, в котором синхронизация задач выполнялась при помощи критических секций. В новом приложении MutexSDI вместо критической секции мы использовали объект Mutex. Кроме того, в приложении MutexSDI демонстрируется способ обнаружения работающей копии приложения, основанный на том, что имена объектов Mutex являются глобальными (так же, как и имена объектов-событий, рассмотренных нами ранее). Исходный текст приложения приведен в листинге 4.3. Так как он сделан из исходного текста приложения MultiSDI, мы опишем только основные отличия. Листинг 4.3. Файл mutexsdi/mutexsdi.c #define STRICT #include <windows.h> #include <windowsx.h> #include <process.h> #include <stdio.h> #include "resource.h" #include "afxres.h" #include "mutexsdi.h" HINSTANCE hInst; char szAppName[] = "MutexMultiSDI"; char szAppTitle[] = "Multithread SDI Application with Mutex"; // Имя объекта Mutex char szMutexName[] = "$MyMutex$MutexMultiSDI$"; // Идентификатор объекта Mutex HANDLE hMutex; // Признак завершения всех задач 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; // Создаем объект Mutex hMutex = CreateMutex(NULL, FALSE, szMutexName); if(hMutex == NULL) { MessageBox(NULL, "CreateMutex Error", szAppTitle, MB_OK | MB_ICONEXCLAMATION); return 0l; } // Преверяем, не было ли это приложение запущено ранее if(GetLastError() == ERROR_ALREADY_EXISTS) { MessageBox(NULL, "MutexSDI already started", szAppTitle, MB_OK | MB_ICONEXCLAMATION); return 0l; } // Регистрируем класс окна 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) { // Сбрасываем флаг завершения задач 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); // Перед завершением освобождаем идентификатор // объекта Mutex CloseHandle(hMutex); // Останавливаем цикл обработки сообщений, расположенный // в главной задаче PostQuitMessage(0); return 0L; } // ----------------------------------------------------- // Функция WndProc_OnPaint // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnPaint(HWND hWnd) { HDC hdc; PAINTSTRUCT ps; RECT rc; DWORD dwRetCode; // Ожидаем, пока объект Mutex не перейдет в // отмеченное состояние dwRetCode = WaitForSingleObject(hMutex, INFINITE); // Если не было ошибок, выполняем рисование if(dwRetCode == WAIT_OBJECT_0) { // Перерисовываем внутреннюю область окна hdc = BeginPaint(hWnd, &ps); GetClientRect(hWnd, &rc); DrawText(hdc, "SDI Window", -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER); EndPaint(hWnd, &ps); // Переводим объект Mutex в неотмеченное состояние ReleaseMutex(hMutex); } 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 with Mutex\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; DWORD dwRetCode; srand((unsigned int)hwnd); while(!fTerminate) { // Ожидаем, пока объект Mutex не перейдет в // отмеченное состояние dwRetCode = WaitForSingleObject(hMutex, INFINITE); // Если не было ошибок, выполняем рисование if(dwRetCode == WAIT_OBJECT_0) { 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); ReleaseMutex(hMutex); } // Если ожидание было отменено или произошла ошибка, // прерываем цикл else { break; } Sleep(100); } } // ----------------------------------------------------- // Функция задачи PaintRect // ----------------------------------------------------- void PaintRect(void *hwnd) { HDC hDC; RECT rect; LONG xLeft, xRight, yTop, yBottom; short nRed, nGreen, nBlue; HBRUSH hBrush, hOldBrush; DWORD dwRetCode; srand((unsigned int)hwnd + 1); while(!fTerminate) { dwRetCode = WaitForSingleObject(hMutex, INFINITE); if(dwRetCode == WAIT_OBJECT_0) { 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); ReleaseMutex(hMutex); } else { break; } Sleep(100); } } // ----------------------------------------------------- // Функция задачи PaintText // ----------------------------------------------------- void PaintText(void *hwnd) { HDC hDC; RECT rect; LONG xLeft, xRight, yTop, yBottom; short nRed, nGreen, nBlue; DWORD dwRetCode; srand((unsigned int)hwnd + 2); while(!fTerminate) { dwRetCode = WaitForSingleObject(hMutex, INFINITE); if(dwRetCode == WAIT_OBJECT_0) { 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); ReleaseMutex(hMutex); } else { break; } Sleep(100); } } В области глобальных переменных определен массив szMutexName, в котором хранится имя объекта Mutex, используемого нашим приложением. Идентификатор этого объекта после его создания будет записан в глобальную переменную hMutex. Функция WinMain создает объект Mutex, вызывая для этого функцию CreateMutex, как это показано ниже: hMutex = CreateMutex(NULL, FALSE, szMutexName); if(hMutex == NULL) { . . . return 0l; } В качестве атрибутов защиты передается значение NULL. Так как второй параметр функции CreateMutex XE "CreateMutex" равен FALSE, объект Mutex после создания будет находиться в отмеченном состоянии. Если при создании объекта функция CreateMutex XE "CreateMutex" не вернула признак ошибки (значение NULL), приложение проверяет, действительно ли был создан новый объект Mutex или же функция вернула идентификатор для уже существующего в системе объекта с таким же именем. Проверка выполняется с помощью функции GetLastError XE "GetLastError" : if(GetLastError() == ERROR_ALREADY_EXISTS) { MessageBox(NULL, "MutexSDI already started", szAppTitle, MB_OK | MB_ICONEXCLAMATION); return 0l; } Если эта функция вернула значение ERROR_ALREADY_EXISTS, значит была запущена копия этого приложения, которая и создала объект Mutex с именем szMutexName. В этом случае приложение выводит сообщение и завершает свою работу. В нашем приложении четыре задачи могут рисовать в главном окне. Это главная задача процесса и три задачи, созданные главной задачей. Главная задача выполняет рисование при обработке сообщения WM_PAINT. Обработкой этого сообщения занимается функция WndProc_OnPaint. Перед тем как получить контекст отображения и приступить к рисованию, эта функция вызывает функцию WaitForSingleObject XE "WaitForSingleObject" , пытаясь стать владельцем объекта Mutex: dwRetCode = WaitForSingleObject(hMutex, INFINITE); Если никакая другая задача в данный момент не претендует на этот объект и, следовательно, не собирается рисовать в окне приложения, функция WaitForSingleObject XE "WaitForSingleObject" немедленно возвращает управление с кодом возврата, равным WAIT_OBJECT_0. После этого функция WndProc_OnPaint получает контекст отображения, выполняет рисование, освободает контекст отображения и затем переводит объект Mutex в неотмеченное состояние, вызывая функцию ReleaseMutex XE "ReleaseMutex" : ReleaseMutex(hMutex); После этого другие задачи, выполняющие ожидание для объекта Mutex, могут продолжить свою работу. В приложении определены три функции задач, выполняющих рисование в окне приложения. Синхронизация всех этих функций между собой и с главной задачей процесса выполняется одинаковым способом. Все эти задачи выполняют в цикле ожидание события для объекта Mutex с идентификатором hMutex: while(!fTerminate) { dwRetCode = WaitForSingleObject(hMutex, INFINITE); if(dwRetCode == WAIT_OBJECT_0) { hDC = GetDC(hwnd); . . . Ellipse(hDC, min(xLeft, xRight), min(yTop, yBottom), max(xLeft, xRight), max(yTop, yBottom)); . . . ReleaseDC(hwnd, hDC); ReleaseMutex(hMutex); } else break; Sleep(100); } Если ни одна задача не владеет объектом Mutex, функция WaitForSingleObject XE "WaitForSingleObject" возвращает значение WAIT_OBJECT_0. При этом задача выполняет рисование в окне приложения. В том случае когда какая-либо другая задача владеет объектом Mutex, данная задача перейдет в состояние ожидания. Если при ожидании произошла ошибка, цикл завершает свою работу. После рисования задача переводит объект Mutex в неотмеченное состояние с помощью функции ReleaseMutex XE "ReleaseMutex" . Кратко перечислим другие файлы, имеющие отношение к приложению MutexSDI. В файле mutexsdi.h (листинг 4.4) находятся прототипы функций, определенных в приложении. Листинг 4.4. Файл mutexsdi/mutexsdi.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 (листинг 4.5) создается автоматически и содержит определения констант для файла ресурсов приложения. Листинг 4.5. Файл mutexsdi/resource.h //{{NO_DEPENDENCIES}} // Microsoft Developer Studio generated include file. // Used by MutexSDI.RC // #define IDR_APPMENU 102 #define IDI_APPICON 103 #define IDI_APPICONSM 104 #define ID_FILE_EXIT 40001 #define ID_HELP_ABOUT 40003 #define ID_FORMAT_BOLD 40010 #define ID_FORMAT_ITALIC 40011 #define ID_FORMAT_UNDERLINE 40012 #define ID_FORMAT_PARAGRAPH_LEFT 40014 #define ID_FORMAT_PARAGRAPH_RIGHT 40015 #define ID_FORMAT_PARAGRAPH_CENTER 40016 #define ID_EDIT_DELETE 40021 #define ID_FILE_SAVEAS 40024 #define ID_EDIT_SELECTALL 40028 #define ID_SETPROTECTION_PAGENOACCESS 40035 #define ID_SETPROTECTION_PAGEREADONLY 40036 #define ID_SETPROTECTION_PAGEREADWRITE 40037 #define ID_SETPROTECTION_PAGEGUARD 40038 #define ID_MEMORY_READ 40039 #define ID_MEMORY_WRITE 40040 #define ID_MEMORY_LOCK 40041 #define ID_MEMORY_UNLOCK 40042 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 121 #define _APS_NEXT_COMMAND_VALUE 40043 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif И, наконец, файл ресурсов приложения mutexsdi.rc приведен в листинге 4.6. Листинг 4.6. Файл mutexsdi/mutexsdi.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 "mutexsdi.ico" IDI_APPICONSM ICON DISCARDABLE "mutexssm.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 |