Головна частина програми, Система, Delphi, статті

Як вже зазначалося – сервіс це звичайна програма. Програма в Pascal “е знаходиться між begin і end. Після запуску нашого сервісу (тут і далі під запуском сервісу розуміється саме запуск його з Менеджера сервісів, а не просто запуск exe “шника сервісу) менеджер сервісів чекає поки наш сервіс викличе функцію StartServiceCtrlDispatcher.Ждать він буде недовго – якщо в нашому exe “шнику кілька сервісів то секунд 30, якщо один – близько секунди, тому поміщаємо виклик StartServiceCtrlDispatcher ближче до begin.


StartServiceCtrlDispatcher як аргумент вимагає
_SERVICE_TABLE_ENTRYA, Тому додаємо в var DispatchTable : array [0 .. кол-во сервісів] of _SERVICE_TABLE_ENTRYA; і заповнюємо цей масив (Природно перед викликом StartServiceCtrlDispatcher).


Т.к. в нашому ехешніке буде 1 сервіс, то заповнюємо його так:

 DispatchTable[0].lpServiceName:=ServiceName;
DispatchTable[0].lpServiceProc:=@ServiceProc;
DispatchTable[1].lpServiceName:=nil;
DispatchTable[1].lpServiceProc:=nil;

Раджу завести константи ServiceName – ім’я сервісу і ServiceDisplayName – псевдонім.

ServiceProc – основна функція сервісу (про неї нижче), а в функцію ми передаємо її адресу.

В DispatchTable [кол-во сервісів] все одно nil – це показує функції, що попереднє поле було останнім. У мене вийшло так:

begin
DispatchTable[0].lpServiceName:=ServiceName;
DispatchTable[0].lpServiceProc:=@ServiceProc;
DispatchTable[1].lpServiceName:=nil;
DispatchTable[1].lpServiceProc:=nil;
if not StartServiceCtrlDispatcher(DispatchTable[0])
then LogError(“StartServiceCtrlDispatcher Error”);
end.

StartServiceCtrlDispatcher виконається тільки після того, як всі сервіси будуть зупинені.

Функція LogError протоколює помилки – напишіть її самі. <


Функція ServiceMain


ServiceMain – основна функція сервісу. Якщо в ехешніке кілька сервісів, але для кожного сервісу пишеться своя ServiceMain функція. Ім’я функції може бути будь-яким! і передається в DispatchTable.lpServiceProc: = @ ServiceMain (см.предидущущій абзац). У мене вона називається ServiceProc і описується так:

procedure ServiceProc(argc : DWORD;var argv : array of
PChar);stdcall;

argc кол-во аргументів і їх масив argv передаються менеджером сервісів з налаштувань сервісу. НЕ ЗАБУВАЙТЕ STDCALL! Така забудькуватість – часта причина помилки в програмі.


В ServiceMain потрібно виконати підготовку до запуску сервісу і зареєструвати обробник повідомлень від менеджера сервісів (Handler). Знову після запуску ServiceMain і до запуску RegisterServiceCtrlHandler має пройти мінімум часу. Якщо сервісу треба робити щось дуже довго і обов’язково до виклику RegisterServiceCtrlHandler, то треба посилати повідомлення
SERVICE_START_PENDING функии SetServiceStatus.


Отже, в RegisterServiceCtrlHandler передаємо назва нашого сервісу та адреса функції Handler “а (см.далее). Далі виконуємо підготовку до запуску та налагодження сервісу. Зупинимося на налаштуванні детальніше.

Ця сама настройка var ServiceStatus : SERVICE_STATUS;

(ServiceStatusHandle: SERVICE_STATUS_HANDLE і ServiceStatus треба зробити глобальними змінними і помістити їх вище всіх функцій).














dwServiceType – Тип сервісу
SERVICE_WIN32_OWN_PROCESS Одиночний сервіс
SERVICE_WIN32_SHARE_PROCESS Кілька сервісів в одному процесі
SERVICE_INTERACTIVE_PROCESS інтерактивний сервіс (може взаємодіяти з користувачем).

Решта константи – про драйвери. Якщо треба – дивіться їх в MSDN.

















dwControlsAccepted – Прийняті повідомлення (які повідомлення ми будемо обробляти)
SERVICE_ACCEPT_PAUSE_CONTINUE призупинення / перезапуск
SERVICE_ACCEPT_STOP зупинка сервісу
SERVICE_ACCEPT_SHUTDOWN перезавантаження комп’ютера
SERVICE_ACCEPT_PARAMCHANGE зміна параметрів сервісу без перезапуску (Win2000 і вище)

Решта повідомлення дивіться знову ж таки в MSDN (куди вже без нього 😉



Після заповнення цієї структури посилаємо наш новий статус функцією SetServiceStatus і ми працюємо :).


Після цього пишемо код самого сервісу. Я повернуся до цього пізніше.

Ось так виглядає моя ServiceMain:

