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

Программирование для 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. Эта функция будет описана в следующем разделе.

Через единственный параметр функция обработки команд получает код команды, который может принимать одно из перечисленных ниже значений.

 Значение

 Описание

 SERVICE_CONTROL_STOP

 Остановка сервиса

 SERVICE_CONTROL_PAUSE

 Временная остановка сервиса

 SERVICE_CONTROL_CONTINUE

 Продолжение работы сервиса после временной остановки

 SERVICE_CONTROL_INTERROGATE

 Когда поступает эта команда, сервис должен немедленно сообщить процессу управления сервисами свое состояние

 SERVICE_CONTROL_SHUTDOWN

 Сервис должен прекратить работу в течении 20 секунд, так как завершается работа операционной системы

Состояние сервиса

Как мы уже говорили, сервис может сообщить процессу управления сервисами свое состояние, для чего он должен вызвать функцию 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 необходимо записать один из перечисленных ниже флагов, определяющих тип сервиса:

 Флаг

 Описание

 SERVICE_WIN32_OWN_PROCESS

 Сервис работает как отдельный процесс

 SERVICE_WIN32_SHARE_PROCESS

 Сервис работает вместе с другими сервисами в рамках одного и того же процесса

 SERVICE_KERNEL_DRIVER

 Сервис представляет собой драйвер операционной системы Microsoft Windows NT

 SERVICE_FILE_SYSTEM_DRIVER

 Сервис является драйвером файловой системы

 SERVICE_INTERACTIVE_PROCESS

 Сервисный процесс может взаимодействовать с программным интерфейсом рабочего стола Desktop

В поле dwCurrentState вы должны записать текущее состояние сервиса. Здесь можно использовать одну из перечисленных ниже констант:

 Константа

 Состояние сервиса

 SERVICE_STOPPED

 Сервис остановлен

 SERVICE_START_PENDING

 Сервис находится в состоянии запуска, но еще не работает

 SERVICE_STOP_PENDING

 Сервис находится в состоянии остановки, но еще не остановился

 SERVICE_RUNNING

 Сервис работает

 SERVICE_CONTINUE_PENDING

 Сервис начинает запускаться после временной остановки, но еще не работает

 SERVICE_PAUSE_PENDING

 Сервис начинает переход в состояние временной остановки, но еще не остановился

 SERVICE_PAUSED

 Сервис находится в состоянии верменной остановки

Задавая различные значения в поле dwControlsAccepted, вы можете указать, какие команды обрабатывает сервис. Ниже приведен список возможных значений:

 Значение

 Команды,, которые может воспринимать сервис

 SERVICE_ACCEPT_STOP

 Команда остановки сервиса SERVICE_CONTROL_STOP

 SERVICE_ACCEPT_PAUSE_CONTINUE

 Команды временной остановки SERVICE_CONTROL_PAUSE и продолжения работы после временной остановки SERVICE_CONTROL_CONTINUE

 SERVICE_ACCEPT_SHUTDOWN

 Команда остановки при завершении работы операционной системы SERVICE_CONTROL_SHUTDOWN

Значение в поле 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, которые будут описаны ниже.

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