| ||||||||||||||||
| ||||||||||||||||
| ||||||||||||||||
Написание сервисов Windows NT на WinAPI Причиной написания этой статьи, как не странно,
стала необходимость написания своего сервиса. Но
в Borland'е решили немного "порадовать" нас,
пользователей Delphi 6 Personal, не добавив возможности
создания сервисов (в остальных версиях Delphi 5 и 6
эта возможность имеется в виде класса TService).
Решив, что еще не все потеряно, взял на проверку
компоненты из одноименного раздела этого сайта.
Первый оказался с многочисленными багами, а до
пробы второго я не дошел, взглянув на исходник -
модуль Forms в Uses это не только окошки, но и более 300
килобайт "веса" программы. Бессмысленного
увеличения размера не хотелось и пришлось
творить свое.
Итак, приступим к написанию своего сервиса... program Project1; uses Windows,WinSvc; {$R *.res} begin end. На этом подготовительный этап закончен -
начинаем писать сервис. Главная часть программы Как уже отмечалось - сервис это обычная
программа. Программа в Pascal'е находится между begin и
end. После запуска нашего сервиса (здесь и далее
под запуском сервиса понимается именно запуск
его из Менеджера сервисов, а не просто запуск
exe'шника сервиса) менеджер сервисов ждет пока наш
сервис вызовет функцию StartServiceCtrlDispatcher.Ждать он
будет недолго - если в нашем exe'шнике несколько
сервисов то секунд 30, если один - около секунды,
поэтому помещаем вызов StartServiceCtrlDispatcher
поближе к begin. DispatchTable[0].lpServiceName:=ServiceName; DispatchTable[0].lpServiceProc:=@ServiceProc; DispatchTable[1].lpServiceName:=nil; DispatchTable[1].lpServiceProc:=nil; Советую завести константы ServiceName - имя сервиса и
ServiceDisplayName - отображаемое имя. 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.
Функция ServiceMain
ServiceMain - основная функция сервиса. Если в
ехешнике несколько сервисов, но для каждого
сервиса пишется своя ServiceMain функция. Имя функции
может быть любым! и передается в
DispatchTable.lpServiceProc:=@ServiceMain (см.предыдущущий абзац). У
меня она называется ServiceProc и описывается так:
Остальные константы - о драйверах. Если надо -
смотрите их в MSDN.
Остальные сообщения смотрите опять же в MSDN
(куда уж без него ;-) 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) -
сообщение сервису. Если в одном процессе
несколько сервисов - для каждого из них должна
быть своя функция.
Также надо обрабатывать SERVICE_CONTROL_INTERROGATE. Что это
такое - непонятно, но обрабатывать надо :)
Передаем новый статус сервиса менеджеру
сервисов функцией SetServiceStatus. 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 делаем глобальной переменной. SERVICE_CONTROL_PAUSE : begin ServiceStatus.dwCurrentState := SERVICE_PAUSED; SuspendThread(hThread); // приостанавливаем поток end; и при возобновлении работы сервиса SERVICE_CONTROL_CONTINUE : begin ServiceStatus.dwCurrentState := SERVICE_RUNNING; ResumeThread(hThread); // возобновляем поток end;
|
|
| ||||||||||||||||
|