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

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

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

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

Приложения EVENT и EVENTGEN

В консольных приложениях EVENT и EVENTGEN, исходные тексты которых приведены в этом разделе, мы демонстрируем использование объектов-событий для синхронизации задач, принадлежащих различным процессам.

Первым необходимо запускать приложение EVENT. Его задача заключается в том, чтобы следить за работой второго приложения EVENTGEN. Приложение EVENTGEN позволяет пользователю вводить с помощью клавиатуры и отображать в своем окне произвольные символы. Каждый раз когда пользователь вводит в окне приложения EVENTGEN какой-либо символ, в окне контролирующего приложения EVENT отображается символ ‘*’ (рис. 4.3).

Рис. 4.3. Окна приложений EVENT и EVENTGEN

Так как пока мы не научились еще передавать данные из одного процесса в другой, мы не можем отобаржать в окне приложения EVENT символы, которые пользователь вводит в окне приложения EVENTGEN. Однако пока нам этого и не надо, так как мы здесь демонстрируем лишь способ синхронизации задач.

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

Рассмотрим сначала исходные тексты приложения EVENT, представленные в листинге 4.1.

Листинг 4.1. Файл event/event1.c


#include <windows.h>
#include <stdio.h>
#include <conio.h>

// Идентификаторы объектов-событий, которые используются
// для синхронизации задач, принадлежащих разным процессам
HANDLE hEvent;
HANDLE hEventTermination;

// Имя объекта-события для синхронизации ввода и отображения
CHAR lpEventName[] = 
  "$MyVerySpecialEventName$";

// Имя объекта-события для завершения процесса
CHAR lpEventTerminationName[] = 
  "$MyVerySpecialEventTerminationName$";

int main()
{
  DWORD dwRetCode;

  printf("Event demo application, master process\n"
    "(C) A. Frolov, 1996, Email: frolov@glas.apc.org\n");
  
  // Создаем объект-событие для синхронизации 
  // ввода и отображения, выполняемого в разных процессах
  hEvent = CreateEvent(NULL, FALSE, FALSE, lpEventName);
  
  // Если произошла ошибка, получаем и отображаем ее код,
  // а затем завершаем работу приложения
  if(hEvent == NULL)
  {
    fprintf(stdout,"CreateEvent: Error %ld\n", 
      GetLastError());
    getch();
    return 0;
  }

  // Если объект-событие с указанным именем существует,
  // считаем, что приложение EVENT уже было запущено
  if(GetLastError() == ERROR_ALREADY_EXISTS)
  {
    printf("\nApplication EVENT already started\n"
      "Press any key to exit...");
    getch();
    return 0;
  }

  // Создаем объект-событие для определения момента
  // завершения работы процесса ввода
  hEventTermination = CreateEvent(NULL, 
    FALSE, FALSE, lpEventTerminationName);

  if(hEventTermination == NULL)
  {
    fprintf(stdout,"CreateEvent (Termination): Error %ld\n", 
      GetLastError());
    getch();
    return 0;
  }
  
  // Цикл отображения. Этот цикл завершает свою работу
  // при завершении процесса ввода
  while(TRUE)
  {
    // Проверяем состояние объекта-события, отвечающего
    // за контроль завершения процесса ввода. Так как
    // указано нулевое время ожидания, такая проверка
    // не уменьшает заметно скорость работы приложения
    dwRetCode = WaitForSingleObject(hEventTermination, 0);

    // Если объект-событие перешел в отмеченное состояние,
    // если процесс ввода завершил свою работу, или
    // если при ожидании произошла ошибка,
    // останавливаем цикл отображения
    if(dwRetCode == WAIT_OBJECT_0  || 
       dwRetCode == WAIT_ABANDONED ||
       dwRetCode == WAIT_FAILED)
      break;

    // Выполняем ожидание ввода символа в процессе,
    // который работает с клавиатурой
    dwRetCode = WaitForSingleObject(hEvent, INFINITE);
    
    // При возникновении ошибки прерываем цикл
    if(dwRetCode == WAIT_FAILED || 
       dwRetCode == WAIT_ABANDONED)
      break;

    // В ответ на каждый символ, введенный процессом, который
    // работает с клавиатурой, отображаем символ '*'
    putch('*');
  }
 
  // Закрываем идентификаторы объектов-событий  
  CloseHandle(hEvent);
  CloseHandle(hEventTermination);

  return 0;
}

