Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Программирование для Windows NT

© Александр Фролов, Григорий Фролов
Том 26, часть 1, М.: Диалог-МИФИ, 1996, 272 стр.

[Назад] [Содеожание] [Дальше]

Приложение SEMMDI

Приложение SEMMDI создано на базе приложения MultiMDI и демонстрирует использование семфафоров для ограничения количества работающих задач, запущенных для MDI-окон.

На рис. 4.6 показано главное окно приложения SEMMDI. Мы запустили это приложение в среде операционной системы Microsoft Windows 95, так как она также является мультизадачной операционной системой и может работать с семафорами и другими объектами синхронизации. Разумеется, что в среде Microsoft Windows NT приложение SEMMDI также будет работать.

Рис. 4.6. Главное окно приложения SEMMDI, запущенного в среде операционной системы Microsoft Windows 95

Выбирая из меню File строку New, вы можете создавать новые MDI-окна. В первых двух созданных вами окнах начнется процесс рисования эллипсов случайных размеров и цвета. В остальных окнах отображается только строка Waiting.

Если теперь закрыть одно из двух работающих окон (в которых идет рисование эллипсов), одно из ожидающих окон “просыпается” и в нем начинается процесс рисования. Таким образом, сколько бы вы ни создали MDI-окон, рисование будет выполняться только в двух из них.

Выбирая из меню Semaphore строку Increment, вы можете увеличивать значение семафора на единицу. При этом максимальное количество окон, в которых идент процесс рисования, также будет увеличиваться.

Заметим, что ожидающие окна нельзя удалить. Все остальные возможности приложения MultiMDI сохранены. В частности, если сделать щелчок правой клавишей мыши в MDI-окне, на экране появится плавающее меню, с помощью которого можно выполнять над этим окном различные операции.

Исходные тексты приложения

Главный файл исходных текстов приложения SEMMDI представлен в листинге 4.7. Заметим, что для сборки проекта необходимо использовать мультизадачный вариант библиотеки времени выполнения.

Листинг 4.7. Файл semmdi/semmdi.c


#define STRICT
#include <windows.h>
#include <windowsx.h>
#include <stdio.h>
#include "resource.h"
#include "afxres.h"
#include "semmdi.h"

// Имена классов окна
char const szFrameClassName[] = "MDISemAppClass";
char const szChildClassName[] = "MDISemChildAppClass";

// Заголовок окна
char const szWindowTitle[] = 
  "Multithread MDI Application with Semaphores";

HINSTANCE hInst;

HWND hwndFrame;  // окно Frame Window
HWND hwndClient; // окно Client Window
HWND hwndChild;  // окно Child Window

// Структура, которая создается для каждого дочернего окна
typedef struct _CHILD_WINDOW_TAG
{
  // Признак активности задачи
  BOOL fActive;

  // Флаг ожидания семафора
  BOOL fWaiting;
  
  // Критическая секция для рисования в окне
  CRITICAL_SECTION csChildWindowPaint; 

  // Идентификатор задачи
  HANDLE hThread;
} CHILD_WINDOW_TAG;

typedef CHILD_WINDOW_TAG *LPCHILD_WINDOW_TAG;

// Семафор для ограничения количества работающих задач
HANDLE hSemaphore;

// Имя семафора
char const lpSemaphoreName[] =
 "$MyVerySpecialSemaphoreName$For$SemMDI$Application$";

// =====================================
// Функция WinMain
// =====================================
int APIENTRY 
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
        LPSTR lpCmdLine, int nCmdShow)
{
  MSG  msg;          // структура для работы с сообщениями
  hInst = hInstance; // сохраняем идентификатор приложения

  // Инициализируем приложение
  if(!InitApp(hInstance))
    return FALSE;

  // Создаем семафор для ограничения количества
  // задач, работающих одновременно
  hSemaphore = CreateSemaphore(NULL, 2, 2, lpSemaphoreName);
  if(hSemaphore == NULL)
  {
    MessageBox(NULL,
      "Ошибка при создании семафора",
       szWindowTitle, MB_OK | MB_ICONEXCLAMATION);
    return FALSE;
  }
  
  // Создаем главное окно приложения - Frame Window
  hwndFrame = CreateWindow(
    szFrameClassName,    // имя класса окна
    szWindowTitle,       // заголовок окна
    WS_OVERLAPPEDWINDOW, // стиль окна
    CW_USEDEFAULT, 0,    // задаем размеры и расположение
    CW_USEDEFAULT, 0,    // окна, принятые по умолчанию
    0,                   // идентификатор родительского окна
    0,                   // идентификатор меню
    hInstance,           // идентификатор приложения
    NULL);   // указатель на дополнительные параметры

  // Если создать окно не удалось, завершаем приложение
  if(!hwndFrame)
    return FALSE;

  // Рисуем главное окно
  ShowWindow(hwndFrame, nCmdShow);
  UpdateWindow(hwndFrame);

  // Запускаем цикл обработки сообщений
  while(GetMessage(&msg, NULL, 0, 0))
  {
    // Трансляция для MDI-приложения
    if(!TranslateMDISysAccel(hwndClient, &msg))
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
  }
  
  // Освобождаем идентификатор семафора
  CloseHandle(hSemaphore);
  return msg.wParam;
}

