Параметры хранятся в реестре (обычно в HKLM\System\CurrentControlSet\Services\имя_службы\Parameters\). Конфигуратор их изменяет, служба, используя функцию RegNotifyChangeKeyValue, отслеживает эти изменения.
Параметры хранятся где угодно. После их изменения конфигуратор посылает службе сообщение. Для этого нужно открыть службу функцией OpenService с правом доступа SERVICE_USER_DEFINED_CONTROL и послать сообщение функцией ControlService. Конечно, в службе необходимо предусмотреть соответствующую обработку.
Используются именованные каналы, сокеты, DCOM или любое другое средство коммуникации между процессами. В этом случае помимо передачи параметров можно получать и изменять состояние службы.
Самый простой вариант – служба реагирует на изменение параметров только после перезапуска.
Это очень большая тема, про которую я очень мало знаю. Но она имеет прямое отношение к службам, если вы хотите заниматься их разработкой, вам (и мне, естественно) придется с ней разобраться.
Интерактивность – это нехорошо. Но служба должна каким-то образом сообщать администратору об ошибках и других важных событиях. Службам, генерирующим несколько десятков (или больше) сообщений в день, целесообразно использовать для этого файлы в формате какой-нибудь СУБД. Для остальных лучшее решение – 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 = 0x1Severity = SuccessFacility = ApplicationSymbolicName = MSG_COOL_HACKLanguage = EnglishHi! 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, прочитать те же сообщения на нигерийском.
Регистрация источника сообщений
В ключе реестра HKLM\System\CurrentControlSet\Services\Eventlog\Application\ нужно создать ключ с любым именем (это имя будет именем источника сообщений), в этом ключе создать строковый параметр EventMessageFile и записать в него полный путь к файлу сообщений.
Имя источника сообщений отображается Event Viewer-ом в колонке «Source», оно же используется для получения описателя (handle) при записи в Event Log.
Запись
Для начала нужно получить описатель.
ПРИМЕЧАНИЕНа вопрос «описатель чего?» я ответить не могу. Простоописатель.… В MSDN сказанотак: «If the function succeeds, the return value is a handle that can be used with the ReportEvent function.» |
Эту операцию выполняет функция RegisterEventSource. Она выглядит так:
HANDLE RegisterEventSource( LPCTSTR lpUNCServerName, LPCTSTR lpSourceName); |
Параметры:
lpUNCServerName | Имя сервера, на котором находится лог. Для записи в лог текущей машины передавайте NULL. |
lpSourceName | Имя зарегистрированного источника сообщений. |
Для закрытия описателя используется функция DeregisterEventSource.
BOOL DeregisterEventSource( HANDLE hEventLog); |
Запись сообщения производит функция ReportEvent.
BOOL ReportEvent( HANDLE hEventLog, WORD wType, WORD wCategory, DWORD dwEventId, PSID pUserSid, WORD wNumOfStrings, DWORD dwDataSize, LPCTSTR* pStrings,LPVOID pRawData); |
Параметры:
hEventLog | Описатель, полученный от RegisterEventSource. |
wType | Тип сообщения, должен совпадать с типом, записанным в файле сообщений. Варианты: EVENTLOG_SUCCESS, EVENTLOG_INFORMATION_TYPE, EVENTLOG_WARNING_TYPE, EVENTLOG_ERROR_TYPE. |
wCategory | Передавайте 0. |
dwEventId | Идентификатор сообщения. Не равен «MessageId». Берётся из создаваемого mc.exe заголовочного файла. |
pUserSid | Передавайте NULL. |
wNumOfStrings | Количество передаваемых строк. |
dwDataSize | Размер передаваемых данных. |
pStrings | Массив строк. Если строк меньше, чем позиций, в лишних позициях будет «%n», где n – номер позиции. |
pRawData | Данные, прикрепляемые к сообщению. |
Unicode
Unicode – кодировка, в которой одному символу соответствуют два байта. В результате получается 65536 различных символов. Использовать Unicode в программах для Windows NT/2000/XP полезно и просто. Полезно потому, что, во-первых, это повышает производительность (почему – ниже), во-вторых, позволяет вывести на экран или в файл именно то, что вы хотите, независимо от локализации ОС пользователя и, в-третьих, иногда это гораздо удобнее. А просто потому, что вся необходимая поддержка обеспечена.
Большинство API-функций, принимающих в качестве параметров строки, существуют в двух вариантах – ANSI и Unicode. ANSI-вариант имеет суффикс «A», Unicode-вариант – суффикс «W» (от wide – широкий). В Windows NT/2000/XP ANSI-функции просто преобразуют переданные строки в Unicode и вызывают соответствующую Unicode-функцию. Unicode – «родная» кодировка для этих ОС. Для Win 9x «родная» кодировка – ANSI, в ОС этой группы полностью реализовано всего несколько Unicode-функций, остальные сразу возвращают ошибку. Поэтому программа, использующая Unicode, в Windows NT/2000/XP будет работать быстрее, а в Win 9x не будет работать вообще. Поскольку в Win 9x служба всё равно не сможет работать, это не должно вас волновать.
Если вы не сталкивались с Unicode раньше и не изучали заголовочные файлы с объявлениями API-функций, предыдущий абзац может вас озадачить. Скорее всего, вы неоднократно использовали API-функции, принимающие строки и точно помните, что у них не было никаких суффиксов. А оказывается – есть. Ниже приведёна часть файла winbase.h:
WINADVAPIBOOLWINAPIEncryptFileA( LPCSTR lpFileName );WINADVAPIBOOLWINAPIEncryptFileW( LPCWSTR lpFileName );#ifdef UNICODE#define EncryptFile EncryptFileW#else#define EncryptFile EncryptFileA#endif // !UNICODE |
Если макрос UNICODE определён, вы будете использовать EncryptFileW, в противном случае – EncryptFileA. Так можно менять используемую версию API-функций. Осталось научиться регулировать тип передаваемой строки. Это тоже несложно, достаточно пользоваться типом TCHAR при объявлении строковых и символьных переменных и заключать соответствующие константы внутрь макроса TEXT. И TCHAR, и TEXT определены в tchar.h. Кроме них, в этом файле определёны макросы для функций стандартной библиотеки С. Например, макрос _tscanf разворачивается или как wscanf, или как scanf, в зависимости от макроса UNICODE.