В глобальных переменных lpEventName и lpEventTerminationName мы храним имена двух объектов-событий, которые будут использоваться для синхронизации. Эти же имена будут указаны функции OpenEvent XE "OpenEvent" в приложении EVENTGEN, которое мы рассмотрим чуть позже.

Объекты-события создаются нашим приложением при помощи функции CreateEvent, вызываемой следующим способом:


hEvent = CreateEvent(NULL, FALSE, FALSE, lpEventName);
hEventTermination = CreateEvent(NULL, FALSE, FALSE, 
  lpEventTerminationName);

Здесь в качестве атрибутов защиты мы указываем значение NULL. Через второй и третий параметр функции CreateEvent XE "CreateEvent" имеют значение FALSE, поэтому создается автоматический объект-событие, которое изначально находится в неотмеченном состоянии. Имя этого события, доступное всем запущенным приложениям, передается функции CreateEvent через последний параметр.

Обратите внимание на следующий фрагмент кода, который расположен сразу после вызова функции, создающей объект-событие с именем lpEventName:


if(GetLastError() == ERROR_ALREADY_EXISTS)
{
  printf("\nApplication EVENT already started\n"
    "Press any key to exit...");
  getch();
  return 0;
}

Функция CreateEvent может завершиться с ошибкой и в этом случае она возвращает значение NULL. Однако пользователь может попытаться запустить приложение EVENT два раза. В первый раз при этом будет создан объект-событие с именем lpEventName. Когда же функция CreateEvent будет вызвана для этого имени еще раз при попытке повторного запуска приложения, она не вернет признак ошибки, несмотря на то что объект-событие с таким именем уже существует в системе. Вместо этого функция вернет идентификатор для уже существующего объекта-события.

Как распознать такую ситуацию?

Очень просто - достаточно сразу после вызова функции CreateEvent проверить код завершения при помощи функции GetLastError XE "GetLastError" . Как мы уже говорили, в случае попытки создания объекта-события с уже существующим в системе именем эта функция вернет значение ERROR_ALREADY_EXISTS XE "ERROR_ALREADY_EXISTS" . Как только это произойдет, наше приложение выдает сообщение о том, что его копия уже запущена, после чего завершает свою работу.

Таким образом мы нашли еще один способ (далеко не последний), с помощью которого в операционной системе Microsoft Windows NT можно предотвратить повторный запуск приложений. До сих пор мы решали эту задачу в графических приложениях с помощью функции FindWindow.

Продолжим изучение исходный текстов приложения EVENT.

После того как приложение создаст два объекта-события, оно входит в цикл отобаржения символа ‘*’.

В этом цикле прежде всего проверяется состояние объекта-события с идентификатором hEventTermination:


dwRetCode = WaitForSingleObject(hEventTermination, 0);
if(dwRetCode == WAIT_OBJECT_0  || 
   dwRetCode == WAIT_ABANDONED ||
   dwRetCode == WAIT_FAILED)
  break;

Приложение EVENTGEN переводит этот объект в отмеченное состояние перед своим завершением.

Обращаем ваше внимание на то что функции WaitForSingleObject указано нулевое время ожидания. В этом случае функция проверяет состояние объекта и сразу же возвращает управление.

Далее выполняется ожидание объекта-события с идентификатором hEvent:


dwRetCode = WaitForSingleObject(hEvent, INFINITE);
if(dwRetCode == WAIT_FAILED || 
   dwRetCode == WAIT_ABANDONED)
  break;

Если это ожидание завершилось с ошибкой, выполняется выход из цикла. Если же оно было выполнено без ошибок, приложение EVENT отображает символ ‘*’, пользуясь для этого функцией putch, известной вам из практики программирования для MS-DOS.

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


CloseHandle(hEvent);
CloseHandle(hEventTermination);

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

Исходные тексты приложения EVENTGEN, которое работает в паре с только что рассмотренным приложением EVENT, приведены в листинге 4.2.

Листинг 4.2. Файл event/eventgen/event2.c