// =====================================
// Функция InitApp
// Выполняет регистрацию класса окна
// =====================================

BOOL InitApp(HINSTANCE hInstance)
{
  ATOM aWndClass; // атом для кода возврата
  WNDCLASS wc;    // структура для регистрации

  // Регистрируем класс для главного окна приложения
  // (для окна Frame Window)
  memset(&wc, 0, sizeof(wc));
  wc.lpszMenuName  = "APP_MENU";
  wc.style         = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc   = (WNDPROC)FrameWndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hInstance;
  wc.hIcon         = LoadIcon(hInstance, "APP_ICON");
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
  wc.lpszClassName = (LPSTR)szFrameClassName;
  aWndClass = RegisterClass(&wc);

  if(!aWndClass)
    return FALSE;

  // Регистрируем класс окна для 
  //дочернего окна Document Window
  memset(&wc, 0, sizeof(wc));
  wc.lpszMenuName  = 0;
  wc.style         = CS_HREDRAW | CS_VREDRAW;
  wc.lpfnWndProc   = (WNDPROC)ChildWndProc;
  wc.cbClsExtra    = 0;
  wc.cbWndExtra    = 0;
  wc.hInstance     = hInstance;
  wc.hIcon         = LoadIcon(hInstance, "APPCLIENT_ICON");
  wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
  wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  wc.lpszClassName = (LPSTR)szChildClassName;
  aWndClass = RegisterClass(&wc);

  if(!aWndClass)
    return FALSE;

  return TRUE;
}

// =====================================
// Функция FrameWndProc
// =====================================

