45832 (Программирование служб: подробности), страница 3
Описание файла
Документ из архива "Программирование служб: подробности", который расположен в категории "". Всё это находится в предмете "информатика" из , которые можно найти в файловом архиве . Не смотря на прямую связь этого архива с , его также можно найти и в других разделах. Архив можно найти в разделе "рефераты, доклады и презентации", в предмете "информатика, программирование" в общих файлах.
Онлайн просмотр документа "45832"
Текст 3 страницы из документа "45832"
В ключе реестра 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:
WINADVAPI BOOL WINAPI EncryptFileA( LPCSTR lpFileName ); WINADVAPI BOOL WINAPI EncryptFileW( 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.
При последовательном употреблении TCHAR, TEXT, _tscanf,.. можно простым изменением настроек переключаться между ANSI и Unicode версиями проекта. Вряд ли вы будете часто пользоваться такой возможностью, но то, что она есть – хорошо.
ПРИМЕЧАНИЕ Никто не заставляет вас использовать одну и ту же кодировку везде, достаточно просто быть последовательным. Например, модуль, осуществляющий запись в лог-файл, может явно вызывать ANSI-функции (с суффиксом «A») и передавать им char-строки. С таким модулем можно работать, но нужно помнить, что его функциям не стоит передавать TCHAR-строки. Иначе в ANSI-версии проекта это будет работать, а в Unicode-версии даже не скомпилируется. В основной части службы предпочтительнее использовать Unicode. |
Мелочи
Здесь собраны факты, знать которые полезно, но не необходимо.
Служба не обязательно является консольным приложением.
В параметре ImagePath ключа HKLM\System\CurrentControlSet\Services\имя_службы можно задать командную строку (можно даже «/uninstall»), но, по-моему, этой возможностью лучше не пользоваться.
Начиная с Windows 2000 в параметре Description ключа HKLM\System\CurrentControl Set\Services\имя_службы можно задать описание службы. Оно отображается Services в столбце «Description». Для установки этого параметра можно воспользоваться RegSetValueEx или ChangeServiceConfig2. Предпочтительнее пользоваться ChangeServiceConfig2, но проще RegSetValueEx…
Судя по всему, пока служба не вызовет StartServiceCtrlDispatcher, SCM не может запустить следующую. Это ещё одна причина не помещать инициализацию в main/WinMain.
После вызова StartServiceCtrlDispatcher основной поток приложения не простаивает. Как минимум, он исполняет обработчики сообщений всех служб exe-файла. Поэтому «задействовано» не три потока, а два.
Когда функция MessageBox вызывается с флагом MB_SERVICE_NOTIFICATION или MD_SERVICE_DEFAULT_DESKTOP_ONLY, в раздел Event Log’а System добавляется запись. Источник – «Application Popup», внутри – содержимое сообщения. Время создания записи соответствует времени вызова функции MessageBox, а не времени отображения сообщения.
Сообщения могут приходить абсолютно бессистемно. То есть, например, несмотря на то, что ваша служба не стоит на паузе, пользователь может (утилитой net.exe или какой-нибудь своей) отправить ей сообщение SERVICE_CONTROL_CONTINUE. Если в результате ваша служба упадёт, он будет очень рад, но уважения к вам у него не прибавится.
Функция CreateProcessW имеет одну особенность – её второй параметр имеет тип LPWSTR, а не LPCWSTR, причём, если этот параметр будет указателем на константу, произойдет исключение. Несмотря на это, в функцию CreateProcessA можно спокойно передавать указатель на константу, так как при преобразовании из ANSI в Unicode она выделяет буфер и передаёт CreateProcessW указатель на него.
Код
В качестве примера я написал небольшую службу, конфигуратор и файл сообщений. Служба почти полностью состоит из стандартной (для меня) обёртки вокруг рабочего потока и может использоваться как заготовка. Краткое описание структуры проекта:
Файл | Описание |
Stddef.h | Помимо традиционного включения windows.h, содержит объявления следующих макросов: ServiceName – «внутреннее» имя службы; DisplayName – «отображаемое» имя службы; EventSource – имя источника сообщений; MsgFileName – путь к файлу сообщений из корня службы. |
main.cpp | Содержит функцию main – точку входа приложения. Main проверяет командную строку, и в зависимости от её содержимого выполняет следующие действия: /install – пытается инсталлировать службу; /uninstall – пытается удалить службу; что-то иное – выводит справочное сообщение. Если в командной строке ничего нет, предположительно приложение запущено SCM-ом. В этом случае main вызывает функцию для выполнения некой глобальной инициализации, вызывает StartServiceCtrlDispatcher, после возвращения управления вызывает функцию для выполнения глобальной очистки. |
Cmdline.h, cmdline.cpp | Функции, вызываемые при обработке командной строки. Это установка/удаление службы, вывод справочного сообщения. |
Stdfunc.h, stdfunc.cpp | «Стандартные» функции службы. ServiceMain, ServiceHandler и функции, посылающие SCM сообщения типа «процесс идёт». Наружу выставляются ServiceMain (указатель на неё передаётся в StartServiceCtrlDispatcher) и FatalError, используемая для информирования SCM о внезапном (т.е. не вызванном сообщением SERVICE_CONTROL_SHUTDOWN или SERVICE_CONTROL_STOP) завершении работы службы. |
Report.h, report.cpp | Интерфейс к Event Log-у. |
Parameters.h, parameters.cpp | Читает из реестра параметры службы. |
Work.h, work.cpp | Рабочая часть службы. Содержит функции: GlobalInit – глобальная инициализация; GlobalEnd – глобальная очистка;Init – инициализация конкретной службы;Run – функция, выполняющая основную работу;Stop, Pause, Continue, ParametersChanged – вызываются из ServiceHandler при получении соответствующего сообщения от SCM. |
Чтобы создать свою службу, используя этот шаблон, нужно внести следующие изменения:
Файл | Изменение, комментарии |
Stddef.h | Изменить значения макросов ServiceName и т.п. |
Cmdline.cpp | Если при установке/удалении необходимо выполнить какие-то специфические действия (записать что-то в реестр, и т.п.), нужно реализовать эти самые действия. |
Stdfunc.cpp | В этот файл вносятся изменения, если служба должна реагировать на некие сообщения от SCM. В этом случае изменения незначительны: в функции ServiceHandler нужно реализовать соответствующие обработчики, а SetServiceStatus будет вызываться с различными флагами. |
Report.h, report.cpp | Поскольку у разных служб разный набор сообщений, в report.h объявляются разные функции. Однако все функции отправки сообщений очень похожи друг на друга, а функции регистрации/дерегистрации источника сообщений просто совпадают. |
Parameters.h, parameters.cpp | Скорее всего, у служб различные параметры, поэтому будут отличаться и интерфейс и реализация. Но обычно все параметры получаются каким-то однотипным путём (в одном ключике реестра), причём этот путь может совпадать в различных службах. Т.е. функция получения конкретного параметра может состоять из вызова некоторой промежуточной функции, одинаковой в разных службах. |
Work.h, work.cpp | В этом файле реализуется основная логика службы.. |
На основе такой заготовки можно достаточно быстро сделать простенькую, но работающую службочку. Если нужно разобраться с тем, как эти самые службы работают, или протестировать какую-либо функцию службы (так как службы работают под необычными учётными записями и с использованием необычных WindowStation, некоторые функции могут работать необычно), это очень удобно.
В реальных проектах службы обычно выступают ещё и в роли COM-серверов. Такие службы я рекомендую писать с использованием "ATL COM Wizard". Если же ваша служба не является COM-сервером, или вы по каким-то причинам не переносите ATL… Что ж, можете попробовать использовать нечто подобное и в настоящем проекте. Можете даже взять мой код. Но, как говорится, код предоставляется as is, к сожалению, я написал его уже после завершения «настоящего» проекта, поэтому «в бою» пока не проверял. Заметите ошибки или пути улучшения – пишите, буду благодарен.
Список литературы
Для подготовки данной работы были использованы материалы с сайта http://www.rsdn.ru/