Смекни!
smekni.com

Программирование служб: подробности (стр. 4 из 4)

При последовательном употреблении 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, к сожалению, я написал его уже после завершения «настоящего» проекта, поэтому «в бою» пока не проверял. Заметите ошибки или пути улучшения – пишите, буду благодарен.