LRESULT CALLBACK 
FrameWndProc(HWND hwnd, UINT msg, 
             WPARAM wParam, LPARAM lParam)
{
  HWND   hwndChild;
  HANDLE hThread;
  DWORD  dwIDThread;
  CHAR   szBuf[255];
  LPCHILD_WINDOW_TAG lpMyWndTag;
  
  // Структура для создания окна Client Window
  CLIENTCREATESTRUCT clcs;

  // Указатель на структуру для хранения 
  // состояния дочернего окна
  LPCHILD_WINDOW_TAG lpTag;

  switch (msg)
  {
    // При создании окна Frame Window создаем
    // окно Client Window, внутри которого будут создаваться
    // дочерние окна Document Window
    case WM_CREATE:
    {
      clcs.hWindowMenu = GetSubMenu(GetMenu(hwnd), 2);
      clcs.idFirstChild = 500;
      
      // Создаем окно Client Window 
      hwndClient = CreateWindow(
        "MDICLIENT",    // имя класса окна
        NULL,           // заголовок окна
        WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | 
          WS_HSCROLL | WS_VSCROLL,  
        0, 0, 0, 0,
        hwnd,         // идентификатор родительского окна
        (HMENU)1,     // идентификатор меню
        hInst,        // идентификатор приложения
        (LPSTR)&clcs);// указатель на дополнительные параметры

      break;
    }

    // Обработка сообщений от главного меню приложения
    case WM_COMMAND:
    {
      switch (wParam)
      {
        // Создание нового окна Document Window
        case CM_FILENEW:
        {
          hwndChild = CreateMDIWindow(
            (LPSTR)szChildClassName,    // класс окна
            "MDI Child Window",         // заголовок окна
            0,                      // дополнительные стили
            CW_USEDEFAULT, CW_USEDEFAULT, // размеры окна 
            CW_USEDEFAULT, CW_USEDEFAULT, //  Document Window
            hwndClient,
            hInst,           // идентификатор приложения
            0);              // произвольное значение

          // Получаем память для структуры, в которой будет
          // хранится состояние окна
          lpTag = malloc(sizeof(CHILD_WINDOW_TAG));
  
          // Устанавливаем признак активности
          lpTag->fActive = 1;

          // Инициализируем критическую секцию
          InitializeCriticalSection(
             &(lpTag->csChildWindowPaint));
          
          // Устанавливаем адрес структуры состояния в
          // памяти окна
          SetWindowLong(hwndChild, GWL_USERDATA, (LONG)lpTag);

          // Создаем задачу для дочернего окна
          hThread = CreateThread(NULL, 0, 
            (LPTHREAD_START_ROUTINE)ThreadRoutine,
            (LPVOID)hwndChild, 0,(LPDWORD)&dwIDThread);

          if(hThread == NULL)
          {
            MessageBox(hwnd,"Ошибка при создании задачи",
	           szWindowTitle, MB_OK | MB_ICONEXCLAMATION);
          }

          // Сохраняем идентификатор созданной задачи
          lpTag->hThread = hThread;
          
          // Отображаем идентификатор задачи в заголовке
          // дочернего окна
          sprintf(szBuf, "Thread ID = %lX", dwIDThread);
          SetWindowText(hwndChild, szBuf);
   
          break;
        }

        // Увеличиваем содержимое счетчика семафора
        // на единицу
        case ID_SEMAPHORE_INCREMENT:
        {
          ReleaseSemaphore(hSemaphore, 1, NULL);
          break;
        }
        
        // Размещение окон Document Window рядом друг с другом
        case CM_WINDOWTILE:
        {
          SendMessage(hwndClient, WM_MDITILE, 0, 0);
          break;
        }

        // Размещение окон Document Window с перекрытием
        case CM_WINDOWCASCADE:
        {
          SendMessage(hwndClient, WM_MDICASCADE, 0, 0);
          break;
        }

        // Размещение пиктограм минимизированых окон 
        // Document Window в нижней части окна Client Window
        case CM_WINDOWICONS:
        {
          SendMessage(hwndClient, WM_MDIICONARRANGE, 0, 0);
          break;
        }

        // Уничтожение всех окон Document Window
        case CM_WINDOWCLOSEALL:
        {
          HWND hwndTemp;

          ShowWindow(hwndClient, SW_HIDE);

          while(TRUE) 
          {
            // Получаем идентификатор дочернего окна 
            // для окна Client Window
            hwndTemp = GetWindow(hwndClient, GW_CHILD);
            // Если дочерних окон больше нет, выходим из цикла
            if(!hwndTemp)
              break;

            // Пропускаем окна-заголовки
            while(hwndTemp && GetWindow(hwndTemp, GW_OWNER))
              hwndTemp = GetWindow(hwndTemp, GW_HWNDNEXT);

            // Удаляем дочернее окно Document Window
            if(hwndTemp)
            {
              // Завершаем задачу, запущенную для окна
              lpMyWndTag = 
                 (LPCHILD_WINDOW_TAG)GetWindowLong(
                 hwndTemp, GWL_USERDATA);
              lpMyWndTag->fActive = 0;  

              // Посылаем сообщение, в ответ на которое
              // окно будет удалено
              SendMessage(hwndClient, WM_MDIDESTROY, 
                (WPARAM)hwndTemp, 0);
            }
            else
              break;
          }

          // Отображаем окно Client Window
          ShowWindow(hwndClient, SW_SHOW);

          break;
        }

        // Устанавливаем классы приоритета процесса
        case ID_PRIORITYCLASS_REALTIME:
        {
          SetPriorityClass(GetCurrentProcess(),
            REALTIME_PRIORITY_CLASS);
          break;
        }
        case ID_PRIORITYCLASS_HIGH:
        {
          SetPriorityClass(GetCurrentProcess(),
            HIGH_PRIORITY_CLASS);
          break;
        }
        case ID_PRIORITYCLASS_NORMAL:
        {
          SetPriorityClass(GetCurrentProcess(),
            NORMAL_PRIORITY_CLASS);
          break;
        }
        case ID_PRIORITYCLASS_IDLE:
        {
          SetPriorityClass(GetCurrentProcess(),
            IDLE_PRIORITY_CLASS);
          break;
        }

        case CM_HELPABOUT:
        {
          MessageBox(hwnd,
            "Демонстрация использования мультизадачности\n"
            "в MDI-приложениях\n"
            "(C) Alexandr Frolov, 1996\n"
            "Email: frolov@glas.apc.org",
            szWindowTitle, MB_OK | MB_ICONINFORMATION);
          break;
        }

        // Завершаем работу приложения
        case CM_FILEEXIT:
        {
          DestroyWindow(hwnd);
          break;
        }

        default:
          break;
      }

      // Определяем идентификатор активного окна 
      // Document Window
      hwndChild =
        (HWND)LOWORD(SendMessage(hwndClient, 
        WM_MDIGETACTIVE, 0, 0l));

      // Если это окно, посылаем ему сообщение WM_COMMAND
      if(IsWindow(hwndChild))
        SendMessage(hwndChild, WM_COMMAND, wParam, lParam);

      return DefFrameProc(
        hwnd, hwndClient, msg, wParam, lParam);
    }

    case WM_DESTROY:
    {
      PostQuitMessage(0);
      break;
    }

    default:
      break;
  }
  return DefFrameProc(hwnd, hwndClient, msg, wParam, lParam);
}

