Если ваша служба успешно выполнила свою миссию или, наоборот, окончательно провалилась (неважно, во время выполнения или инициализации), её нужно завершить. Несколько вариантов того, «как делать не надо»:
Завершить все рабочие потоки, поток, выполняющий Handler[Ex] не трогать. В этом случае SCM «ничего не заметит» и служба продолжит выполняться. Это не смертельно, но и не очень хорошо, так как ресурсы-то используются.
Завершить все рабочие потоки, поток, выполняющий Handler[Ex] завершить вызовом ExitThread при обработке первого следующего сообщения. SCM генерирует ошибку и добавляет запись о ней в Event Log.
Завершить процесс вызовом ExitProcess. Результат аналогичен предыдущему, даже ошибка такая же. Странно, что код завершения процесса не сохраняется.
А теперь о том, как надо. Об окончании работы служба должна сообщить. Как обычно, для сообщения об изменении состояния используется функция SetServiceStatus. В данном случае из всех полей передаваемой в неё структуры SERVICE_STATUS интерес представляют dwCurrentState, dwWin32ExitCode и dwServiceSpecificExitCode. dwCurrentState в любом случае должно быть установлено в SERVICE_STOPPED, значения остальных зависят от ситуации.
Служба завершилась успешно. dwWin32ExitCode = NO_ERROR, в Event Log ничего записано не будет.
Произошла неисправимая ошибка, и это одна из стандартных ошибок Windows. dwWin32ExitCode = ERROR_..., в Event Log будет добавлена запись, описывающая ошибку, числовое значение ошибки указано не будет.
Произошла неисправимая ошибка, специфичная для вашей службы. dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR, в dwServiceSpecificExitCode код ошибки. Так как систему кодирования ошибок вы придумали сами, расшифровать значение кода можете тоже только вы. В Event Log будетдобавленазаписьследующегосодержания: «The ... service terminated with service-specific error ...» (в местах многоточий – имя службы и код ошибки).
Если для завершения службы необходимо выполнить продолжительные действия, в процессе их выполнения имеет смысл посылать уведомления SERVICE_STOP_PENDING. Но это не обязательно.
Ещё один тонкий момент: что будет с вашей службой после вызова SetServiceStatus? Все потоки прекратят исполняться сразу и окончательно, или им дадут «умереть естественной смертью»? Я попытался выяснить это, и получил следующее (это верно для любых вариантов завершения службы, при которых вызывается SetServiceStatus с соответствующими параметрами, кроме случая с SERVICE_CONTROL_SHUTDOWN):
Если exe-файл содержит несколько служб и хоть одна из них (не считая завершающейся) запущена, ничего интересного не произойдёт. То есть, ни процесс, ни один из потоков не будут завершены насильно.
После завершения последней службы функция ServiceControlDispatcher возвращает управление в main/WinMain. Если main/WinMain самостоятельно заканчивается в течение 20-ти секунд, то, как и у нормального приложения, выполняется функция ExitProcess() и завершаются все потоки.
ПРЕДУПРЕЖДЕНИЕТак как ServiceControlDispatcher возвращает управление в main/WinMain сразу после вызова SetServiceStatus, main/WinMain может вызвать ExitProcess раньше, чем ваш рабочий поток (или потоки, если у вас их несколько) закончат выполнение. В результате, например, могут оказаться невызванными деструкторы стековых объектов. Чтобы избежать этого можно поступить так: - получить описатель рабочего потока (например, с помошью DuplicateHandle) и сохранить его в глобальной переменной - в main/WinMain дождаться завершения рабочего потока Другие (но тоже печальные) возможные последствия преждевременного вызова ExitProcess описаны в MSDN в Q201349: "PRB: ServiceMain thread May Get Terminated in DllMain() when a Service Process Exits Normally". Большое спасибо Dima2 за это замечание. |
Через 20 секунд после завершения последней службы процесс уничтожается.
В один exe-файл можно поместить несколько служб. Название раздела характеризует моё отношение к таким проектным решениям. Это затрудняет кодирование и отладку, а единственный известный мне выигрыш – экономия ресурсов на компьютере пользователя (если вы пишете несколько зависимых друг от друга служб, наверное, появляются и другие выигрыши; я этим ни разу не занимался). Но, тем не менее, на моей машине в service.exe находятся службы Alerter, AppMgmt, Browser, Dhcp, dmserver, Dnscache, Eventlog, lanmanserver, lanmanworkstation, LmHosts, Messenger, PlugPlay, ProtectedStorage, seclogon, TrkWks, W32Time и Wmi. Вряд ли их писали люди глупее меня.
Интерактивности в службах следует избегать. Службы предназначены для непрерывной работы в отсутствии пользователей, поэтому дожидаться, пока оператор нажмёт «OK», можно очень долго. Но, тем не менее, возможности есть.
ПРЕДУПРЕЖДЕНИЕУчтите, что все описанные в этом разделе методы (а о существовании других я не слышал) позволяют взаимодействовать только с консольной сессией (службы запускаются в консольной сессии, так как в ней запускается SCM). Поэтому интерактивная (в широком смысле этого слова) служба будет некорректно работать в Windows NT/2000 Terminal Service и, что гораздо более важно, в Windows XP при использовании возможности Fast User Switching. То есть, всё будет корректно, но, возможно, не совсем так, как вы ожидали. Рекомендую почитать про Terminal Service (это круто!) и никогда не использовать интерактивность в службах. |
Самое простое – отобразить сообщение (MessageBox). Это может любая служба, какие бы флаги не стояли. Для этого нужно в функцию MessageBox[Ex] помимо прочих флагов передать MB_SERVICE_NOTIFICATION или MB_DEFAULT_DESKTOP_ONLY. Первый флаг заставит функцию вывести сообщение на экран, даже если пользователь ещё не вошёл в систему. Выглядит забавно. Представьте: на экране приглашение ввести пароль и десяток сообщений, поздравляющих пользователя с 1 апреля. Но для этого придётся написать десять служб, так как процесс не может отображать на экране несколько таких сообщений одновременно, судя по всему, они ставятся в очередь (к MB_DEFAULT_DESKTOP_ONLY это тоже относится). Если установлен второй флаг, сообщение появится только на «нормальном» рабочем столе. Более строго, MB_SERVICE_NOTIFICATION заставляет сообщение появиться на текущем активном desktop-е, а MB_DEFAULT_DESKTOP_ONLY только на «нормальном». Этими флагами можно пользоваться, если определен макрос _WIN32_WINNT и его значение больше или равно 0x0400.
ПРИМЕЧАНИЕДля реализации этой возможности привлечены неслабые средства. В Spy++ видно, что окна (MessageBox) принадлежат одному из потоков CSRSS.EXE. Это имеет забавный побочный эффект: сообщение может висеть на экране даже после завершения приложения. Соберите и запустите такую программку:.#define _WIN32_WINNT 0x0500#include <windows.h>int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int){MessageBox(NULL, "try to kill me", "undead", MB_SERVICE_NOTIFICATION);return 0;}.А теперь попробуйте убить процесс из Task Manager’а. Если сделать несколько потоков, можно проверить ExitProcess, она в этой ситуации тоже не помогает. |
Если пользовательский интерфейс вашей службы должен быть богаче, существует два выхода. В первом случае должны выполняться следующие условия:
Служба запущена в контексте безопасности LocalSystem.
Служба должна быть помечена как интерактивная.
Значение параметра NoInteractiveServices ключа HKLM\SYSTEM\CurrentControlSet\ Control\Windows\ должно быть равно 0.
Если всё это так, служба может выводить на экран что угодно. Иначе, служба может попробовать самостоятельно открыть и использовать нужный ей desktop. Подобнее об объектах «desktop» и «window station» смотрите в MSDN.
Подробности программирования
Ниже рассмотрены вопросы, имеющие косвенное отношение к службам, но прямое к их программированию. То есть, при разработке вашей конкретной службы всё это может вам и не понадобиться, но вообще разбираться в этом надо. Большинство тем имеют отношение не только к службам, но и к обычным приложениям. Рассмотрение не очень подробно, но введение дано (кроме вопроса о системе безопасности).
Есть несколько причин, по которым отлаживать службы сложнее, чем обычные приложения.
В службе будет как минимум два потока.
Службу запускает SCM.
Если в exe-файле несколько служб, отладка будет ещё неприятнее.
Однако чаще всего удаётся написать приложение, полностью воспроизводящее рабочую часть службы, отладить его и поместить в обёртку из некоторого стандартного кода, превращающего его в службу. Если ядро приложения отлажено, и код, реализующий стандартные функции службы, проверен, при стыковке больших проблем быть не должно. Но (как это ни странно) появляются средние и мелкие. Помимо «отладки без отладчика» остаётся следующее.
Присоединить отладчик к запущенной службе.
Использовать DebugBreak.
В HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options добавить ключ «имя_исполяемого_файла.exe» (без пути), в нём создать строковый параметр Debugger и записать в него полное имя отладчика. При запуске указанного файла, он будет запущен под отладчиком.
Использовать SoftIce.
При отладке кода запуска следует помнить, что ограничение на время (30 секунд) никуда не исчезает, если вы не уложитесь, служба завершится.
Обычно у службы есть какие-то параметры, которые можно настраивать. Иногда нужно иметь возможность определять и корректировать текущее состояние службы.
ПРИМЕЧАНИЕИзменение состояния и изменение параметров – это не одно и то же. Например, вы написали какую-то сетевую службу. Если есть возможность изменять состояние, администратор может, просмотрев список текущих соединений, какие-то из них разорвать. Изменением параметров этого не достигнуть. |
Остановимся на случае не интерактивной службы, так как в интерактивной службе проблему можно решить так же, как и в обычном приложении.
Для администрирования пишется отдельное приложение (далее – конфигуратор), которое каким-то образом взаимодействует со службой. Могу предложить следующие варианты: