]>Службы Windows (Windows Service)

Службы Windows (Windows Service)

Аватар пользователя mabu

Как правило, служба Windows — это приложение, предназначенное для работы без взаимодействия с пользователем. Службы выполняются в отдельной оконной станции, поэтому пользовательский ввод с клавиатуры, мыши или консоли им недоступен.

Все службы запускаются диспетчером управления служб — программой services.exe. Службы могут быть однопоточными — по одной службе на один исполняемый файл, и многопоточными — больше одной службы на один исполняемый файл, каждая выполняется в своём потоке.

  1. Функции
    1. StartServiceCtrlDispatcher
    2. RegisterServiceCtrlHandler
    3. SetServiceStatus
  2. Пример

Функции

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

И вспомогательные функции для работы службы:

StartServiceCtrlDispatcher

Сообщает диспетчеру служб о готовности работать в качестве службы. Эта функция должна быть вызвана как можно скорее после запуска программы, иначе диспетчер сочтёт, что служба зависла и завершит её.

Код FreeBASIC
Declare Function StartServiceCtrlDispatcher Alias "StartServiceCtrlDispatcherW"( _
&t;ByVal lpServiceStartTable As Const SERVICE_TABLE_ENTRYW Ptr) _
As WINBOOL

Параметры

lpServiceStartTable
Указатель на массив структур SERVICE_TABLE_ENTRY, содержащих данные об именах служб и точек входа.

Возвращаемое значение

Если диспетчер принял данные о запуске служб, то возвращаемое значение — не ноль.

Если произошла ошибка, то возвращаемое значение — ноль. Для дополнительных сведений об ошибке можно вызвать функцию GetLastError.

Вот типичные коды ошибок:

Замечания

После вызова функции StartServiceCtrlDispatcher диспетчер возвратит управление только тогда, когда все службы, указанные в массиве SERVICE_TABLE_ENTRY будут остановлены, или в случае ошибки.

Структура SERVICE_TABLE_ENTRY

Код FreeBASIC
Type SERVICE_TABLE_ENTRY
&t;lpServiceName As LPWSTR
&t;lpServiceProc As LPSERVICE_MAIN_FUNCTIONW
End Type

Члены структуры

lpServiceName
Указатель на строку с именем службы.
lpServiceProc
Указатель на функцию, которую вызовет диспетчер как точку входа в службу.

Точка входа в службу

Точка входа в службу — это обычная процедура, которая должна иметь следующий вид:

Код FreeBASIC
Declare Sub SvcMain( _
&t;ByVal dwNumServicesArgs As DWORD, _
&t;ByVal lpServiceArgVectors As LPWSTR Ptr)

Параметры

dwNumServicesArgs
Количество параметров командной строки + имя службы.
lpServiceArgVectors
Массив указателей на строки. Это параметры командной строки, первым параметром будет идти имя службы.

Возвращаемое значение

Функция не возвращает значений.

Замечания

На самом деле имя функции может быть любым, главное, чтобы параметры соответствовали объявлению.

RegisterServiceCtrlHandler

Регистрирует функцию‐обработчик команд для управления службой. Диспетчер будет вызывать эту функцию всякий раз, чтобы отправить ей специальный код.

Код FreeBASIC
Declare Function RegisterServiceCtrlHandler Alias "RegisterServiceCtrlHandlerW"( _
&t;ByVal lpServiceName As LPCWSTR, _
&t;ByVal lpHandlerProc As LPHANDLER_FUNCTION) _
As SERVICE_STATUS_HANDLE

Параметры

lpServiceName
Указатель на строку с именем службы.
lpHandlerProc
Указатель на функцию, которую вызовет диспетчер когда службе будет отправлен сигнал.

Возвращаемое значение

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

Если функция выдала ошибку, то возвращаемое значение — ноль. Для дополнительной информации об ошибке можно вызвать функцию GetLastError

Вот типичные коды ошибок:

Замечания

Эта функция должна быть вызвана как можно скорее после вызова StartServiceCtrlDispatcher, чтобы диспетчер смог зарегистрировать обработчик сигналов управления службой. Иначе диспетчер сочтёт, что служба зависла и завершит её.