// =====================================
// Функция ChildWndProc
// =====================================

LRESULT CALLBACK
ChildWndProc(HWND hwnd, UINT msg, 
             WPARAM wParam, LPARAM lParam)
{
  HDC hdc;
  PAINTSTRUCT ps;
  RECT rc;
  LPCHILD_WINDOW_TAG lpMyWndTag;
  HMENU hmenuPopup;
  POINT pt;
  CHAR szBuf[256];

  switch (msg)
  {
    case WM_PAINT:
    {
      // Получаем адрес структуры состояния окна
      lpMyWndTag = 
        (LPCHILD_WINDOW_TAG)GetWindowLong(hwnd, GWL_USERDATA);

      // Входим в критическую секцию
      EnterCriticalSection(&(lpMyWndTag->csChildWindowPaint));

      // Перерисовываем внутреннюю область дочернего окна
      hdc = BeginPaint(hwnd, &ps);
      GetClientRect(hwnd, &rc);

      if(lpMyWndTag->fWaiting)
        DrawText(hdc, "Waiting...", -1, &rc,
          DT_SINGLELINE | DT_CENTER | DT_VCENTER);
      else
        DrawText(hdc, "Child Window. Running", -1, &rc,
          DT_SINGLELINE | DT_CENTER | DT_VCENTER);

      EndPaint(hwnd, &ps);

      // Выходим из критической секции
      LeaveCriticalSection(&(lpMyWndTag->csChildWindowPaint));
      break;
    }

    case WM_CLOSE:
    {
      // Сбрасываем признак активности задачи
      lpMyWndTag = 
        (LPCHILD_WINDOW_TAG)GetWindowLong(hwnd, GWL_USERDATA);

      // Если задача находится в состоянии ожидания 
      // семафора, запрещаем удаление окна
      if(lpMyWndTag->fWaiting)
        return 0;
      
      // Если задача не ожидает семафор, завершаем
      // задачу и разрешаем удаление окна
      else
        lpMyWndTag->fActive = 0;  
      
      break;
    }

    // Когда пользователь нажимает правую кнопку мыши 
    // в дочернем окне, отображаем плавающее меню
    case WM_RBUTTONDOWN:
    {
      pt.x = LOWORD(lParam);
      pt.y = HIWORD(lParam);
      ClientToScreen(hwnd, &pt);

      hmenuPopup = GetSubMenu(
        LoadMenu(hInst, "IDR_POPUPMENU"), 0);
      TrackPopupMenu(hmenuPopup, 
        TPM_CENTERALIGN | TPM_LEFTBUTTON,
        pt.x, pt.y, 0, hwnd, NULL);
      DestroyMenu(hmenuPopup);
      break;
    }

    // Обрабатываем команды, поступающие от плавающего меню
    case WM_COMMAND:
    {
      switch (wParam)
      {
        // Приостановка выполнения задачи
        case ID_THREADCONTROL_SUSPEND:
        {
          lpMyWndTag = 
            (LPCHILD_WINDOW_TAG)GetWindowLong(
            hwnd, GWL_USERDATA);
          
          // Входим в критическую секцию
          EnterCriticalSection(
             &(lpMyWndTag->csChildWindowPaint));

          SuspendThread(lpMyWndTag->hThread);

          // Выходим из критической секции
          LeaveCriticalSection(
            &(lpMyWndTag->csChildWindowPaint));

          break;
        }
        
        // Возобновление выполнения задачи
        case ID_THREADCONTROL_RESUME:
        {
   
          lpMyWndTag = 
            (LPCHILD_WINDOW_TAG)GetWindowLong(
            hwnd, GWL_USERDATA);
          ResumeThread(lpMyWndTag->hThread);
          break;
        }

        // Изменение относительного приоритета
        case ID_THREADCONTROL_PRIORITYLOWEST:
        {
          lpMyWndTag = 
            (LPCHILD_WINDOW_TAG)GetWindowLong(
            hwnd, GWL_USERDATA);
          SetThreadPriority(lpMyWndTag->hThread, 
            THREAD_PRIORITY_LOWEST);
          break;
        }
        case ID_THREADCONTROL_PRIORITYNORMAL:
        {
          lpMyWndTag = 
            (LPCHILD_WINDOW_TAG)GetWindowLong(
            hwnd, GWL_USERDATA);
          SetThreadPriority(lpMyWndTag->hThread, 
            THREAD_PRIORITY_NORMAL);
          break;
        }
        case ID_THREADCONTROL_PRIORITYHIGHEST:
        {
          lpMyWndTag = 
            (LPCHILD_WINDOW_TAG)GetWindowLong(
            hwnd, GWL_USERDATA);
          SetThreadPriority(lpMyWndTag->hThread, 
            THREAD_PRIORITY_HIGHEST);
          break;
        }

        // Определение и отображение относительного приоритета
        case ID_THREADCONTROL_GETPRIORITY:
        {
          lpMyWndTag = 
            (LPCHILD_WINDOW_TAG)GetWindowLong(
            hwnd, GWL_USERDATA);
          
          strcpy(szBuf, "Thread priority: ");
          switch (GetThreadPriority(lpMyWndTag->hThread))
          {
            case THREAD_PRIORITY_LOWEST:
            {
              strcat(szBuf, "THREAD_PRIORITY_LOWEST");
              break;
            }
            case THREAD_PRIORITY_BELOW_NORMAL:
            {
              strcat(szBuf, "THREAD_PRIORITY_BELOW_NORMAL");
              break;
            }
            case THREAD_PRIORITY_NORMAL:
            {
              strcat(szBuf, "THREAD_PRIORITY_NORMAL");
              break;
            }
            case THREAD_PRIORITY_ABOVE_NORMAL:
            {
              strcat(szBuf, "THREAD_PRIORITY_ABOVE_NORMAL");
              break;
            }
            case THREAD_PRIORITY_HIGHEST:
            {
              strcat(szBuf, "THREAD_PRIORITY_HIGHEST");
              break;
            }
          }

	   MessageBox(hwnd, szBuf,
            szWindowTitle, MB_OK | MB_ICONINFORMATION);
          break;
        }

        // Удаление задачи
        case ID_THREADCONTROL_KILLTHREAD:
        {
          lpMyWndTag = 
            (LPCHILD_WINDOW_TAG)GetWindowLong(
             hwnd, GWL_USERDATA);
          TerminateThread(lpMyWndTag->hThread, 5);
          break;
        }
        
        // Уничтожение дочернего окна
        case ID_THREADCONTROL_CLOSEWINDOW:
        {
          lpMyWndTag = 
            (LPCHILD_WINDOW_TAG)GetWindowLong(
            hwnd, GWL_USERDATA);

          // Если задача не находится в состоянии ожидания
          // семафора, удаляем окно и завершаем работу 
          // задачи, запущенной для него
          if(!lpMyWndTag->fWaiting)
          {
            SendMessage(hwndClient, WM_MDIDESTROY, 
                (WPARAM)hwnd, 0);

            lpMyWndTag->fActive = 0;  
          }
          break;
        }
        default:
          break;
      }
      break;
    }
    default:
      break;
  }
  return DefMDIChildProc(hwnd, msg, wParam, lParam);
}

