45832 (665176), страница 2
Текст из файла (страница 2)
Служба завершилась успешно. 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 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 секунд) никуда не исчезает, если вы не уложитесь, служба завершится.
Администрирование
Обычно у службы есть какие-то параметры, которые можно настраивать. Иногда нужно иметь возможность определять и корректировать текущее состояние службы.
ПРИМЕЧАНИЕ Изменение состояния и изменение параметров – это не одно и то же. Например, вы написали какую-то сетевую службу. Если есть возможность изменять состояние, администратор может, просмотрев список текущих соединений, какие-то из них разорвать. Изменением параметров этого не достигнуть. |
Остановимся на случае не интерактивной службы, так как в интерактивной службе проблему можно решить так же, как и в обычном приложении.
Для администрирования пишется отдельное приложение (далее – конфигуратор), которое каким-то образом взаимодействует со службой. Могу предложить следующие варианты:
Параметры хранятся в реестре (обычно в HKLM\System\CurrentControlSet\Services\имя_службы\Parameters\). Конфигуратор их изменяет, служба, используя функцию RegNotifyChangeKeyValue, отслеживает эти изменения.
Параметры хранятся где угодно. После их изменения конфигуратор посылает службе сообщение. Для этого нужно открыть службу функцией OpenService с правом доступа SERVICE_USER_DEFINED_CONTROL и послать сообщение функцией ControlService. Конечно, в службе необходимо предусмотреть соответствующую обработку.
Используются именованные каналы, сокеты, DCOM или любое другое средство коммуникации между процессами. В этом случае помимо передачи параметров можно получать и изменять состояние службы.
Самый простой вариант – служба реагирует на изменение параметров только после перезапуска.
Безопасность
Это очень большая тема, про которую я очень мало знаю. Но она имеет прямое отношение к службам, если вы хотите заниматься их разработкой, вам (и мне, естественно) придется с ней разобраться.
Event Log
Интерактивность – это нехорошо. Но служба должна каким-то образом сообщать администратору об ошибках и других важных событиях. Службам, генерирующим несколько десятков (или больше) сообщений в день, целесообразно использовать для этого файлы в формате какой-нибудь СУБД. Для остальных лучшее решение – Event Log. Он достаточно гибок, стандартен, администратор может просмотреть его утилитой Event Viewer.
ПРИМЕЧАНИЕ Event Log может использоваться любым приложением. Поскольку статья посвящена службам, я использую слово «служба». Возможности Event Log-а не исчерпываются тем, что описано в этом разделе, но обычно ничего большего и не требуется. Если вам нужно больше, советую обратиться к уже упоминавшейся книге Джеффри Рихтера и Джейсона Кларка «Программирование серверных приложений в Windows 2000» или к MSDN. |
Если вы хотите записать сообщение в Event Log, нужно:
создать файл сообщений (при разработке службы);
зарегистрировать в реестре новый источник сообщений (при инсталляции);
собственно записать сообщение.
Файл сообщений
Файл сообщений – это exe- или dll-файл, содержащий ресурс «таблица сообщений». Для получения нужно скомпилировать message compiler’ом (mc.exe) правильно написанный текстовый файл (ниже), получившийся файл ресурсов включить в проект и скомпоновать. Mc.exe не интегрирован в Visual Studio и запускается из командной строки. Ключами можно не пользоваться, достаточно написать «mc filename.mc». На выходе будет filename.h, filename.rc, filenameXXXX.bin (в некоторых случаях несколько штук). filename.rc – тот самый файл ресурсов, он ссылается на filenameXXXX.bin. filename.h содержит идентификаторы сообщений и используется службой.
«Правильно написанный текстовый файл» имеет следующую структуру:
[Заголовок] сообщение_1 ... сообщение_N |
Заголовок необязателен, он может отсутствовать или присутствовать частично. Обычно используется только поле LanguageNames. Синтаксис несложен:
LanguageNames = (обозначение_для_языка_1 = LangID_1 : имя_bin_файла_1) ... LanguageNames = (обозначение_для_языка_n = LangID_n : имя_bin_файла_n) |
«обозначение_для_языка» и «имя_bin_файла» могут быть произвольными, таблица LangID есть в MSDN (смотрите GetLocalInfo()).
Смысл этого поля – перечисление поддерживаемых языков. Если файл сообщений поддерживает несколько языков, в разных локализациях Windows (русской, английской, ...) Event Log будет отображать разные версии сообщений.
Само сообщение выглядит так:
MessageId = 0x1 Severity = Success Facility = Application SymbolicName = MSG_COOL_HACK Language = English Hi! I hack you! You login: %1, you password: %2. . Language = Russian Привет! Я тебя взломал! Твой логин: %1, твой пароль: %2. . |
Поля «MessageId», «Severity», «Facility» и «SymbolicName» составляют заголовок сообщения.
MessageId | Идентификатор сообщения. Не обязателен, в случае отсутствия инкрементируется MessageId предыдущего сообщения (для первого сообщения MessageId – 0). |
Severity | Тип сообщения, определены типы «Success», «Informational», «Warning», и «Error», их названия можно переопределить в заголовке, но на название типа и иконку, отображаемые Event Viewer’ом, это не повлияет. Не обязательно, в случае отсутствия наследуется от предыдущего сообщения, по умолчанию – значение «Success». |
Facility | Позволяет задать категорию сообщения. Определены значения «System» и «Application», можно определить (в заголовке файла) ещё около четырёх тысяч. Не обязательно, в случае отсутствия наследуется от предыдущего сообщения, первое сообщение по умолчанию имеет значение «Application». |
SymbolicName | Имя соответствующего сообщению макроса в генерируемом h-файле. |
Тело сообщения начинается после строки «Language = XXXX» и заканчивается строкой, не содержащей ничего, кроме точки и перевода строки. На каждый определённый в заголовке язык должно быть по одному «телу» (если вы не определили ни одного языка, используйте «English»). Вместо «%1» ... «%99» будут вставлены строки, которые приложение передаст при записи сообщения. Учтите, что этот механизм предназначен для передачи имён файлов, IP-адресов, каких-то чисел и т.д. Но не для передачи текста. Можно, конечно, сделать так:
Language = English %1 . |
но, с моей точки зрения, это плохая идея. Дело в том, что в файлах Event Log-а хранится имя источника, номер сообщения, переданные строки и прикреплённые данные, но не сам текст. Поэтому, если записать сообщение, а потом изменить dll или значение параметра EventMessageFile в реестре, текст изменится. Насколько я знаю, это нужно, чтобы, когда пользователь из Китая, у которого всё на китайском, посылает свой файл с логом (лог, описываемый в этом разделе, находится в WinNT\System32\config\AppEvent.Evt) разработчику из Нигерии, тот мог бы, используя свою dll, прочитать те же сообщения на нигерийском.
Регистрация источника сообщений