Идентификатор, возвращаемый функцией RegisterServiceCtrlHandler не следует закрывать.

Функция‐обработчик команд диспетчера

Обработчик команд диспетчера должен иметь следующий вид:

Код FreeBASIC
Declare Sub SvcCtrlHandler( _
&t;ByVal dwCtrl As DWORD)

Параметры

dwCtrl
Специальное число, отправляемое диспетчером. Это число может быть одной из следующих констант:
  • SERVICE_CONTROL_INTERROGATE — диспетчер требует у службы текущее состояние, необходимо отправить диспетчеру NO_ERROR функцией SetServiceStatus;
  • SERVICE_CONTROL_CONTINUE — диспетчер запускает службу или снимает с паузы;
  • SERVICE_CONTROL_PAUSE — диспетчер ставит службу на паузу;
  • SERVICE_CONTROL_STOP — диспетчер останавливает службу, необходимо завершить работу службы, тем не менее, это не значит, что диспетчер выгрузит службу из памяти;
  • SERVICE_CONTROL_SHUTDOWN — операционная система завершает работу, служба должна отправить NO_ERROR функцией SetServiceStatus.
Также диспетчер может отправлять пользовательские коды из диапазона от 128 до 255, что с ними делать — это усматривает сам разработчик.

Возвращаемое значение

Функция не возвращает значений.

Замечания

Любая служба обязана уметь обрабатывать код SERVICE_CONTROL_INTERROGATE.

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

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

SetServiceStatus

Сообщает диспетчеру состояние службы.

Код FreeBASIC
Declare Function SetServiceStatus( _
&t;ByVal hServiceStatus As SERVICE_STATUS_HANDLE, _
&t;ByVal lpServiceStatus As LPSERVICE_STATUS) _
As WINBOOL

Параметры

hServiceStatus
Идентификатор службы, возвращённый функцией RegisterServiceCtrlHandler.
lpServiceStatus
Указатель на структуру SERVICE_STATUS.

Возвращаемое значение

Если функция выполняется успешно, то возвращаемое значение — не ноль.

Если функция завершается ошибкой, то возвращаемое значение — ноль.

Замечания

После вызова функции RegisterServiceCtrlHandler необходимо как можно скорее сообщить диспетчеру состояние службы, вызвав функцию SetServiceStatus с кодом SERVICE_START_PENDING. Иначе диспетчер сочтёт службу зависшей и уничтожит.

Также о любом изменении состояния службы (запущена, остановлена, поставлена на паузу) необходимо извещать диспетчер.

Структура SERVICE_STATUS

Код FreeBASIC
Type SERVICE_STATUS
&t;dwServiceType As DWORD
&t;dwCurrentState As DWORD
&t;dwControlsAccepted As DWORD
&t;dwWin32ExitCode As DWORD
&t;dwServiceSpecificExitCode As DWORD
&t;dwCheckPoint As DWORD
&t;dwWaitHint As DWORD
End Type

Члены структуры:

dwServiceType
Тип службы. Нас интересуют значения SERVICE_WIN32_OWN_PROCESS для однопоточной службы и SERVICE_WIN32_SHARE_PROCESS для многопоточных служб.
dwCurrentState
Текущее состояние службы. Необходимо устанавливать, чтобы сообщить диспетчеру, что сейчас делает служба:
  • SERVICE_START_PENDING — служба готовится к запуску;
  • SERVICE_RUNNING — служба запущена и работает;
  • SERVICE_PAUSE_PENDING — служба ставится на паузу;
  • SERVICE_PAUSED — служба поставлена на паузу;
  • SERVICE_CONTINUE_PENDING — служба продолжает работу;
  • SERVICE_STOP_PENDING — служба готовится к остановке;
  • SERVICE_STOPPED — служба остановлена.
