Создание Windows сервисов средствами FPC и Lazarus |
03.04.2009 Лагунов А.А. |
Существует целый класс специальных программ, которые по логике своей работы не требуют постоянных ответных действий от оператора. И это самые хорошие для конечного пользователя программы, так как они работают сами.
В Linux и прочих клонах UNIX они называются демонами, в Windows это службы. Хотя название этого класса программ в windows и Unix-подобных системах различно — принцип работы их одинаков. Программа запускается в фоновом режиме и не требует, чтобы оператор совершал при этом какие-либо действия.
Компилятор FPC имеет в своём составе прекрасные библиотеки для облегчения создания таких программ. IDE Lazarus с помощью своих визуальных средств — большое подспорье в этом интересном деле.
Процесс создания в общем не зависит от операционной системы, полностью проявляя главный девиз Lazarus: «Пишем код один раз — компилируем везде».
Сначала подготовим Lazarus к работе — этот этап общий для любой операционной системы, где он запускается.
Для этого необходимо установить в Lazarus дополнительные компоненты для создания сервисов. То есть необходимо открыть пакет lazdaemon.lpk и установить его. Он находится в каталоге стандартных компонентов — $lazarus/components/daemon ($lazarus — это та папка, куда установлен IDE lazarus). После успешной компиляции и перезапуска IDE в меню создания нового проекта (меню «Проект/Создать новый проект») будет доступен новый тип проекта (Daemon (service) Application:
А в меню «Файл/Создать...» будет доступно создание новых объектов (Daemon Application, Daemon Module, Daemon Mapper):
Пункт меню «Файл/Создать...» необходим в том случае, если планируется в одном исполняемом модуле разместить несколько служб.
При создании нового проекта будет открыто автоматически 2 невизуальных формы - класс TDaemon и TDaemonMapper.
TDaemon — непосредственно экземпляр сервиса. Именно этот объект реализует сам сервис (его мы видим в Windows в «Управление компьютерами/Сервисы»).
TDaemonMapper — это компонент, отвечающий за управление работой сервисов. Особенно актуален он в случае включения в один исполняемый модуль нескольких сервисов.
Рассмотрим свойства и события, доступные у TDaemonMapper.
Уникальным свойством компоненты TDaemonMapper является DaemonDefs.
property DaemonDefs : TDaemonDefs;Именно оно определяет, какие сервисы содержит исполняемый модуль, производит регистрацию сервисов в системе. Это свойство — коллекция элементов. Ниже, в п.3.3 подробно рассматривается элемент, который может содержаться в данной коллекции.
Стоит обратить внимание на несколько событий используемой компоненты.
property OnCreate : TNotifyEvent;Событие, которое возникает в момент запуска исполняемого модуля программы и создания экземпляра объекта TDaemonMapper. Полезно,например, когда необходимо при запуске считать значения некоторых переменных из конфигурационного файла, подготовить какие либо структуры данных и т.п.
property OnDestroy : TNotifyEvent;Событие, которое возникает в момент завершения работы исполняемого модуля программы и уничтожения экземпляра объекта TDaemonMapper. Используется для «уборки» после работы программы: освобождение ресурсов, закрытие файлов и т.п.
property OnRun : TNotifyEvent;Событие возникает при запуске на выполнение исполняемого файла с нестандартными параметрами запуска или вообще без параметров. Стандартными считаются 3 параметра командной строки: -i, -r, -u (подробнее параметры расписаны в Приложение А. Стандартные параметры командной строки)
property OnInstall : TNotifyEvent;Событие возникает в момент регистрации сервисов в системе — запуск программы с ключом -i.
property OnUnInstall : TNotifyEvent;Событие возникает в момент удаления регистрационной информации о сервисе из системы — запуск программы с ключом -u.
Экземпляры данного класса хранятся в свойстве-коллекции DaemonDefs класса TDaemonMapper.
property DaemonClassName : String;Свойство содержит имя типа регистрируемого сервиса — например TDaemon1. На данном этапе Lazarus не позволяет визуально выбрать значение — пока можно только вписать вручную. Но, я надеюсь, в ближайшее время будет реализован редактор.
property Name : String;Наименование сервиса
property DisplayName : String;Наименование сервиса — отображается в окне ОС просмотра списка зарегистрированных сервисов.
property RunArguments : String;Список дополнительных аргументов командной строки, которые будут передаваться указанному сервису в момент старта сервиса.
property Options : TDaemonOptions;Параметры работы сервиса — может ли сервис быть остановлен или приостановлен вручную, может ли он взаимодействовать с рабочим столом оператора (см. Приложение В. Параметры сервиса — TDaemonOption)
property Enabled : Boolean;Признак доступности сервиса. Если это свойство включено, то в момент запуска исполняемого файла с ключом регистрации сервисов (-i), сервис будет зарегистрирован. В противном случае — сервис не регистрируется. Аналогичное поведение и при деинсталяции сервисаов. Этот параметр удобно менять в момент создания экземпляра TDaemonMapper из его соответствующего события OnCreate.
property WinBindings : TwinBindings;Сложное свойство, отвечает за параметры регистрации сервиса в Windows системах:
property Dependencies : TDependencies;Список служб, от которых зависит данная служба.
property GroupName : String;Наименование группы, к которой относится служба.
property Password : String;Пароль учётной записи, от имени которой производится запуск данной службы (пустой при запуске от System).
property UserName : String;Наименование учётной записи, от имени которой производится запуск данной службы.
property StartType : TStartType;Тип запуска службы — автоматический или ручной. Допустимые значения приведены ниже (см. Приложение Б. Типы запуска служб).
property WaitHint : Integer;Временной интервал, который необходим службе для выполнения действий. Например, время необходимое для старта службы. По истечении этого времени будет сгенерированно сообщение о том, что служба не отвечает на внешние запросы. Если в качестве значения указать 0, то ожидание не прерывается.
property ServiceType : TServiceType;Определяется тип службы — либо просто программа, либо драйвер устройства или файловой системы. В данной статье рассматриваются только службы с типом stWin32.
property LogStatusReport : Boolean;Писать в файл протокола факт изменения состояния сервиса (запуск / приостановка / остановка)
property OnCreateInstance : TNotifyEvent;Это событие возникает в момент создания самого экземпляра сервиса.
Эта невизуальная компонента является реализацией сервиса. Физически при запуске сервиса создаётся дополнительный поток в составе приложения, который и является основной рабочей нитью сервиса. Этот поток вызывает методы компонента, потомка TDaemon, по мере поступления от операционной системы соответствующих сигналов.
Ниже приведен перечень свойств этого компонента:
property Status;Свойство содержит текущее состояние сервиса.
property OnStart : TDaemonOKEvent;Событие возникает в момент начала выполнения сервиса.
property OnStop : TDaemonOKEvent;Событие возникает в момент остановки сервиса.
property OnPause : TDaemonOKEvent;Событие возникает в момент приостановки работы сервиса.
property OnContinue : TDaemonOKEvent;Событие возникает в момент возобновления работы сервиса.
property OnShutDown : TDaemonEvent;Событие возникает в момент выгрузки сервиса.
property OnExecute : TDaemonEvent;Событие возникает в момент начала выполнения сервиса.
property BeforeInstall : TDaemonEvent;
property AfterInstall : TDaemonEvent;
property BeforeUnInstall : TDaemonEvent;
property AfterUnInstall : TDaemonEvent;События, возникают до и после момента установки и деинсталяции сервиса соответственно.
property OnControlCode : TcustomControlCodeEvent;Это событие возникает для обработки не стандартного сообщения от системы (не имеющего типового обработчика).
Для примера рассмотрим создание сервиса, который будет определять наличие файла в указанной папке по специальной маске, и если такой файл или файлы будут найдены — сохранит информацию об этом в базу данных FireBird.
Внешний вид окна разработки сервиса приведён на рисунке ниже:
Разместим в окне сервиса компоненты доступа к базе данных (UIBDataBase1, UIBQuery1, UIBTransaction1). Для периодического сканирования каталога, в котором могут находится и появляться в течение работы сервиса файлы, будем использовать таймер (Timer1).
Создадим обработчики на события сервиса:
Теперь приступим к реализации основного рабочего кода сервиса — сканирование каталога и сохранение результата сканирования в базе данных. Запуск процесса сканирования будет происходит по сигналу таймера — поэтому пишем его обработчик. Полный текст всего примера есть в прикреплённом архиве — а тут приведу только часть кода:
procedure TTestDaemon.Timer1Timer(Sender: TObject); begin try DoScanFolder; if UIBTransaction1.InTransaction then //Если файлы были обнаружены - то транзакция будет запущена - //комитем данные в базу UIBTransaction1.Commit; except on E:Exception do begin //Если файлы были обнаружены - то транзакция будет запущена - //откатим всё if UIBTransaction1.InTransaction then UIBTransaction1.RollBack; //Чтобы наш сервис продолжал работать - //сообщение об ошибке запишем в протокол //А саму ошибку - подавим Application.Logger.Log(E.Message); end; end; end; procedure TTestDaemon.DoScanFolder; var R:TSearchRec; Code:integer; begin Code:=FindFirst(FScanFolder+FScanMask, faAnyFile, R); try while Code=0 do begin if (R.Attr and faDirectory) = 0 then begin DoSaveFileNameToBD(R.Name); DeleteFile(FScanFolder+R.Name); end; FindNext(R); end; finally FindClose(R); end; end; procedure TTestDaemon.DoSaveFileNameToBD(AFileName: string); begin if not UIBTransaction1.InTransaction then UIBTransaction1.StartTransaction; UIBQuery1.Params.ByNameAsString['sys_log_name']:=AFileName; UIBQuery1.ExecSQL; end;
Таким образом, функционал всего сервиса обеспечивается 3-мя методами.
В процедуре обработчике таймера вызываем процедуру сканирования тестового каталога, после завершения этой процедуры, если была начата транзакция записи данных, то подтверждаем транзакцию и записываем данные в базу.
Если же при выполнении кода была ошибка — то она будет подавлена. Работа сервиса прервана не будет, но факт ошибки будет записан в файле протокола.
Метод DoScanFolder выполнит сканирование указанного каталога. Если при этом будут обнаружены файлы удовлетворяющие условию, их названия будут записаны в БД программы с помощью процедуры DoSaveFileNameToBD, а затем, после успешной записи — удалены.
Метод DoSaveFileNameToBD просто проверяет была ли начата транзакция в данной итерации, если нет — то открывает её и записывает данные в базу.
Рассмотрим создание сервиса с помощью FPC и Lazarus для использования его в OS Windows. Само создание сервисов не отличается от описанного выше.
Специфика начинается в момент установки. Фактически установку созданного сервиса можно проводить из любого каталога, но если сервис будет запускаться от специальной учётной записи, необходимо отследить возможность этой учётной записи работать с папкой, где расположены файлы сервиса (исполняемые и вспомогательные). Также для этой учётной записи должна быть доступна на чтение и удаление папка, в которой сервис будет производить поиск файлов по маске.
Весь процесс установки сервиса сводится к запуску исполняемого файла с ключом командной строки -i. После выполнения этой команды сервис зарегистрирует себя в реестре Windows. Можно открыть окно управления компьютером и запустить сервис.
Для деинсталляции сервиса необходимо просто запустить сервис с ключом -u.
По умолчанию протоколирование ведётся в системный каталог — %SYSTEM_ROOT%system32<Имя программы>.log.
Данная статья написана с использованием Lazarus версии 0.9.27 (18965) и FPC версии 2.3.1. Но эти же примеры должны заработать и на предыдущих версиях (не очень старых). Лично для меня эта статья была инициирована необходимостью написания сервиса рассылки СМС сообщений. Так что всё проверено опытным путём.
Параметр | Длинная форма параметра | Описание |
-r | --run | Запуск сервисов на выполнение |
-i | --install | Регистрация сервисов в системе |
-u | --uninstall | Удаление регистрационной информации о сервисе |
Тип запуска | Описание |
stBoot | Используется только для драйверов устройств. Для обычных служб эквивалентно stAuto. |
stSystem | Используется только для драйверов устройств. Для обычных служб эквивалентно stAuto. |
stAuto | Служба будет запущена в момент старта операционной системы. |
stManual | Запуск данной службы производится вручную либо оператором, либо по требованию другой службы. |
stDisabled | Запуск данной службы запрещён. |
Параметр | Описание |
doAllowStop | Разрешена ручная остановка сервиса |
doAllowPause | Разрешено приостанавливать сервис |
doInteractive | Сервису разрешено взаимодействовать с рабочим столом оператора |