Для кого предназначена эта статья: в статье рассмотрим простейший вариант создания службы для Windows, основной работой которой будет запуск 1С77 через OLE и вызов функций из полученного объекта 1С. Таким образом мы убъем сразу два зайца - научимся создавать службы и научимся подключать в наш проект через COM интерфейс сторонние приложения.

Сначала небольшое вступление. Что такое "служба"? Это знакомое нам "приложение" с расширением ".exe", но имеющее 2 отличия от привычных программ:

  • Не имеет формы, кнопок и прочих визуальных элементов управления, что понятно ведь приложение как правило будет работать в фоновом режиме, независимо от пользователей в системе, что в общем и требуется от службы.
  • Приложение "служба" обязано отвечать нескольким требованиям, поэтому обычное (даже консольное) приложение не подойдет. Службы запускаются и останавливаются через специальную консоль*: "Панель управления - Администрирование - Службы". Обычное приложение не содержит кода способного обрабатывать команды запуска и остановки службы.

*Консоль MMC (Microsoft Management Console) группирует средства администрирования, которые используются для администрирования компьютеров, служб, других системных компонентов и сетей.

Информация о каждой службе хранится в реестре - в ключе HKLM\SYSTEM\CurrentControlSet\Services\ServiceName. Там содержатся следующие сведения:

  • Тип службы. Указывает на то, реализована ли в данном приложении только одна служба (эксклюзивная) или же их в приложении несколько. Эксклюзивная служба может работать в любом контексте безопасности. Несколько служб внутри одного приложения могут работать только в контексте LocalSystem.
  • Тип запуска. Автоматический - служба запускается при старте системы. По требованию - служба запускается пользователем вручную. Деактивированный - служба не может быть запущена.
  • Имя исполняемого модуля (EXE-файл, параметр ImagePath).
  • Порядок запуска по отношению к другим службам. В некоторых случаях для корректной работы службы требуется, чтобы была запущена одна или несколько других служб. В этом случае в реестре содержится информация о службах, запускаемых перед данной.
  • Контекст безопасности выполнения службы (сетевое имя и пароль). По умолчанию контекст безопасности соответствует LocalSystem.

Если вы еще не знаете что такое MSDN, то похвалить пока вас пока не получится :), обязательно пройдите по указанной ссылке и ознакомьтесь с сайтом-помощником от Microsoft. Поверьте, нет ничего лучше чем прочитать пару тройку статей в первоисточнике. Когда пять первых ссылок в google выдают на ваш вопрос что то не очень понятное, это должно означать для вас только одно - пора обратиться к первоисточнику. Вкратце MSDN - огромная база знаний, в которой есть все что касается разработки для Windows, когда вам необходимы функции предоставляемые самой Windows и многое другое.

Вот что написано в MSDN про службы: Службы Microsoft Windows, ранее называвшиеся службами NT, позволяют создавать исполняемые приложения, работающие продолжительное время и выполняющиеся в отдельной сессии Windows. Эти службы не содержат элементов пользовательского интерфейса, могут быть остановлены и запущены повторно и способны автоматически запускаться при загрузке компьютера. Эти особенности делают службы идеальным средством для использования на сервере, а также в ситуациях, когда необходима долговременно работающая функциональность, не мешающая работе пользователей на том же компьютере. Кроме того, службы могут запускаться в контексте безопасности конкретной учетной записи Windows, отличающейся от текущего пользователя или учетной записи компьютера по умолчанию.

Пример службы "Источник бесперебойного питания", как она представлена в реестре, обратите внимание на то что служба это приложение ups.exe:

Шаг 1. Создаем новый проект

Открываем нашу папку для проектов "delphi_projects" и создаем подпапку для нового проекта, у меня получилось так: "C:\delphi_projects\006_service".

Открываем Delphi, закрываем открывшееся приложение через "File - Close All", создаем новый проект нужного нам типа: File - New - Other - Service Application.

Перед нами открывается новый проект, скомпилировав который через Ctr-F9 мы получим EXE файл приложения, которое и будет являться службой. Дабы сразу порадовать себя любимого, мы так и поступим: сохраняем проект в нашу папку 006_service, заходим в ObjIns в делфи и изменим свойства службы:

  • Переименуем службу, свойство Name="service1c"
  • Зададим имя для пользователя, свойство DisplayName="Управление приложением 1С77"

 


