Создание 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 | Сервису разрешено взаимодействовать с рабочим столом оператора |
| FPC | 3.2.2 | release |
| Lazarus | 3.2 | release |
| MSE | 5.10.0 | release |
| fpGUI | 1.4.1 | release |