Реферат: Программирование служб: подробности
Задержка перед вызовом RegisterServiceCtrlHandler[Ex].
Задержка перед первым вызовом SetServiceStatus.
Слишком большие паузы между вызовами SetServiceStatus.
Не меняется поле dwCheckPoint структуры, передаваемой SetServiceStatus.
Во всех перечисленных случаях реакция системы будет одинаковой. А именно:
A) Служба запускается автоматически.
Минуты через две (если за это время «нарушение» не прекратится и служба не начнёт работать нормально) в Event Log-е появится запись «The ... service hung on starting.»
Еслихотьоднаслужба «повисла», пользовательполучитсообщение «At least one service or driver failed during system startup. Use Event Viewer to examine the event log for details.» Такое ощущение, что это сообщение появляется в тот момент, когда запускается первая «зависшая» служба (сам понимаю, что звучит нелогично, но что делать...).
B) Служба запускается вручную из Services.
Минуты три система подождёт.
Появится сообщение об ошибке.
В программе Services в столбце Status служба будет помечена словом «Starting».
В любом случае служба, в конце концов, запустится.
Эта информация не очень важна (и, кстати, не документирована), так как даже таких нарушений лучше не допускать. Но представлять, что будет, если по каким-то причинам, ваша служба слегка притормозит, полезно.
Кто будет работать?
Этот вопрос возник у меня, когда я писал свою первую службу. Если чётче сформулировать, то звучит он так: который из потоков можно использовать в качестве рабочего? На первый взгляд задействовано три потока: один исполняет main/WinMain, второй – ServiceMain, третий – Handler[Ex] (не совсем так, см. «Мелочи»). Очевидно, что первый и третий потоки не подходят. Про второй поток ничего не известно и, вполне возможно, функция ServiceMain должна возвращать управление. Я поступил просто: создал в ServiceMain дополнительный поток, который выполнял работу. Окончание функции выглядело так:
... // Создаёт рабочий поток и возвращает управление Begin(); } |
Это работает. Никаких дополнительных проблем при таком подходе не обнаружено.
После внимательного прочтения MSDN выяснилось, что вообще-то для работы предназначен поток, выполняющий ServiceMain. Болеетого, вописаниинаписано: «A ServiceMain function does not return until its services are ready to terminate.» Возвращать управление из ServiceMain сразу рекомендуется только службам, не нуждающимся в потоке для выполнения работы (например, вся работа может заключаться в реакции на сообщения). Я советую следовать рекомендациям Microsoft.
Корректное завершение
Если ваша служба успешно выполнила свою миссию или, наоборот, окончательно провалилась (неважно, во время выполнения или инициализации), её нужно завершить. Несколько вариантов того, «как делать не надо»:
Завершить все рабочие потоки, поток, выполняющий Handler[Ex] не трогать. В этом случае SCM «ничего не заметит» и служба продолжит выполняться. Это не смертельно, но и не очень хорошо, так как ресурсы-то используются.
Завершить все рабочие потоки, поток, выполняющий Handler[Ex] завершить вызовом ExitThread при обработке первого следующего сообщения. SCM генерирует ошибку и добавляет запись о ней в Event Log.
Завершить процесс вызовом ExitProcess. Результат аналогичен предыдущему, даже ошибка такая же. Странно, что код завершения процесса не сохраняется.
А теперь о том, как надо. Об окончании работы служба должна сообщить. Как обычно, для сообщения об изменении состояния используется функция SetServiceStatus. В данном случае из всех полей передаваемой в неё структуры SERVICE_STATUS интерес представляют dwCurrentState, dwWin32ExitCode и dwServiceSpecificExitCode. dwCurrentState в любом случае должно быть установлено в SERVICE_STOPPED, значения остальных зависят от ситуации.
Служба завершилась успешно. dwWin32ExitCode = NO_ERROR, в Event Log ничего записано не будет.