Компилируем проект Ctrl-F9, получаем EXE, который теперь надо зарегистрировать в системе как службу, а именно прописать в реестре. К счастью, служба созданная в Delphi может это сделать сама, надо только запустить нашу службу однократно с параметром "/install", например через "Пуск - Выполнить" пишем полный путь к EXE + параметр:

После чего должно появиться сообщение "Service installed successfully", заходим в консоль управления службами и наблюдаем за нашей, пока еще ничего не делающей, службой:

Заметьте, что служба запустилась и работает с указанного произвольного пути, но рабочий вариант желательно все же скопировать к собратьям в system32 и регистрировать ее оттуда, все таки служба - серьезное приложение, не допускающее промашек.

Шаг 2. Подготовим тестовую базу 1С77

Основная идея - запускать 1С77 через N секунд, подключаться к определенной базе и делать запись в один из справочников путем вызова глобальной процедуры в приложении 1С77.

Подготовим базу 1С:

  • Создаем папку внутри папки с проектом у меня так "C:\delphi_projects\006_service_02\1C_test_service"
  • Запускаем 1С77 и добавляем новую базу в список, папка базы та же.
  • Запускаем новую базу в режиме "конфигуратора", выбираем формат DBF, новая база создана.
  • Для теста нам достаточно ввести один справочник и в глобальном модуле сделать одну процедуру, которую и будем вызывать из нашей службы. Создадим новый справочник "ОлеВыгрузки" и добавим в него 3 реквизита "Дата, Время, Комментарий". В глобальном модуле создадим одну процедуру "Процедура ЗапускСервиса() Экспорт", в ней будем создавать один элемент в нашем справочнике при каждом вызове процедуры
     
  • Сохраняем проект 1С, создаем одного пользователя UserAdmin, с паролем 456123; добавим еще пользователя user2, войдя в 1С под ним будем любоваться на появляющиеся записи в нашем справочнике, сохраняем список пользователей, закрываем конфигуратор.

Шаг 3. Запускаем 1С из сервиса

Вернемся в Delphi, теперь надо создать новую процедуру, в которой будем подключаться к 1С и организовать периодический ее вызов. Первое что надо сделать это добавить в раздел Uses ссылки на модули которые нам понадобятся "ComObj, Variants, ActiveX", эти модули нам нужны т.к. запускать 1С планируется через интерфейс COM:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, SvcMgr, Dialogs, ComObj, Variants, ActiveX;

Для того чтобы заставить службу выполнять ваш код существует два варианта:

  • Прописать весь код в процедуре события OnExecute, сделать бесконечный цикл, перед каждой итерацией делать проверку на остановку службы, как только произойдет прерывание цикла служба остановится.
  • Создать один или несколько потоков при старте службы, в событии OnStart, при этом надо не забыть их остановить, при получении соответствующего уведомления об остановке службы.

Рассмотрим первый вариант выполнения полезного кода. Идем в Delphi, вызываем визуальную форму нашей компоненты и создаем процедуру для события OnExecute (ctrl+F12 - Unit1 - F12 - открывается форма - ObjIns - закладка Events - двойной клик в событие OnExecute), автоматически создается процедура, в которой прописываем наш цикл:

procedure Tservice1c.ServiceExecute(Sender: TService);
begin 
const
  SecBetweenRuns = 5;
var
  Count: Integer;
begin
  Count := 0;
  while not Terminated do
  begin
    Inc(Count);
    if Count >= SecBetweenRuns then
    begin
      Count := 0;
      {Здесь мы вызываем нашу процедуру, которая будет содержать полезный код}
      CreateElement(); //наша процедура, объявим ее позже
    end;
    Sleep(1000);
    ServiceThread.ProcessRequests(False);
  end;
end;

Итак, мы имеем бесконечный цикл, в котором установлена задержка в 1 секунду, каждая 5-я итерация рабочая, остается подключиться к 1С77 и вызвать процедуру из глобального модуля. Создаем переменную нужного типа.

var
...
App1c: Olevariant;

Создаем процедуру коннекта к 1С:

procedure Start1C();
begin
    CoInitialize(nil); //инициализация com-среды
    App1c := UnAssigned; //на случай если уже запущена 1С обнулим
    App1c := CreateOleObject('V77.Application'); //создаем объект-приложение 1С
    App1c.initialize(App1c.rmtrade,'/DC:\delphi_projects\006_service\1C_test_service /NUserAdmin /P456123','NO_SPLASH_SHOW');  //подключаемся к одной из баз
end;