// =====================================
// Функция ThreadRoutine
// Задача, которая выполняется для каждого
// дочернего окна
// =====================================

DWORD ThreadRoutine(HWND hwnd) 
{
  LONG lThreadWorking = 1L;
  HDC hDC;
  RECT rect;
  LONG xLeft, xRight, yTop, yBottom;
  short nRed, nGreen, nBlue;
  HBRUSH hBrush, hOldBrush;
  LPCHILD_WINDOW_TAG lpMyWndTag;

  // Получаем указатель на структуру параметров окна 
  lpMyWndTag = 
    (LPCHILD_WINDOW_TAG)GetWindowLong(hwnd, GWL_USERDATA);

  // Если произошла ошибка, завершаем работу задачи
  if(lpMyWndTag == NULL)
  {
    return 0;
  }

  // Устанавливаем флаг ожидания семафора
  lpMyWndTag->fWaiting = TRUE;

  // Выполняем ожидание семафора. Если оно завершилось
  // с ошибкой, завершаем работу задачи
  if(WAIT_FAILED == 
    WaitForSingleObject(hSemaphore, INFINITE))
  {
    return 0;
  }

  // Сбрасываем флаг ожидания семафора
  lpMyWndTag->fWaiting = FALSE;

  srand((unsigned int)hwnd);
  
  while(TRUE) 
  {
    if(!lpMyWndTag->fActive)
      break;

    EnterCriticalSection(&(lpMyWndTag->csChildWindowPaint));

    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(&(lpMyWndTag->csChildWindowPaint));
    Sleep(1);
  }

  DeleteCriticalSection(&(lpMyWndTag->csChildWindowPaint));
  free(lpMyWndTag);
  
  // Перед завершением работы задачи увеличиваем
  // на единицу счетчик семафора, разрешая работу
  // других задач, находящихся в состоянии ожидания
  if(hSemaphore != NULL)
    ReleaseSemaphore(hSemaphore, 1, NULL);

  return 0;
}

