На главную страницу


View in English


Как написать службу NT на VB6/VB5

Как известно, Visual Basic - не самое подходящее средство для написания служб (Service) Windows NT/2000/XP. Проблема заключается в том, что для написания службы требуется использование функции API CreateThread , что не поддерживается ни VB5, ни VB6. VB позволяет создавать многопоточные программы, но не с помощью функции CreateThread. VB5 использует общий объект Err для всех потоков, созданных функцией API CreateThread. Это недопустимо, поскольку изменение значений этого объекта может происходить из разных потоков в непредсказуемой последовательности и даже одновременно на многопроцессорных компьютерах. Напротив, VB6 использует для объекта Err (и не только для него) локальную память потока (TLS), но в момент создания потока функцией CreateThread TLS не инициализирована, и поэтому после простой перекомпиляции в VB6 программа не работает совсем (точнее, работает только при компиляции в p-code с общим объектом Err).

Довольно сложный способ решения проблемы создания потоков в VB предложил Мэтью Кэрланд (см. ссылки), но для создания службы можно применить более простое решение. Программа будет работать и без TLS, если в новом потоке совсем не использовать объект Err, а кроме этого использовать только арифметические операторы и вызовы API, описанные в библиотеке типов. Как известно, свойства объекта Err обновляются в двух случаях: когда происходит Run-time ошибка (Err.Number), и после выполнения каждой функции API (Err.LastDllError). Первого можно избежать написанием безошибочного кода, второго - описанием функций API с помощью библиотеки типов (описание функций API не должно содержать атрибутов usesgetlasterror, поэтому библиотеки типов из известной книги Брюса Мак-Кинни не годятся). Функциональная часть службы должна работать в главном потоке, и на нее указанные ограничения не распространяются. Этот подход работает как в VB6, так и в VB5.

Разумеется, для создания своей службы вы можете использовать бесплатный элемент управления NTSVC.OCX от Microsoft, и этот способ достаточно прост и надежен, но имеет один недостаток: вы не можете применять опцию "Unattended execution". Совместно с этой опцией элементы управления OCX применяться не могут, а именно такой режим работы для службы является предпочтительным. Кроме того, поскольку этот элемент управления распространяется в виде исходного кода, существует несколько откомпилированных версий этого компонента, не вполне совместимых между собой.

Этот пример написан при помощи VB6 без использования каких-либо внешних компонентов. Поскольку служба скомпилирована с опцией "Unattended execution", она не имеет визуального интерфейса. Для просмотра сообщений, записываемых службой в Application Log, используйте Event Viewer.

Функциональная часть службы (отсутствующая в данном примере) должна управляться событиями, поступающими от Диспетчера Служб. Все события должны обрабатываться в течение нескольких секунд, в противном случае служба не будет иметь возможности обрабатывать запросы Диспетчера Служб.

См. также:

Microsoft Knowledge Base Q137890, Q170883, Q175948,

An OLE Control for Creating Win32 Services in Visual Basic,

Manipulate Windows NT Services by Writing a Service Control Program,

Creating an Agent NT Service with VB,

How-To Run Your Application as a Service,

How-To Run Your Application as a Service - Part II,

Статью Мэтью Кэрланда "Create Worker Threads in DLLs" в журнале "Visual Basic Programmer's Journal" за июнь 1999 года.


Обновления исходного кода:

06 июня 2004 г.

1. Все вызовы API-функций за исключением GetVersionEx заменены на Unicode-версии, в коде и в библиотеке типов. В библиотеку типов добавлены новые значения Enum для поддержки новых управляющмх кодов Windows 2000.

2. Добавлена функция MsgWaitObj для предотвращения блокировки обработки сообщений. Все вызовы WaitForSingleObject и WaitForMultipleObjects в Sub Main заменены на вызовы MsgWaitObj.

Вопросы и Ответы:

В: Существует ли способ сделать так, чтобы события в Event Viewer обозначались именем приложения вместо VBRuntime?
О: Да, конечно. Например, можно использовать решение из этой статьи. Код для этой статьи можно загрузить отсюда.

В: Я запустил ServiceSampleControl.vbp и проинсталлировал службу, и затем попытался ее запустить, но она не запустилась. При попытке запуска ServiceSample.vbp сообщение об ошибке указывает, что программа должна запускаться как служба.
О: Пробовали ли вы запустить SvSampleControl.exe? Данный пример устроен таким образом, что модуль службы (SvSample.exe) должен находиться в том же каталоге, что и управляющая программа (SvSampleControl.exe) или ее проект (ServiceSampleControl.vbp), если вы предпочитаете запускать ее из IDE. Поскольку проект управляющей программы находится в отдельном каталоге, она не может найти модуль службы. Посмотрите протокол событий Системы - этот факт должен быть там отражен. Разумеется, такое поведение управляющей программы может быть изменено переписыванием нескольких байт исходного кода.