Рассмотрим подробней про параметры Initialize(Объект.RMTrade, КоманднаяСтрока, ПустаяСтрока), всего их три:

  • "Объект": Идентификатор созданного OLE объекта 1С:Предприятие. "RMTrade": Добавочное ключевое слово.
  • "КоманднаяСтрока": Строковое выражение — командная строка запуска 1С. В строке могут использоваться параметры: "/D" - путь к базе, "/N" - пользователь базы, "/P" - пароль пользователя.
  • "ПустаяСтрока": Строковое выражение. Параметр может содержать пустую строку или строковое значение "NO_SPLASH_SHOW" — отключить заставку при запуске системы 1С.

Далее объявляем нашу процедуру CreateElement(), в которой будем вызывать функции 1С:

procedure CreateElement();
begin

try
    App1c.EvalExpr('ЗапускСервиса()'); //объект App1c содержит активное подключение к 1С, а "ЗапускСервиса()" это процедура из глобального модуля 1С!
    //MessageBeep(MB_OK); //можно использовать сигнал для проверки работы службы
except
    Start1C();
end;

end;

Воспользуемся функциями старта и остановки сервиса для того чтобы создать и уничтожить переменные:

procedure Tservice1c.ServiceStart(Sender: TService; var Started: Boolean);
begin
  Start1C();
end;

procedure Tservice1c.ServiceStop(Sender: TService; var Stopped: Boolean);
begin
  App1c.ExitSystem(0);
  App1c := UnAssigned;
end;

Все готово! Компилируем проект Ctrl+F9, регистрируем наш сервис через пуск-выполнить "путь + /install"

Шаг 4. Проверка работы службы

Заходим в "Управление компьтером" переходим в службы (можно это сделать и через панель управления) и меняем пользователя под которым запускается служба на вашего пользователя windows, если этого не сделать то служба зависнет при включении. Дело в том что 1С запустившись из нового для пользователя каталога открывает окно регистрации новой базы, чтобы внести ее в реестр, а этого окна вы не увидите (запущена то она по умолчанию под другим пользователем!) и произойдет затык. После смены пользователя ("вход от имени") запускаем службу. Затем открываем 1С77, заходим в нашу пустую базу под пользователем user2, которого мы предварительно создали в шаге 2 и через меню "Операции - Справочники" открываем наш единственный справочник. Если все сделали правильно, то через каждые 5 секунд в справочнике будет появляться новая запись, а если перезапустить копьютер, то в диспечере задач сразу после запуска windows будет появляться процесс 1cv7s.exe, что будет являться показателем того что наша служба работает.

У меня их создалось уже много, т.к. службу установил уже давненько, пока писал статью служба не дремала, насоздавала мне несколько тысяч элементов :).

Если элементы не появляются, то попробуйте закоментировать открытие 1С, вместо этого раскомментируйте звуковой сигнал, если сигнал периодически срабатывает - проблема в вашей базе 1С, если не работает - что-то не так с самой службой.

Шаг 5. Разбор полетов, свойства и методы службы

Свойства службы

Name — это основное имя службы, должно быть уникальным в системе, так что его обязательно нужно сменить на что-либо другое. Под этим именем мы будем регистрироваться в реестре, под этим именем будем писать логи, с этим именем будем оперировать, общаясь со службой программно, и т.п. В имени службы рекомендуется использовать только латинские буквы и цифры.

DisplayName — а вот это читабельное название службы. Именно оно отображается в оснастке "Службы". Это та строка, которую обычно видит пользователь (системный администратор). Если оно не задано, то вместо него берётся имя службы из свойства Name. Для службы можно задать более подробное описание, но в Delphi нет подходящего свойства, поэтому задание описания надо будет сделать руками (см. пункт 8).

AllowPause, AllowStop — говорят сами за себя: можно ли приостанавливать и останавливать службу соответственно. Отвечают за доступность соответствующих кнопок в оснастке "Службы". Если вы назначите соответствующие обработчики событий (см. ниже), то эти свойства автоматически переключатся в True. Заметим, что эти свойства не влияют на возможность отправки сообщений вашей службе. Т.е. даже, если вы указали AllowPause = False, никто не запрещает стороннему приложению отправить вашей службе сообщение приостановки SERVICE_CONTROL_PAUSE.

StartType — тип запуска службы по умолчанию. Имеет значение только при установке службы. В остальное время не используется. Отвечает только за то, какой тип запуска будет у вашей службы при её самоустановке. Имеет смысл менять значения только на Auto (автоматический запуск при старте системы), Manual (запуск только по требованию, например, по указанию пользователя или во время запуска зависимой службы) и Disabled (запуск запрещён). Остальные имеют смысл только для драйверов. Ещё раз заметим, что независимо от типа запуска, при самоустановке служба добавляется в остановленном состоянии.