Файл semmdi.h (листинг 4.8) содержит прототипы функций, определенных в приложении SEMMDI.

Листинг 4.8. Файл semmdi/semmdi.h


// Прототипы функций
BOOL InitApp(HINSTANCE);
LRESULT CALLBACK FrameWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK ChildWndProc(HWND, UINT, WPARAM, LPARAM);
DWORD ThreadRoutine(HWND hwnd);
VOID AddThreadToList(HANDLE hThread);

Файл resource.h (листинг 4.9), создаваемый автоматически, содержит определение констант для ресурсов приложения SEMMDI.

Листинг 4.9. Файл semmdi/resource.h


//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by Mdiapp.rc
//
#define CM_HELPABOUT                    100
#define CM_FILEEXIT                     101
#define CM_FILENEW                      102
#define CM_WINDOWTILE                   103
#define CM_WINDOWCASCADE                104
#define CM_WINDOWICONS                  105
#define CM_WINDOWCLOSEALL               106
#define ID_THREADCONTROL_SUSPEND        40001
#define ID_THREADCONTROL_RESUME         40002
#define ID_THREADCONTROL_PRIORITYLOWEST 40003
#define ID_THREADCONTROL_PRIORITYNORMAL 40004
#define ID_THREADCONTROL_PRIORITYHIGHEST 40005
#define ID_THREADCONTROL_GETPRIORITY    40006
#define ID_THREADCONTROL_KILLTHREAD     40007
#define ID_THREADCONTROL_CLOSEWINDOW    40008
#define ID_PRIORITYCLASS_REALTIME       40009
#define ID_PRIORITYCLASS_NORMAL         40010
#define ID_PRIORITYCLASS_HIGH           40011
#define ID_PRIORITYCLASS_IDLE           40012
#define ID_SEMAPHORE_INCREMENT          40013

// Next default values for new objects
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40015
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif

Ресурсы приложения SEMMDI определены в файле mdiapp.rc, который приведен в листинге 4.10.

Листинг 4.10. Файл semmdi/mdiapp.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
//

APP_MENU MENU DISCARDABLE 
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "&New",            CM_FILENEW
        MENUITEM SEPARATOR
        MENUITEM "E&xit",           CM_FILEEXIT
    END
    POPUP "&Semaphore"
    BEGIN
        MENUITEM "&Increment",      ID_SEMAPHORE_INCREMENT
    END
    POPUP "Priority class"
    BEGIN
        MENUITEM "&Realtime",       ID_PRIORITYCLASS_REALTIME
        MENUITEM "&High",           ID_PRIORITYCLASS_HIGH
        MENUITEM "&Normal",         ID_PRIORITYCLASS_NORMAL
        MENUITEM "&Idle",           ID_PRIORITYCLASS_IDLE
    END
    POPUP "&Window"
    BEGIN
        MENUITEM "&Tile",           CM_WINDOWTILE
        MENUITEM "&Cascade",        CM_WINDOWCASCADE
        MENUITEM "Arrange &Icons",  CM_WINDOWICONS
        MENUITEM "Close &All",                  CM_WINDOWCLOSEALL
    END
    POPUP "&Help"
    BEGIN
        MENUITEM "&About...",       CM_HELPABOUT
    END
