Здесь мы создаем таблицу точек входа сервиса.
Структура типа SERVICE_TABLE_ENTRY позволяет задать функцию ServiceMain()
для сервиса, носящего указанное в структуре имя.
Функция StartServiceCtrlDispatcher()
связывает главный поток сервиса с менеджером сервисов (service control
manager, SCM).
Когда SCM запускает сервис, он ожидает выполнения сервисом функции StartServiceCtrlDispatcher().
Главный поток сервиса должен вызвать эту функцию как можно быстрее.
Если функция выполняется успешно, она связывает вызывающий ее поток с
SCM и не завершается, пока не будет остановлен сервис. SCM использует
это соединение, чтобы посылать сервису управляющие запросы.
Если сервис запускается как отдельный процесс (как, например, в нашем
случае), он должен вызвать функцию StartServiceCtrlDispatcher()
немедленно. Поэтому всю инициализацию следует делать позже, в функции ServiceMain(). Если в
рамках одного процесса запускается несколько сервисов, главный поток
должен вызвать функцию StartServiceCtrlDispatcher()
не позднее, чем через 30 секунд.
Функция
ServiceMain()
Функция ServiceMain()
определяется процессом в качестве точки входа для данного сервиса. Эта
функция может носить любое имя, заданное приложением.
Аргументы функции ServiceMain()
(аналогичны аргументам функции main()): dwArgc - количество аргументов, lpszArgv - указатель на массив,
который содержит указатели на строковые аргументы функции.
Функция RegisterServiceCtrlHandler()
регистрирует функцию, которая будет обрабатывать управляющие запросы к
сервису (такие, например, как остановка сервиса). В случае успешного
завершения функция возвращает дескриптор статуса сервиса (service status
handle). При неудачном завершении функция возвращает нулевое значение.
//заполняем структуру, определяющую состояние сервиса: //сервис выполняется как отдельный процесс ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS; //устанавливаем состояние ожидания запуска сервиса SetSomeServiceStatus(SERVICE_START_PENDING, NO_ERROR, 4000);
//инициализация для SomeService InitSomeServiceData(argc, argv);
Поле dwServiceType структуры
ss устанавливаем в SERVICE_WIN32_OWN_PROCESS. Это означает, что сервис
будет выполняться как отдельный процесс, т.е. будет иметь собственное
адресное пространство.
Затем устанавливаем состояние ожидания запуска сервиса c помощью
созданной нами вспомогательной функции SetSomeServiceStatus().
Эта функция изменяет содержимое структуры ss, которое использует SCM
для получения информации о сервисе.
Далее можно выполнить всю необходимую инициализацию для сервиса
(определяемая пользователем функция
InitSomeServiceData()).
Когда инициализация выполнена, вызываем функцию SetSomeServiceStatus() с параметром
SERVICE_RUNNING, чтобы установить состояние работающего сервиса.
После изменения состояния сервиса выполняется основной код программы.
Функция
ServiceControl()
Эта функция является управляющей функцией сервиса и может носить любое
имя, определенное приложением.
void WINAPI ServiceControl(DWORD dwControlCode) { //анализируем код команды и выполняем ее switch(dwControlCode) { //команда остановки сервиса case SERVICE_CONTROL_STOP: { //устанавливаем состояние ожидания остановки SetSomeServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
С помощью функции SetSomeServiceStatus()
мы устанавливаем состояние ожидания остановки сервиса, после чего можно
вызвать определяемую пользователем функцию StopSomeService(), которая выполнит
все необходимые действия перед остановкой сервиса. Содержимое этой
функции зависит от конкретного сервиса.
Далее с помощью функции SetSomeServiceStatus()
сообщаем SCM, что сервис остановлен.
Когда сервис получает команду SERVICE_CONTROL_INTERROGATE, это
означает, что он должен немедленно обновить информацию о статусе
сервиса, используемую SCM.
Функция
SetSomeServiceStatus()
Эта функция изменяет содержимое структуры ss, которое использует SCM
для получения информации о статусе сервиса.
//если сервис не находится в процессе запуска, его можно остановить if(dwCurrentState == SERVICE_START_PENDING) ss.dwControlsAccepted = 0; else ss.dwControlsAccepted = SERVICE_ACCEPT_STOP;
//если сервис не работает и не остановлен, увеличиваем значение счетчика //шагов длительных операций if(dwCurrentState == SERVICE_RUNNING || dwCurrentState == SERVICE_STOPPED) ss.dwCheckPoint = 0; else ss.dwCheckPoint = dwCheckPoint++;
//обновить информацию о сервисе SetServiceStatus(ssHandle, &ss); }
Значение поля dwCheckPoint
следует периодически изменять во время длительного запуска или
остановки. Например, если при запуске сервиса инициализация выполняется
в несколько этапов, то нужно увеличивать значение dwCheckPoint после каждого этапа.
При этом программа, которая запросила услуги сервиса, может узнать,
какой объем работы уже выполнен сервисом. Поле dwCheckPoint должно быть равно нулю,
когда сервис не находится в состоянии запуска, остановки или выполнения
длительной операции.
Поле dwControlsAccepted
определяет управляющий код, который может принимать и обрабатывать
данный сервис. По умолчанию все сервисы могут принимать команду
SERVICE_CONTROL_INTERROGATE. Если это поле имеет значение
SERVICE_ACCEPT_STOP, то сервис может быть остановлен с помощью команды
SERVICE_CONTROL_STOP.
Функции
InitSomeServiceData() и StopSomeService()
BOOL InitSomeServiceData() { //... return TRUE; }
BOOL StopSomeService() { //... return TRUE; }
С помощью этих функций проводится инициализация, необходимая для
данного сервиса, а также действия, которые необходимо выполнить перед
остановкой сервиса. Содержимое этих функций зависит от конкретного
сервиса.
Приложение,
управляющее сервисом
Приложение SomeSrvcApp может инсталлировать сервис в систему, запускать
и останавливать его, получать информацию о сервисе и удалять сервис из
системы.
Здесь не будут описываться функции WinMain()
и InitApp(), поскольку они не
обладают никакими особенностями, заслуживающими внимания. Функция WinMain() создает главное окно с
шестью кнопками: "Install Service", "Start Service", "Get Service
Configuration", "Stop Service", "Remove Service" и "Exit".
Функция WndProc()
В функции WndProc() используются макросы для обработки сообщений
WM_COMMAND и WM_DESTROY:
Функция WndProcOnCommand()
вызывается функцией WndProc(),
если окно получает сообщение WM_COMMAND. Функция WndProcOnCommand() содержит код,
выполняющийся при нажатии на кнопки главного окна приложения.
Приведем полностью код этой функции:
void WndProcOnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify) { SC_HANDLE hService; //дескриптор сервиса SC_HANDLE hSCManager; //дескриптор Service Control Manager LPQUERY_SERVICE_CONFIG lpBufConfig; DWORD dwBytesNeeded; char szBufConfig[4096];
Здесь мы отрываем базу данных Service Control Manager (SCM) с помощью
функции OpenSCManager()
с уровнем доступа SC_MANAGER_CREATE_SERVICE, который позволяет создавать
сервисы.
Для создания сервиса используется функция CreateService(), которая возвращает
дескриптор созданного сервиса или NULL в случае неудачного выполнения.
Одним из параметров функции является путь к сервису, который должен
соответствовать расположению сервиса в Вашем компьютере. Проследите,
чтобы путь был указан правильно, иначе создать сервис не получится.
В качестве типа сервиса мы указали SERVICE_WIN32_OWN_PROCESS. Это
означает, что сервис будет выполняться как отдельный процесс. При
успешном завершении функция CreateService()
добавляет сервис в базу данных SCM. Вы можете в этом убедиться, открыв
системную панель управления сервисами (Панель управления ->
Администрирование -> Службы).
Для открытия сервиса мы используем функцию OpenService(). Третьим параметром
этой функции является тип доступа к сервису. Указывая тип доступа
SERVICE_START, мы получаем возможность запускать сервис.
Запуск сервиса осуществляется с помощью функции StartService(), которая возвращает
нулевое значение, если запустить сервис не удалось.
На этот раз, открывая сервис, мы указываем тип доступа
SERVICE_QUERY_CONFIG, чтобы получить информацию о конфигурации сервиса.
Эту информацию мы помещаем в структуру типа QUERY_SERVICE_CONFIG (через
указатель на эту структуру lpBufConfig, используя динамически выделяемую
память) с помощью функции QueryServiceConfig().
Функция QueryServiceConfig()
помещает в структуру типа QUERY_SERVICE_CONFIG информацию о сервисе,
которая находится в реестре. Эта информация была помещена в реестр
функцией CreateService().
Далее мы помещаем содержимое некоторых полей структуры в буфер
szBufConfig и выводим его содержимое с помощью функции MessageBox().
Здесь мы открываем сервис, используя уровень доступа SERVICE_STOP.
Для остановки сервиса используется функция ControlService(). Эта функция
посылает сервису управляющий код, который она получает в качестве
второго параметра. В нашем случае управляющий код равен константе
SERVICE_CONTROL_STOP. Третьим параметром функции является адрес
структуры типа SERVICE_STATUS, в которой содержится информация о текущем
статусе сервиса. Содержимое этой структуры использует SCM. Если сервис
остановить не удалось функция ControlService()
возвращает нулевое значение.
//удаление сервиса из системы case IDB_REMOVE: { hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if(!hSCManager) { MessageBox(NULL, "Can't open Service Control Manager", "Error", MB_OK | MB_ICONERROR); break; } hService = OpenService(hSCManager, SomeServiceName, SERVICE_STOP | DELETE); if(!hService) { GetSomeSvcError(); break; } if(ss.dwCurrentState != SERVICE_STOPPED) ControlService(hService, SERVICE_CONTROL_STOP, &ss); //удаляем сервис из системы DeleteService(hService); CloseServiceHandle(hService); CloseServiceHandle(hSCManager); break; }
В этом случае, открывая сервис, мы используем комбинацию флагов
SERVICE_STOP и DELETE, поскольку если сервис в данный момент работает,
то прежде чем его удалять из системы, его необходимо остановить. Для
удаления сервиса из системы используется функция DeleteService().
//завершаем работу приложения case IDB_EXIT: { PostQuitMessage(0); return; } default: break; } return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify, DefWindowProc); }
Функции
WndProcOnDestroy() и GetSomeSvcError()
Приведем код функции WndProcOnDestroy()
и функции GetSomeSvcError(),
которая используется для вывода сообщений об ошибках:
void GetSomeSvcError() { DWORD dwError = GetLastError(); char errMess[100]; switch(dwError) { case ERROR_ACCESS_DENIED: { sprintf(errMess, "The specified service control manager"\ "database handle does not have access to"\ "the service."); break; } case ERROR_INVALID_HANDLE: { sprintf(errMess, "The specified handle is invalid."); break; } case ERROR_INVALID_NAME: { sprintf(errMess, "The specified service name is invalid."); break; } case ERROR_SERVICE_DOES_NOT_EXIST: { sprintf(errMess, "The specified service does not exist."); break; } default: { sprintf(errMess, "Can't open service.\nError number not found."); } } MessageBox(NULL, errMess, "Error", MB_OK | MB_ICONERROR); return; }
В функции GetSomeSvcError()
анализируется код ошибки, полученный с помощью функции Win API GetLastError(). Полный
список кодов ошибок и их значений можно посмотреть в файле WinError.h
или в документации по Win API.