Приложение 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 равен FALSE, объект Mutex после создания будет находиться в отмеченном состоянии.
Если при создании объекта функция CreateMutex не вернула признак ошибки (значение NULL), приложение проверяет, действительно ли был создан новый объект Mutex или же функция вернула идентификатор для уже существующего в системе объекта с таким же именем. Проверка выполняется с помощью функции 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, пытаясь стать владельцем объекта Mutex:
dwRetCode = WaitForSingleObject(hMutex, INFINITE);
Если никакая другая задача в данный момент не претендует на этот объект и, следовательно, не собирается рисовать в окне приложения, функция WaitForSingleObject немедленно возвращает управление с кодом возврата, равным WAIT_OBJECT_0. После этого функция WndProc_OnPaint получает контекст отображения, выполняет рисование, освободает контекст отображения и затем переводит объект Mutex в неотмеченное состояние, вызывая функцию 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 возвращает значение WAIT_OBJECT_0. При этом задача выполняет рисование в окне приложения. В том случае когда какая-либо другая задача владеет объектом Mutex, данная задача перейдет в состояние ожидания. Если при ожидании произошла ошибка, цикл завершает свою работу.
После рисования задача переводит объект Mutex в неотмеченное состояние с помощью функции 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