END

IDR_POPUPMENU MENU DISCARDABLE 
BEGIN
    POPUP "&Thread Control"
    BEGIN
        MENUITEM "&Suspend",       ID_THREADCONTROL_SUSPEND
        MENUITEM "&Resume",        ID_THREADCONTROL_RESUME
        MENUITEM SEPARATOR
        MENUITEM "Priority &Lowest", ID_THREADCONTROL_PRIORITYLOWEST

        MENUITEM "Priority &Normal", ID_THREADCONTROL_PRIORITYNORMAL

        MENUITEM "Priority &Highest", ID_THREADCONTROL_PRIORITYHIGHEST

        MENUITEM SEPARATOR
        MENUITEM "&Get Priority", ID_THREADCONTROL_GETPRIORITY
        MENUITEM SEPARATOR
        MENUITEM "&Kill Thread", ID_THREADCONTROL_KILLTHREAD
        MENUITEM "&Close Window", ID_THREADCONTROL_CLOSEWINDOW
    END
END

//////////////////////////////////////////////////////////////
//
// Icon
//

// Icon with lowest ID value placed first to ensure 
// application icon
// remains consistent on all systems.
APP_ICON                ICON    DISCARDABLE     "multimdi.ico"
APPCLIENT_ICON          ICON    DISCARDABLE     "mdicl.ico"

#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

#endif    // English (U.S.) resources
//////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
//////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
//////////////////////////////////////////////////////////////
#endif    // not APSTUDIO_INVOKED

Определения и глобальные переменные

Приложение SEMMDI сделано на базе описанного ранее приложения MultiMDI, поэтому здесь мы приведем сокращенное описание определений, глобальных переменных и функций.

Для каждого дочернего MDI-окна в приложении MultiMDI мы создавали структуру типа CHILD_WINDOW_TAG, в которой хранилась такая информация, как признак активности задачи, запущенной для этого окна, критическая секция для рисования в окне, а также идентификатор задачи, запущенной для окна. Создавая приложение SEMMDI, мы добавили в эту структуру поле fWaiting:


typedef struct _CHILD_WINDOW_TAG
{
  BOOL fActive;
  BOOL fWaiting;
  CRITICAL_SECTION csChildWindowPaint; 
  HANDLE hThread;
} CHILD_WINDOW_TAG;
typedef CHILD_WINDOW_TAG *LPCHILD_WINDOW_TAG;

В поле fWaiting записывается значение TRUE, когда соответствующая задача находится в состоянии ожидания семафора. Проверяя содержимое этого поля, мы запрещаем пользователю удалять окна, если соответствующие им задачи находятся в состоянии ожидания семафора.

Описание функций приложения

Приведем описание функций, которые изменились по сравнению с приложением MultiMDI.

Функция WinMain

Дополнительно к действиям, выполняемым этой функцией в приложении MultiMDI, новый вариант функции WinMain создает семафор с именем lpSemaphoreName:


hSemaphore = CreateSemaphore(NULL, 2, 2, lpSemaphoreName);
if(hSemaphore == NULL)
{
  MessageBox(NULL, "Ошибка при создании семафора",
    szWindowTitle, MB_OK | MB_ICONEXCLAMATION);
  return FALSE;
}

Начальное и максимальное значение счетчика семафора равно двум, поэтому одновременно будут работать не более двух задач, выполняющих рисование эллипсов в MDI-окнах, созданных пользователем. Если при создании семафора произошла ошибка, на экране появляется сообщение, после чего работа приложения завершается.

После завершения цикла обработки сообщений наше приложение освобождает идентификатор созданного ранее семафора, вызывая для этого функцию CloseHandle XE "CloseHandle" :


CloseHandle(hSemaphore);

Функция FrameWndProc

Эта функция обрабатывает сообщения для главного окна приложения Frame Window, в частности, сообщения, которые поступают от главного меню приложения.

Обработчик сообщения WM_COMMAND XE "WM_COMMAND" получает управление, когда пользователь выбирает строки главного меню приложения.

Обработка сообщений от меню Semaphore

Если из меню Semaphore выбрать строку Increment, содержимое счетчика семафора будет увеличено на единицу:


case ID_SEMAPHORE_INCREMENT:
{
  ReleaseSemaphore(hSemaphore, 1, NULL);
  break;
}