ServiceStartName и Password — имя учётной записи пользователя и её пароль для запуска службы из-под неё. Обычно оставляют пустыми — в этом случае служба пускается из-под учётной записи LocalSystem. Аналогично StartType, эти свойства используются только во время самоустановки службы. Служба, работающая под LocalSystem, по умолчанию может свободно выполнять операции, обычно запрещённые для других учётных записей. Заметим, что при работе из-под LocalSystem, службе не следует обращаться к ключу реестра HKEY_CURRENT_USER (да и вообще, работа с любыми объектами пользователя из службы часто оказывается плохой идеей). Кроме того, нужно быть аккуратным с разделом HKEY_CLASSES_ROOT, т.к. это виртуальный раздел, часть которого берётся из профиля текущего пользователя (HKEY_CURRENT_USER), а часть — из общесистемного (HKEY_LOCAL_MACHINE).

ErrorSeverity — насколько серьёзны сбои в службе. Ignore — пользователя не уведомлять, в журнал не заносить; Normal (по умолчанию) — уведомить пользователя, занести событие в журнал; Severe — если последняя удачная конфигурация еще не используется, загружается именно эта конфигурация. Если она уже используется, загрузка продолжается; Critical — если последняя удачная конфигурация еще не используется, загружается именно эта конфигурация. Если она уже используется, загрузка останавливается и выдаётся синий экран смерти.

Interactive — сервис может взаимодействовать с интерфейсом пользователя: может выводить на консоль окна и принимать ввод от пользователя (так называемые "интерактивные" службы). Не рекомендуется устанавливать в True. Более того, в Windows Vista интерактивные службы запрещены по умолчанию. Интерактивный процесс может быть запущен только из-под учётной записи LocalSystem (при установке свойства в True автоматически сбрасываются свойства ServiceStartName и Password и наоборот).

LoadGroup и Dependencies — используются для указания порядка загрузки, если это важно для вашей службы. В "LoadGroup" вписывается имя группы, в которую будет входить ваша служба (если будет). Примеры можно посмотреть в реестре: HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\ServiceGroupOrder. Т.е. фактически тут определяется, ДО каких служб должна быть запущена ваша служба. Если вы, к примеру, впишете туда "NetBIOSGroup", то службы, зависящие от "NetBIOSGroup" будут ожидать загрузки вашей службы (на самом деле, всё немного сложнее, но не будем на первый раз слишком сильно углубляться в подробности). "Dependencies" же определяет, какие службы должны быть запущены до вас. При добавлении в список зависимости вы должны указывать свойство IsGroup — является ли имя, введённое в Name, именем группы или же это имя службы. Зависимость от группы трактуется следующим образом: будет произведена попытка запуска каждой службы из группы. Ваша служба будет запущена, если хотя одна служба из указанной группы оказалась запущенной. Такая вот весьма странная логика запуска.

Для новой службы обычно требуется задать Name, DisplayName и StartType.

События службы

OnAfterInstall, OnAfterUninstall, OnBeforeInstall, OnBeforeUninstall — эти события возникают после и до регистрации и удаления службы в системе (разумеется речь идёт только о самоустановке и самоудалении). Чаще всего в этих событиях происходит старт/останов службы, а также заполнение/чистка реестра (например, текстовое описание службы или регистрация источника сообщений для службы, см. ниже).

OnCreate и OnDestroy — ну с этим ясно, возникают при создании и удалении модуля данных, представляющего службу.

OnShutdown — возникает при завершении работы системы. Все исключения в обработчике этого события игнорируются. Exe-приложение будет завершено сразу после выхода из обработчика OnShutdown. Кстати, во время завершения работы системы это событие рассылается всем службам сразу. Поэтому, если ваша служба зависит от ещё какой-то службы, то имейте в виду, что служба, от которой вы зависите, при завершении работы системы может остановиться даже раньше, чем вы получите уведомление OnShutdown.

OnExecute - возникает после запуска службы, процедура основного потока службы.

OnStart, OnStop, OnPause, OnContinue — события возникают при запуске службы, остановке, паузе и команде продолжить. События, кстати, весьма напоминают поведения "потока" TThread и не спроста, сама служба построена как минимум на одном потоке, но есть возможность добавлять несколько своих, если это надо.

Исходный код и тестовая база доступны для скачивания по ссылке.