Программирование для 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 В глобальных переменных 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 завершается цикл отображения. |