Для этого используется функция ReleaseSemaphore XE "ReleaseSemaphore" .

Обработка сообщений от меню Window

Меню Window в приложении SEMMDI, как и в приложении MultiMDI, предназначено для управления дочерними MDI-окнами. Выбирая строки этого меню, пользователь может упорядочить расположение окон одним из двух способов, упорядочить расположение пиктограмм минимизированных окон, а также закрыть все дочерние MDI-окна.

Функция ChildWndProc

Функция ChildWndProc обрабатывает сообщения, поступающие в дочерние MDI-окна.

Обработка сообщения WM_PAINT

Обработчик сообщения WM_PAINTполучает управление, когда возникает необходимость перерисовать внутреннюю область дочернего MDI-окна. При перерисовке с помощью критической секции выполняется синхронизация главной задачи приложения и задачи дочернего окна.

Если задача, запущенная для дочернего MDI-окна, находится в состоянии ожидания, в окне отображается строка Waiting, а если эта задача работает - строка Child Window. Running. Для выбора нужной строки обработчик сообщения WM_PAINT XE "WM_PAINT" проверяет содержимое поля fWaiting структуры параметров окна типа CHILD_WINDOW_TAG:


if(lpMyWndTag->fWaiting)
  DrawText(hdc, "Waiting...", -1, &rc,
    DT_SINGLELINE | DT_CENTER | DT_VCENTER);
else
  DrawText(hdc, "Child Window. Running", -1, &rc,
    DT_SINGLELINE | DT_CENTER | DT_VCENTER);

Это сообщение поступает в функцию дочернего окна при уничтожении последнего.

Обработчик сообщения WM_CLOSE проверяет флаг ожидания в поле fWaiting структуры параметров окна типа CHILD_WINDOW_TAG. Если задача не находится в состоянии ожидания, обработчик сбрасывает признак активности задачи, в результате чего задача завершает свою работу:


if(lpMyWndTag->fWaiting)
  return 0;
else
  lpMyWndTag->fActive = 0;  

В том случае, когда задача находится в состоянии ожидания, обработчик сообщения WM_CLOSE XE "WM_CLOSE" возвращает нулевое значение, запрещая удаление окна.

Обработка сообщения WM_COMMAND

Сообщение WM_COMMAND поступает в функцию дочернего MDI-окна, когда пользователь выбирает строки плавающего меню.

С помощью плавающего меню вы можете удалить дочернее MDI-окно, завершив работу соответствующей задачи. При этом задача не должна находится в состоянии ожидания семафора:


case ID_THREADCONTROL_CLOSEWINDOW:
{
  lpMyWndTag = (LPCHILD_WINDOW_TAG)GetWindowLong(
            hwnd, GWL_USERDATA);
  if(!lpMyWndTag->fWaiting)
  {
    SendMessage(hwndClient, WM_MDIDESTROY, (WPARAM)hwnd, 0);
    lpMyWndTag->fActive = 0;  
  }
  break;
}

Как видно из приведенного выше фрагмента исходного текста, для определения возможности удаления окна обработчик сообщения WM_COMMAND XE "WM_COMMAND" проверяет флаг ожидания в поле fWaiting структуры параметров окна типа CHILD_WINDOW_TAG.

Функция задачи ThreadRoutine

Задача ThreadRoutine запускается для каждого вновь создаваемого дочернего MDI-окна. Ее функция получает параметр - идентификатор этого дочернего окна, который используется ей для выполнения рисования. Другие параметры, нужные для работы функции задачи ThreadRoutine, извлекаются из структуры типа CHILD_WINDOW_TAG.

Перед началом своей работы функция задачи ThreadRoutine устанавливает флаг ожидания в поле fWaiting структуры типа CHILD_WINDOW_TAG:


lpMyWndTag->fWaiting = TRUE;

Затем она выполняет ожидание семафора, вызывая для этого функцию WaitForSingleObject XE "WaitForSingleObject" :


if(WAIT_FAILED == WaitForSingleObject(hSemaphore, INFINITE))
{
  return 0;
}

Если пользователь создал не более двух окон, вызов функции WaitForSingleObject XE "WaitForSingleObject" не приведет к задержке в выполнении задачи. Если же окон создано больше, задача перейдет в состояние ожидания.

Когда задача вновь возобновит свою работу (в результате того, что пользователь закроет одно из активных окон), флаг ожидания сбрасывается:


lpMyWndTag->fWaiting = FALSE;
[Назад] [Содеожание] [Дальше]