В: Возможно ли включить свойство "Разрешить службе взаимодействовать с Рабочим Столом"?.
О: Да, это возможно. При вызове функции CreateService или ChangeServiceConfig присвойте параметру dwServiceType следующее значение:
SERVICE_WIN32_OWN_PROCESS Or SERVICE_INTERACTIVE_PROCESS

Const SERVICE_WIN32_OWN_PROCESS = &h10&
Const SERVICE_INTERACTIVE_PROCESS = &h100&
В: Возможно ли службу, взаимодействующую с Рабочим Столом, запустить от имени другого пользователя (администратора)?
О: Нет, это невозможно, только запущенные от имени LocalSystem службы могут взаимодействовать с Рабочим Столом. Это ограничение безопасности Windows.
Обычно программисты в таких случаях делят приложение на две части: службу, работающую от имени пользователя с достаточными правами, выполняющую основную работу и обеспечивающую взаимодействие с сетью; и обычную автозагружаемую программу, обеспечивающую взаимодействие с текущим пользователем. Эти две части взаимодействуют между собой одним из известных способов: COM, именованные трубы, разделяемая память и т.д. Использование любого из этих методов применительно к службам не является простой задачей, поскольку требуется учитывать наличие барьеров безопасности.

В: Известно ли вам о проблемах при одновременной работе вашей службы и приложений, использующих локальный DDE? Когда работает служба, локальные DDE-приложения (и некоторые программы установки на базе InstallShield) зависают. Процессы существуют, но не работают. Как только служба останавливается, локальные DDE-приложения начинают работать.
О: Попробуйте заменить все вызовы API-функций ожидания (WaitForSingleObject и др.) моей неблокирующей функцией MsgWaitObj. Использование блокирующих версий функций ожидания в VB-программе не является корректным даже если программа не имеет визуального интерфейса (не трогайте WaitForSingleObject в NTService.bas: использование этой функции в потоке службы законно, а DoEvents из MsgWaitObj, скорее всего, не является безопасной для потоков (thread-safe), и использующиеся там функции API не описаны в библиотеке типов).

Замечание: обновленный исходный код примера службы доступен для загрузки, в нем учтены приведенные выше соображения.

В: Я хочу приспособить ваш пример службы NT для запуска моего приложения как службы. На какие особенности этого процесса я должен обратить внимание в первую очередь?
О: Первое правило для службы: она должна отвечать на запрос остановки настолько быстро, насколько возможно. Если ваша программа выполняет длительые операции, добавьте промежуточные проверки эвента hStopPendingEvent event:
If WaitForSingleObject(hStopPendingEvent, 0&) = 0& Then ... прекращение операции
Следующее правило: код инициализации должен быть коротким. Это означает, что программа должна выполнить строку
SetServiceState SERVICE_RUNNING
не позднее, чем через 1-2 секунды.

Пример представляет два различных подхода к устройству службы.
1. Программа использует объекты, отвечающие на внешние события (например, вы пишете Интернет-сервер, реагирующий на появление данных в сокете). У вас есть код инициализации, код завершения, код главного цикла отсутствует. Интервал повторения главного цикла можно установить равным INFINITE.
2. Ваша программа выполняет периодические действия (например, вы пишете планировщик задач). У вас нет кода инициализации (или он состоит из очень короткого присвоения начальных значений), нет кода завершения, и весь рабочий код сосредоточен в главном цикле.
Разумеется, оба подхода можно комбинировать.

Следующим важным моментом является безопасность. Если служба работает от имени LocalSystem, ей полностью недоступны сетевые ресурсы, если же она работает от имени какого-либо пользователя, она имеет доступ к объектам безопасности в соответствии с правами соответствующего пользователя. Если служба является COM-сервером, для доступа к ней из пользовательского приложения требуется DCOM, даже если доступ осуществляется с того же компьютера. Для корректной работы COM-службы потребуется добавить и изменить несколько значений реестра.

В: Я пытаюсь записать некоторое значение в реестр (HKCU\...) из службы, но редактор реестра показывает, что ничего не записано.
О: Ваша проблема в том, что служба работает от имени другого пользователя (обычно "LocalSystem"). Это означает, что HKCU для вашего интерактивного сеанса и для службы указывают на различные разделы реестра. Для хранения данных используйте ключ "HKLM\Software\Your Company\Your Program" или запускайте службу от имени вашей учетной записи (это можно настроить в Панель Управления\Службы).

Загрузить исходный код
vb6svc.zip (34 кБ, интерфейс на английском языке)

Эта страница обновлялась в последний раз 06 июн 2004 г.
© 2004 Сергей Мерзликин
Пишите: