Форум программистов, компьютерный форум, киберфорум
Наши страницы
Jin X
Войти
Регистрация
Восстановить пароль
Оценить эту запись

Пара нюансов при создании службы (Windows Service) и не только

Запись от Jin X размещена 26.03.2019 в 16:27
Обновил(-а) Jin X 15.04.2019 в 21:14

Пара нюансов при создании службы (Windows Service) и не только

Пишу службу (Windows Service) на Delphi через VCL. В том же EXE-шнике делаю конфигуратор с оконным интерфейсом.
Получается так:
  • При запуске с опцией /install служба устанавливается (встроенная фича TServiceApplication).
  • При запуске с опцией /uninstall служба удаляется (встроенная фича TServiceApplication).
  • При запуске с опцией /config запускается конфигуратор.
  • Запуск службы происходит по специальной схеме (через тот же EXE-шник). Это внутренняя кухня модуля [Vcl.]SvcMgr.
Если вырезать интимные подробности, DPR-файл выглядит примерно так:
Delphi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
program ServiceTest;
 
uses
  System.SysUtils,
  Vcl.Forms,
  Vcl.SvcMgr,
  MyService in 'MyService.pas',
  ConfigForm in 'ConfigForm.pas';
 
{$R *.RES}
 
begin
  if FindCmdLineSwitch('CONFIG') then
  begin
    // Запуск конфигуратора
    Vcl.Forms.Application.Initialize;
    Vcl.Forms.Application.MainFormOnTaskbar := True;
    Vcl.Forms.Application.CreateForm(TFormConfig, FormConfig);
    Vcl.Forms.Application.Run;
  end
  else
  begin
    // Запуск сервиса или установщика сервиса
    if not Vcl.SvcMgr.Application.DelayInitialize or Vcl.SvcMgr.Application.Installing then
      Vcl.SvcMgr.Application.Initialize;
    Vcl.SvcMgr.Application.CreateForm(TMyService, MyService);
    Vcl.SvcMgr.Application.Run;
  end;
end.
Проблема №1

Оказывается, в VCL нет встроенных средств для запуска службы. Службу можно установить, а запускаться она будет только после перезагрузки компьютера. Довольно странно, но факт.
Решить эту проблему через WinAPI (если класс нашей службы называется TMyService):
Delphi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Запуск (Start = True) или остановка (Start = False) службы (метод наследника TService).
procedure TMyService.StartStopService(Start: Boolean);
var
  SvcMgr, Svc: SC_HANDLE;
  P: PChar;
  SS: TServiceStatus;
begin
  // Открываем диспетчер управления службами (SCM)
  SvcMgr := OpenSCManager(nil, nil, STANDARD_RIGHTS_REQUIRED);
  if SvcMgr <> 0 then
  begin
    // Открываем службу с именем Name (это свойство объекта класса-наследника TService)
    Svc := OpenService(SvcMgr, PChar(Name), SERVICE_ALL_ACCESS);
    if Svc <> 0 then
    begin
      // Запуск или остановка?
      if Start then
      begin
        P := nil;
        StartService(Svc, 0, P);  // запускаем службу
      end
      else
      begin
        FillChar(SS, SizeOf(SS), 0);
        ControlService(Svc, SERVICE_CONTROL_STOP, SS);  // останавливаем службу
      end;
      CloseServiceHandle(Svc);
    end;
    CloseServiceHandle(SvcMgr);
  end;
end;
Если нам нужно запускать службу сразу после её установки, делаем так:
Delphi
1
2
3
4
procedure TMyService.ServiceAfterInstall(Sender: TService);
begin
  StartStopService(True);
end;
Представьте себе, остановки при удалении (деинсталляции) службы тоже не происходит, поэтому делаем ещё и так:
Delphi
1
2
3
4
procedure TMyService.ServiceBeforeUninstall(Sender: TService);
begin
  StartStopService(False);
end;
Проблема №2

Каким же образом сообщить службе об изменении конфигурации (чтобы она прочитала её заново – из реестра или с диска)? Проще всего – создать глобальное именованное событие (CreateEvent) и через него сигнализировать (SetEvent) из конфигуратора службе (которая будет ждать его, либо периодически проверять, через WaitForSingleObject). Но тут нас поджидает засада: при попытке открыть из конфигуратора событие (OpenEvent), созданное службой, мы получим ошибку запрета доступа (GetLastError = ERROR_ACCESS_DENIED).

Это происходит из-за того, что объект (событие) создан "системой" (службы запускаются от имени "системы"), а открывается администратором (если конфигуратор находится в том же EXE – установить/удалить службу может только администратор) или даже обычным пользователем (если конфигуратор находится в другом EXE-шнике). Тоже самое будет при попытке открыть обычным пользователем объект, созданный администратором.

Что же делать? Сначала я решил пойти по странному пути: создать объект в конфигураторе, а в службе периодически пытаться открыть его, и после успешного открытия начать проверять состояние ("система" может открыть объект, созданный администратором или обычным пользователем). Но потом эта идея мне показалась костыльной, и я решил разобраться, как создать объект в службе, не защищённый от доступа админов и обычных юзеров.

Вот так это делается (в службе):
Delphi
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Создать событие с именем Name в глобальном пространстве имён, доступный любым процессам.
function CreateUnprotectedEvent(const Name: String): THandle;
var
  SA: TSecurityAttributes;
  SD: TSecurityDescriptor;
begin
  Result := 0;
  // Создаем дескриптор безопасности
  if InitializeSecurityDescriptor(@SD, SECURITY_DESCRIPTOR_REVISION) and
     // DACL не установлен, объект не защищён
     SetSecurityDescriptorDacl(@SD, True, nil, False) then
  begin
    // Настраиваем атрибуты безопасности, передавая указатель на дескриптор безопасности
    SA.nLength := SizeOf(SA);
    SA.lpSecurityDescriptor := @SD;
    SA.bInheritHandle := False;
    // Создаём событие
    Result := CreateEvent(@SA, True, False, Pointer('Global\' + Name));
  end;
end;
(разумеется, при необходимости создания нескольких событий нет смысла создавать и настраивать несколько дескрипторов и атрибутов безопасности, можно использовать одни и те же; при завершении работы ничего, кроме события, закрывать или освобождать не нужно)

Как вы понимаете, это всё можно делать не только при создании служб и не только для создания событий. Если вам не нравится взаимодействие через события, и вы хотите иметь возможность передавать какие-то данные, можете создать, к примеру, разделяемую память. В общем, простор для творчества есть
Всего комментариев 0
Комментарии
 
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2019, vBulletin Solutions, Inc.
Рейтинг@Mail.ru