dwControlsAccepted
Указывает диспетчеру, какие команды управления может принимать служба.
  • SERVICE_ACCEPT_PAUSE_CONTINUE — служба обрабатывает сообщения о паузе и возобновлении;
  • SERVICE_ACCEPT_SHUTDOWN — служба обрабатывает сообщения о завершении системы;
  • SERVICE_ACCEPT_STOP — служба обрабатывает сообщения об останове.
В простом случае достаточно указать SERVICE_ACCEPT_STOP, чтобы диспетер отправлял сигналы остановки и запуска. Тем не менее, диспетчер в любом случае также будет отправлять код SERVICE_CONTROL_INTERROGATE.
dwWin32ExitCode
Код завершения программы. Если не было ошибок, необходимо устанавливать в NO_ERROR.
dwServiceSpecificExitCode
Код завершения службы. Если не было ошибок, необходимо устанавливать в NO_ERROR.
dwCheckPoint
Это значение служба должна периодически увеличивать, чтобы сообщать диспетчеру о прогрессе длительных операций запуска, остановки и прочего.
dwWaitHint
Максимальное значение, до которого служба будет увеличивать dwCheckPoint. Диспетчер будет использовать для построения прогресса длительных операций.

Пример

Создадим простую службу с именем MyNewService, которая не будет ничего делать. Этот код можно использовать как заготовку для служб.

Код FreeBASIC
#define unicode
#include once "windows.bi"

Declare Sub ReportSvcStatus(ByVal dwCurrentState As DWORD, ByVal dwWin32ExitCode As DWORD, ByVal dwWaitHint As DWORD)
Declare Sub SvcMain(ByVal dwNumServicesArgs As DWORD, ByVal lpServiceArgVectors As LPWSTR Ptr)
Declare Sub SvcCtrlHandler(ByVal dwCtrl As DWORD)

' Имя службы
Const ServiceName = "MyNewService"

' Состояние службы
Dim Shared gSvcStatus As SERVICE_STATUS
' идентификатор службы
Dim Shared gSvcStatusHandle As SERVICE_STATUS_HANDLE
' Событие
Dim Shared ghSvcStopEvent As HANDLE
' Счётчик длительных операций
Dim Shared dwCheckPoint As DWORD


' TODO Добавить любые дополнительные службы в этот список
Dim DispatchTable(1) As SERVICE_TABLE_ENTRY = Any
DispatchTable(0).lpServiceName = @ServiceName
DispatchTable(0).lpServiceProc = @SvcMain
' Массив должен заканчиваться пустым элементом
DispatchTable(1).lpServiceName = 0
DispatchTable(1).lpServiceProc = 0

' Вызов функции StartServiceCtrlDispatcher возвращает значение когда служба завершится
If StartServiceCtrlDispatcher(@DispatchTable(0)) = 0 Then
&t;' Произошла ошибка
&t;Select Case GetLastError()
&t;&t;Case ERROR_FAILED_SERVICE_CONTROLLER_CONNECT
&t;&t;&t;Print "Запуск из консоли"
&t;&t;Case ERROR_INVALID_DATA
&t;&t;&t;Print "Неправильные данные"
&t;&t;Case ERROR_SERVICE_ALREADY_RUNNING
&t;&t;&t;Print "Служба уже запущена"
&t;End Select
End If
' Процесс должен завершаться как можно проще:
ExitProcess(0)