procedure ServiceProc(argc : DWORD;var argv : array of PChar);stdcall;
var
Status : DWORD;
SpecificError : DWORD;
begin
ServiceStatus.dwServiceType := SERVICE_WIN32;
ServiceStatus.dwCurrentState := SERVICE_START_PENDING;
ServiceStatus.dwControlsAccepted := SERVICE_ACCEPT_STOP
or SERVICE_ACCEPT_PAUSE_CONTINUE;
ServiceStatus.dwWin32ExitCode := 0;
ServiceStatus.dwServiceSpecificExitCode := 0;
ServiceStatus.dwCheckPoint := 0;
ServiceStatus.dwWaitHint := 0;
ServiceStatusHandle :=
RegisterServiceCtrlHandler(ServiceName,@ServiceCtrlHandler);
if ServiceStatusHandle = 0 then WriteLn(“RegisterServiceCtrlHandler Error”);
Status :=ServiceInitialization(argc,argv,SpecificError);
if Status <> NO_ERROR
then begin
ServiceStatus.dwCurrentState := SERVICE_STOPPED;
ServiceStatus.dwCheckPoint := 0;
ServiceStatus.dwWaitHint := 0;
ServiceStatus.dwWin32ExitCode:=Status;
ServiceStatus.dwServiceSpecificExitCode:=SpecificError;
SetServiceStatus (ServiceStatusHandle, ServiceStatus);
LogError(“ServiceInitialization”);
exit;
end;
ServiceStatus.dwCurrentState :=SERVICE_RUNNING;
ServiceStatus.dwCheckPoint :=0;
ServiceStatus.dwWaitHint :=0;
if not SetServiceStatus (ServiceStatusHandle,ServiceStatus)
then begin
Status:=GetLastError;
LogError(“SetServiceStatus”);
exit;
end;
// WORK HERE / / ТУТ БУДЕ ОСНОВНИЙ КОД ПРОГРАМИ
end;

Функція Handler


Функція Handler буде викликатися менеджером сервісів при передачі повідомлень сервісу. Знову ж назву функції – будь-яке. Адреса функції передається за допомогою функції RegisterServiceCtrlHandler (див. вище). Функція має один параметр типу DWORD (Cardinal) – повідомлення сервісу. Якщо в одному процесі декілька сервісів – для кожного з них повинна бути своя функція.

procedure ServiceCtrlHandler(Opcode : Cardinal);stdcall;

Знову не забуваємо про stdcall.


Отже, функція отримує код повідомлення, який ми і перевіряємо. Починаємо згадувати, що ми писали в ServiceStatus.dwControlsAccepted. У мене це SERVICE_ACCEPT_STOP і SERVICE_ACCEPT_PAUSE_CONTINUE, значить, мені треба перевіряти повідомлення SERVICE_CONTROL_PAUSE, SERVICE_CONTROL_CONTINUE, SERVICE_CONTROL_STOP і виконувати відповідні дії. Решта повідомлення:



















ServiceStatus.dwControlsAccepted Оброблювані повідомлення
SERVICE_ACCEPT_PAUSE_CONTINUE SERVICE_CONTROL_PAUSE і SERVICE_CONTROL_CONTINUE
SERVICE_ACCEPT_STOP SERVICE_CONTROL_STOP
SERVICE_ACCEPT_SHUTDOWN SERVICE_CONTROL_SHUTDOWN
SERVICE_ACCEPT_PARAMCHANGE SERVICE_CONTROL_PARAMCHANGE


Також треба обробляти SERVICE_CONTROL_INTERROGATE. Що це таке – незрозуміло, але обробляти треба 🙂 Передаємо новий статус сервісу менеджеру сервісів функцією SetServiceStatus.


Приклад функції Handler:

procedure ServiceCtrlHandler(Opcode : Cardinal);stdcall;
var
Status : Cardinal;
begin
case Opcode of
SERVICE_CONTROL_PAUSE :
begin
ServiceStatus.dwCurrentState := SERVICE_PAUSED;
end;
SERVICE_CONTROL_CONTINUE :
begin
ServiceStatus.dwCurrentState := SERVICE_RUNNING;
end;
SERVICE_CONTROL_STOP :
begin
ServiceStatus.dwWin32ExitCode:=0;
ServiceStatus.dwCurrentState := SERVICE_STOPPED;
ServiceStatus.dwCheckPoint :=0;
ServiceStatus.dwWaitHint :=0;
if not SetServiceStatus (ServiceStatusHandle,ServiceStatus)
then begin
Status:=GetLastError;
LogError(“SetServiceStatus”);
Exit;
end;
exit;
end;
SERVICE_CONTROL_INTERROGATE : ;
end;
if not SetServiceStatus (ServiceStatusHandle, ServiceStatus)
then begin
Status := GetLastError;
LogError(“SetServiceStatus”);
Exit;
end;
end;

Реалізація головної функції програми


У функції ServiceMain (см.там, де зазначено) пишемо код сервісу. Так як сервіс зазвичай постійно знаходиться в пам’яті комп’ютера, то швидше за все код буде знаходитися в циклі. Наприклад в такому:


repeat Щось робимо поки сервіс не завершиться.
until ServiceStatus.dwCurrentState = SERVICE_STOPPED;

Але це пройде якщо сервіс не обробляє повідомлення призупинення / перезапуску, інакше сервіс ніяк не прореагує. Інший варіант:

repeat
if ServiceStatus.dwCurrentState <> SERVICE_PAUSED then чогось робимо
until ServiceStatus.dwCurrentState = SERVICE_STOPPED;

І третій, імхо, найправильніший варіант = використання потоку:

Пишемо функцію

function MainServiceThread(p:Pointer):DWORD;stdcall;
begin щось робимо
end;

і в ServiceMain створюємо потік

var
ThID : Cardinal;

hThread:=CreateThread(nil,0,@MainServiceThread,nil,0,ThID); і чекаємо його завершення
WaitForSingleObject(hThread,INFINITE); закриваючи після цього його дескриптор
CloseHandle(hThread);


При цьому hThread робимо глобальної змінної.

Тепер при призупинення сервісу (в Handler) робимо так

  SERVICE_CONTROL_PAUSE    :
begin
ServiceStatus.dwCurrentState := SERVICE_PAUSED; SuspendThread (hThread); / / припиняємо потік
end;

і при відновленні роботи сервісу

  SERVICE_CONTROL_CONTINUE :
begin
ServiceStatus.dwCurrentState := SERVICE_RUNNING; ResumeThread (hThread); / / відновлюємо потік
end;

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*