Программирование для Windows NT© Александр Фролов, Григорий ФроловТом 27, часть 2, М.: Диалог-МИФИ, 1996, 272 стр. Создание сервисного процессаДля того чтобы создать загрузочный модуль сервиса, вы должны подготовить исходные тексты обычного консольного приложения, имеющего функцию main (не WinMain, а именно main). Функция main сервисного процессаВ простейшем случае функция main вызывает функцию StartServiceCtrlDispatcher, что необходимо для подключения главной задачи сервисного процесса к процессу управления сервисами. Ниже мы привели пример функции main сервисного процесса: #define MYServiceName "Sample of simple service" void main(int agrc, char *argv[]) { SERVICE_TABLE_ENTRY DispatcherTable[] = { { MYServiceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain }, { NULL, NULL } }; if(!StartServiceCtrlDispatcher(DispatcherTable)) { fprintf(stdout,"StartServiceCtrlDispatcher: Error %ld\n", GetLastError()); getch(); return; } } Функции StartServiceCtrlDispatcher передается указатель на массив структур типа SERVICE_TABLE_ENTRY. В этом массиве описываются точки входа всех сервисов, определенных в данном файле. Таким образом, в одном файле можно определить сразу несколько сервисов. Последняя строка таблицы всегда должна содержать значения NULL - это признак конца таблицы. Тип SERVICE_TABLE_ENTRY и соответствующий указатель определены следующим образом: typedef struct _SERVICE_TABLE_ENTRY { LPTSTR lpServiceName; LPSERVICE_MAIN_FUNCTION lpServiceProc; } SERVICE_TABLE_ENTRY, *LPSERVICE_TABLE_ENTRY; В поле lpServiceName записывается указатель на текстовую строку имени сервиса, а в поле lpServiceProc - указатель на точку входа сервиса. Заметим, что функция main должна вызвать функцию StartServiceCtrlDispatcher достаточно быстро - не позднее чем через 30 секунд после запуска. Получив управление, функция StartServiceCtrlDispatcher не возвращает его до тех пор, пока все сервисы, запущенные в рамках данного процесса, не завершат свою работу. При успешном завершении функция StartServiceCtrlDispatcher возвращает значение TRUE. Если же произойдет ошибка, возвращается значение FALSE. Код ошибки можно определить при помощи функции GetLastError. Точка входа сервисаТочка входа сервиса - это функция, адрес которой записывается в поле lpServiceProc массива структур SERVICE_TABLE_ENTRY. Имя функции может быть любым, а прототип должен быть таким, как показанный ниже: void WINAPI ServiceMain(DWORD dwArgc, LPSTR *lpszArgv); Точка входа сервиса вызывается при запуске сервиса функцией StartService (эту функцию мы рассмотрим позже). Через параметр dwArgc передается счетчик аргументов, а через параметр lpszArgv - указатель на массив строк параметров. В качестве первого параметра всегда передается имя сервиса. Остальные параметры можно задать при запуске сервиса функцией StartService. Функция точки входа сервиса должна зарегистрировать функцию обработки команд и выполнить инициализацию сервиса. Первая задача решается с помощью функции RegisterServiceCtrlHandler, прототип которой приведен ниже: SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler( LPCTSTR lpszServiceName, // имя сервиса LPHANDLER_FUNCTION lpHandlerProc); // адрес функции // обработки команд Через первый параметр этой функции необходимо передать адрес текстовой строки имени сервиса, а через второй - адрес функции обработки команд (функция обработки команд будет рассмотрена ниже). Вот пример использования функции RegisterServiceCtrlHandler: SERVICE_STATUS_HANDLE ssHandle; ssHandle = RegisterServiceCtrlHandler(MYServiceName, ServiceControl); Функция RegisterServiceCtrlHandler в случае успешного завершения возвращает идентификатор состояния сервиса. При ошибке возвращается нулевое значение. Заметим, что регистрация функции обработки команд должна быть выполнена немедленно в самом начале работы функции точки входа сервиса. Теперь перейдем к решению второй задачи - инициализации сервиса. В процессе инициализации функция точки входа сервиса выполняет действия, которые зависят от назначения сервиса. Необходимо, однако, помнить, что без принятия дополнительных мер инициализация должна выполняться не дольше одной секунды. А что делать, если инициализация сервиса представляет собой достаточно длительный процесс? В этом случае перед началом инициализации функция точки входа сервиса должна сообщить процессу управления сервисами, что данный сервис находится в состоянии ожидания запуска. Это можно сделать с помощью функции SetServiceStatus, которая будет описана позже. Перед началом инициализации вы должны сообщить процессу управления сервисами, что сервис находится в состоянии SERVICE_START_PENDING. После завершения инициализации функция точки входа сервиса должна указать процессу управления сервисами, что процесс запущен и находится в состоянии SERVICE_RUNNING. Функция обработки командКак следует из названия, функция обработки команд, зарегистрированная функцией RegisterServiceCtrlHandler, обрабатывает команды, передаваемые сервису операционной системой, другими сервисами или приложениями. Эта функция может иметь любое имя и выглядит следующим образом: void WINAPI ServiceControl(DWORD dwControlCode) { switch(dwControlCode) { case SERVICE_CONTROL_STOP: { ss.dwCurrentState = SERVICE_STOP_PENDING; ReportStatus(ss.dwCurrentState, NOERROR, 0); // Выполняем остановку сервиса, вызывая функцию, // которая выполняет все необходимые для этого действия // ServiceStop(); ReportStatus(SERVICE_STOPPED, NOERROR, 0); break; } case SERVICE_CONTROL_INTERROGATE: { ReportStatus(ss.dwCurrentState, NOERROR, 0); break; } default: { ReportStatus(ss.dwCurrentState, NOERROR, 0); break; } } } В приведенном выше фрагменте кода для сообщения процессу управления сервисами текущего состояния сервиса мы вызываем созданную нами функцию ReportStatus. Эта функция будет описана в следующем разделе. Через единственный параметр функция обработки команд получает код команды, который может принимать одно из перечисленных ниже значений.
Состояние сервисаКак мы уже говорили, сервис может сообщить процессу управления сервисами свое состояние, для чего он должен вызвать функцию SetServiceStatus. Прототип этой функции мы привели ниже: BOOL SetServiceStatus( SERVICE_STATUS_HANDLE sshServiceStatus, // идентификатор // состояния сервиса LPSERVICE_STATUS lpssServiceStatus); // адрес структуры, // содержащей состояние сервиса Через параметр sshServiceStatus функции SetServiceStatus вы должны передать идентификатор состояния сервиса, полученный от функции RegisterServiceCtrlHandler. В параметре lpssServiceStatus вы должны передать адрес предварительно заполненной структуры типа SERVICE_STATUS: typedef struct _SERVICE_STATUS { DWORD dwServiceType; // тип сервиса DWORD dwCurrentState; // текущее состояние сервиса DWORD dwControlsAccepted; // обрабатываемые команды DWORD dwWin32ExitCode; // код ошибки при запуске // и остановке сервиса DWORD dwServiceSpecificExitCode; // специфический код ошибки DWORD dwCheckPoint; // контрольная точка при // выполнении длительных операций DWORD dwWaitHint; // время ожидания } SERVICE_STATUS, *LPSERVICE_STATUS; В поле dwServiceType необходимо записать один из перечисленных ниже флагов, определяющих тип сервиса:
В поле dwCurrentState вы должны записать текущее состояние сервиса. Здесь можно использовать одну из перечисленных ниже констант:
Задавая различные значения в поле dwControlsAccepted, вы можете указать, какие команды обрабатывает сервис. Ниже приведен список возможных значений:
Значение в поле dwWin32ExitCode определяет код ошибки WIN32, который используется для сообщения о возникновении ошибочной ситуации при запуске и остановки сервиса. Если в этом поле указать значение ERROR_SERVICE_SPECIFIC_ERROR, то будет использован специфический для данного сервиса код ошибки, указанной в поле dwServiceSpecificExitCode структуры SERVICE_STATUS. Если ошибки нет, в поле dwWin32ExitCode необходимо записать значение NO_ERROR. Поле dwServiceSpecificExitCode используется в том случае, когда в поле dwWin32ExitCode указано значение ERROR_SERVICE_SPECIFIC_ERROR. Теперь о поле dwCheckPoint. Это поле должно содержать значение, которое должно периодически увеличиваться при выполнении длительных операций запуска, остановки или продолжения работы после временной остановки. Если выполняются другие операции, в это поле необходимо записать нулевой значение. Содержимое поля dwWaitHint определяет ожидаемое время выполнения (в миллисекундах) длительной операции запуска, остановки или продолжения работы после временной остановки. Если за указанное время не изменится содержимое полей dwCheckPoint или dwCurrentState, процесс управления сервисами будет считать, что произошла ошибка. В наших примерах для сообщения текущего состояния сервиса процессу управления сервисами мы используем функцию ReportStatus, исходный текст которой приведен ниже: void ReportStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint) { static DWORD dwCheckPoint = 1; if(dwCurrentState == SERVICE_START_PENDING) ss.dwControlsAccepted = 0; else ss.dwControlsAccepted = SERVICE_ACCEPT_STOP; ss.dwCurrentState = dwCurrentState; ss.dwWin32ExitCode = dwWin32ExitCode; ss.dwWaitHint = dwWaitHint; if((dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED)) ss.dwCheckPoint = 0; else ss.dwCheckPoint = dwCheckPoint++; SetServiceStatus(ssHandle, &ss); } При заполнении структуры SERVICE_STATUS эта функция проверяет содержимое поля dwCurrentState. Если сервис находится в состоянии ожидания запуска, в поле допустимых команд dwControlsAccepted записывается нулевое значение. В противном случае функция записывает туда значение SERVICE_ACCEPT_STOP, в результате чего сервису может быть передана команда остановки. Далее функция заполняет поля dwCurrentState, dwWin32ExitCode и dwWaitHint значениями, полученными через параметры. В том случае, когда сервис выполняет команды запуска или остановки, функция увеличивает значение счетчика шагов длительных операций dwCheckPoint. Текущее значение счетчика хранится в статической переменной dwCheckPoint, определенной в нашей функции. После подготовки структуры SERVICE_STATUS ее адрес передается функции установки состояния сервиса SetServiceStatus. Для определения текущего состояния сервиса вы можете использовать функцию QueryServiceStatus, прототип которой приведен ниже: BOOL QueryServiceStatus( SC_HANDLE schService, // идентификатор сервиса LPSERVICE_STATUS lpssServiceStatus); // адрес структуры // SERVICE_STATUS Идентификатор сервиса вы можете получить от функций OpenService или CreateService, которые будут описаны ниже. |