' Точка входа службы
' Параметры:
' dwArgc — количество аргументов командной строки
' lpszArgv — массив аргументов командной строки
Sub SvcMain(ByVal dwNumServicesArgs As DWORD, ByVal lpServiceArgVectors As LPWSTR Ptr)
&t;' Регистрация функции‐обработчика сообщений от контроллёра
&t;gSvcStatusHandle = RegisterServiceCtrlHandler(@ServiceName, @SvcCtrlHandler)
&t;If gSvcStatusHandle = 0 Then
&t;&t;' Какая‐то ошибка
&t;&t;Exit Sub
&t;End If
&t;
&t;' Заполнить структуру SERVICE_STATUS
&t;gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS
&t;gSvcStatus.dwServiceSpecificExitCode = 0
&t;
&t;' Сообщить контроллёру, что служба ожидает
&t;ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000)
&t;
&t;' TODO Добавить необходимых инициализаций
&t;' Необходимо периодически сообщать контроллёру SERVICE_START_PENDING
&t;' чтобы контроллёр не считал службу зависшей
&t;' Если инициализация не удалась необходимо
&t;' сообщить контроллёру SERVICE_STOPPED
&t;
&t;' Событие, которое будем ожидать для завершения службы
&t;ghSvcStopEvent = CreateEvent(NULL, _ /' атрибуты безопасности по умолчанию '/
&t;&t;TRUE, _ /' ручное сбрасывание '/
&t;&t;FALSE, _ /' событие ещё не произошло '/
&t;&t;NULL) /' без имени '/
&t;
&t;' Если событие не создано, то сообщить контроллёру,
&t;' что служба остановлена и выйти
&t;If ghSvcStopEvent = NULL Then
&t;&t;ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0)
&t;&t;Exit Sub
&t;End If
&t;
&t;' Сообщить контроллёру, что служба запущена
&t;ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0)
&t;
&t;' TODO Делать полезную работу службы
&t;' Например, запустить поток, в котором будет идти основная работа
&t;
&t;' Ожидать до тех пор, пока служба не остновится
&t;WaitForSingleObject(ghSvcStopEvent, INFINITE)
&t;ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0)
End Sub

' Вызывается контроллёром и отправляет управляющий код службе
' Параметры:
' dwCtrl — управляющий код
Sub SvcCtrlHandler(ByVal dwCtrl As DWORD)
&t;' Эти коды могут поступать в любом порядке,
&t;' даже если предыдущие операции не завершены
&t;Select Case dwCtrl
&t;&t;Case SERVICE_CONTROL_INTERROGATE
&t;&t;&t;ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0)
&t;&t;Case SERVICE_CONTROL_STOP
&t;&t;&t;' Код остановки службы
&t;&t;&t;ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0)
&t;&t;&t;' Установить событие для остановки службы
&t;&t;&t;SetEvent(ghSvcStopEvent)
&t;&t;Case Else
&t;&t;&t;' Обработка собственных кодов
&t;End Select
End Sub

' Сообщение котроллёру состояния службы
' Параметры:
' dwCurrentState — текущее состояние службы
' dwWin32ExitCode — код ошибки
' dwWaitHint — расчётное время операции, в миллисекундах
Sub ReportSvcStatus(ByVal dwCurrentState As DWORD, ByVal dwWin32ExitCode As DWORD, ByVal dwWaitHint As DWORD)
&t;' Заполнить структуру SERVICE_STATUS
&t;gSvcStatus.dwCurrentState = dwCurrentState
&t;gSvcStatus.dwWin32ExitCode = dwWin32ExitCode
&t;gSvcStatus.dwWaitHint = dwWaitHint
&t;
&t;If dwCurrentState = SERVICE_START_PENDING Then
&t;&t;gSvcStatus.dwControlsAccepted = 0
&t;Else
&t;&t;gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP
&t;End If
&t;
&t;If dwCurrentState = SERVICE_RUNNING Or dwCurrentState = SERVICE_STOPPED Then
&t;&t;gSvcStatus.dwCheckPoint = 0
&t;Else
&t;&t;dwCheckPoint += 1
&t;&t;gSvcStatus.dwCheckPoint = dwCheckPoint
&t;End If
&t;
&t;' Сообщить состояние службы контроллёру
&t;SetServiceStatus(gSvcStatusHandle, @gSvcStatus)
End Sub

Для компиляции необходимо использовать многопоточную библиотеку времени выполнения, то есть компилировать с параметром -mt.

Регистрация и запуск службы

Для регистрации службы в базе данных диспетчера можно воспользоваться утилитой sc:

Код Batch
set current_dir=%~dp0
sc create MyNewService binPath= "%current_dir%MyNewService.exe" start= "auto"

Теперь, если открыть остастку управления службами services.msc, то можно увидеть нашу службу в списке служб.

Запустить службу можно прямо из оснастки управления или этой командой:

Код Batch
sc start MyNewService

Поделись ссылочкой в социальных сетях