#include <windows.h>
#include <stdio.h>
#include <conio.h>

// Идентификаторы объектов-событий, которые используются
// для синхронизации задач, принадлежащих разным процессам
HANDLE hEvent;
HANDLE hEventTermination;

// Имя объекта-события для синхронизации ввода и отображения
CHAR lpEventName[] = 
  "$MyVerySpecialEventName$";

// Имя объекта-события для завершения процесса
CHAR lpEventTerminationName[] = 
  "$MyVerySpecialEventTerminationName$";

int main()
{
  CHAR chr;

  printf("Event demo application, slave process\n"
    "(C) A. Frolov, 1996, Email: frolov@glas.apc.org\n"
    "\n\nPress  to terminate...\n");
  
  // Открываем объект-событие для синхронизации 
  // ввода и отображения
  hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, lpEventName);
  
  if(hEvent == NULL)
  {
    fprintf(stdout,"OpenEvent: Error %ld\n", 
      GetLastError());
    getch();
    return 0;
  }

  // Открываем объект-событие для сигнализации о
  // завершении процесса ввода
  hEventTermination = OpenEvent(EVENT_ALL_ACCESS, 
    FALSE, lpEventTerminationName);
  
  if(hEventTermination == NULL)
  {
    fprintf(stdout,"OpenEvent (Termination): Error %ld\n", 
      GetLastError());
    getch();
    return 0;
  }

  // Цикл ввода. Этот цикл завершает свою работу,
  // когда пользователь нажимает клавишу <ESC>, 
  // имеющую код 27
  while(TRUE)
  {
    // Проверяем код введенной клавиши
    chr = getche();
    
    // Если нажали клавишу <ESC>, прерываем цикл
    if(chr == 27)
      break;

    // Устанавливаем объект-событие в отмеченное
    // состояние. В ответ на это процесс отображения
    // выведет на свою консоль символ '*'
    SetEvent(hEvent);
  }
 
  // После завершения цикла переключаем оба события
  // в отмеченное состояние для отмены ожидания в
  // процессе отображения и для завершения этого процесса
  SetEvent(hEvent);
  SetEvent(hEventTermination);
  
  // Закрываем идентификаторы объектов-событий
  CloseHandle(hEvent);
  CloseHandle(hEventTermination);

  return 0;
}

В глобальных переменных lpEventName и lpEventTerminationName записаны имена объектов-событий, которые в точности такие же, как и в приложении EVENT.

Функция main приложения EVENTGEN прежде всего открывает объекты-события, созданные приложением EVENT. Для этого она вызывает функцию OpenEvent XE "OpenEvent" , как это показано ниже:


hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, lpEventName);
hEventTermination = OpenEvent(EVENT_ALL_ACCESS, 
  FALSE, lpEventTerminationName);

Если пользователь случайно запустил первым приложение EVENTGEN, первый из только что приведенных вызовов функции OpenEvent XE "OpenEvent" закончится с ошибкой, так как объекты-события с данными именами неизвестны системе. При этом работа приложения EVENTGEN завершается аварийно с выдачей соответствующего сообщения.

Далее функция main приложения EVENTGEN входит в цикл, в котором она выполняет ввод символов с клавиатуры. Этот цикл прерывается, если пользователь нажимает клавишу <ESC>.

После ввода каждого символа приложение EVENTGEN устанавливает объект-событие hEvent в отмеченное состояние, вызывая для этого функцию SetEvent XE "SetEvent" :


SetEvent(hEvent);

При этом приложение EVENT выходит из состояния ожидания и отображает в своем окне один символ ‘*’. Так как объект-событие работает в автоматическом режиме, после выхода главной задачи приложения EVENT из состояния ожидания объект-событие hEvent автоматически сбрасывается в неотмеченное состояние. В результате после отображения одного символа ‘*’ приложение EVENT вновь переводится в состояние ожидания до тех пор, пока пользователь не нажмет какую-либо клавишу в окне приложения EVENTGEN.

После того как пользователь нажал клавишу <ESC>, оба объекта-события переключаются в отмеченное состояние:


SetEvent(hEvent);
SetEvent(hEventTermination);

Это приводит к тому что в приложении EVENT завершается цикл отображения.

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