Легенда:
новое сообщение
закрытая нитка
новое сообщение
в закрытой нитке
старое сообщение
|
- Напоминаю, что масса вопросов по функционированию форума снимается после прочтения его описания.
- Новичкам также крайне полезно ознакомиться с данным документом.
[Win32] Хых, руки у мадемуазель зачесались ;-) 14.05.04 19:47 Число просмотров: 4533
Автор: HandleX <Александр М.> Статус: The Elderman Отредактировано 18.05.04 08:04 Количество правок: 5
|
> В продолжение темы: > А чем отличаются сервисы, от обычнх приложений? > Наскоко трудно переделать обычную прогу в сервис - наверно > нужно поддерживать некий специфический интерфейс? Итак, службы... Да, поддерживают они особый интерфейс для "общения" с Service Control Manager'ом.
SCM их и запускает, исходя из того, какие параметры имеются в записи реестра об этой службе. Между
прочим, SCM запускает также и драйверы, поэтому "захват власти" над SCM является лакомой добычей
хакеров и вирусов, поскольку именно оттуда они могут запустить драйвер ядра. А драйвер ядра может
поиметь абсолютно полный контроль над системой.
Но написание драйверов ядра задача не для слабонервных. И не всякий компилятор сможет это
сделать. К примеру, Delphi даже при создании простейшего *.exe, который запускается и тут же
выходит, запихивает в него некий run-time, который использует user-mode API, и запуск
такого "драйвера" вызовет ошибку. Вообще, я не слышал, чтобы драйверы компилировали на
чём-нибудь отличном от M$ VC.
Однако вернёмся к user-mode службам. Сперва службу нужно написать ;-) Там всё просто, в MSDN есть
примеры ;-) Поскольку службу запускает SCM, он некоторое время ждёт, когда служба вызовет
StartServiceCtrlDispatcher(), параметром этой функции является массив из записей вида
"Имя службы-Точка входа в MainServiceProc), из чего делаем вывод, что в одном исполняемом файле
службы может "содержаться" несколько служб. Интересно, что возврат из StartServiceCtrlDispatcher()
происходит только после остановки всех прописанных в этом вызове служб. Если
StartServiceCtrlDispatcher() возвращает false, значит что-то не так, и поможет GetLastError() ;-)
Итак, если всё в порядке, SCM запускает новый поток и в конце концов передаёт управление на
ServiceMain(). В этой функции программист должен первым делать зарегистрировать свою функцию
ServiceControlHandler(), в которую SCM будет "кидать" сообщения о том, что нужно делать службе -
запуститься, перейти в паузу, выйти из паузы, остановиться и проч. Необходимо сказать о
многопоточности. ServiceControlHandler() и ServiceMain() исполняются в отдельных потоках, это нужно
учитывать. Но это и кое-что облегчает... Часто программисты для перевода службы в "паузу" просто
исполняют SuspendThread() для потока ServiceMain(), а с помощью ResumeThread восстанавливают
работу службы. Сервис может "отчитываться" SCM о своём состоянии, докладывать ему об ошибках
во время запуска, "говорить" SCM о том, какие "команды" он может в данный момент воспринимать.
В частности, службу можно запрограммировать так, что остановить её после запуска, или загнать в
паузу, будет невозможно, эдакий "неубиваемый" сервис... Единственное, его можно грохнуть через
TerminateProcess(), но это отдельная история...
И вот, "скелет" службы создан, и руки чешутся нажать на кнопку "Run" компилятора... Однако спешка
нужна сами знаете где... Теперь службу нужно "прописать" в систему. Нужно или в самой программе
службы встроить реакцию на скажем, параметр командной строки типа -install либо написать
программу-установщик, которая зарегистрирует службу в системе. Делается это при помощи вызова
функций API OpenSCManager() и CreateService(), внимательное изучение параметров этих двух функций
поможет понять как управлять и инсталлировать службы, а также какие они бывают и когда SCM будет
их запускать. К примеру, можно указать SCM автоматический запуск службы во время старта OS, или
ручной, или вообще отключить службу...
Ну и наконец, после того, как служба инсталлирована в систему, можно попробовать её запустить...
Делается это встроенным в OS "запускателем" командной строки net start ИмяСлужбы или через
оснастку управления службами.
Отладка служб тоже занятие не для слабонервных ;-) Однако есть подсказки от M$ о том, как это делается.
> Кстати, наскоко я понимаю запускать приложения в качестве > сервиса пожет токо супер пользователь? При вызове функции OpenSCManager() одним из параметров является уровень доступа к службам...
По умолчанию обычный пользователь не может установить службу. А вот дальше доступ
(сможет ли пользователь запустить, остановить или даже удалить уже прописанную службу) задаётся
после создании службы при помощи функции SetServiceObjectSecurity(), иныим словами для каждой
службы можно сконфигурировать любой Список Контроля Доступа для любых пользователей и групп в системе.
Ну как, после всего вышесказанного не отпало желание написать службу? ;-)
Для интересующихся и начинающих изучать программирование приведу пример простейшей службы на
Delphi, которая может себя устанавливать и удалять, при запуске "пикает" в системный спикер, может
войти в паузу и быть остановлена.
Успехов.
program SimpleServ;
{$APPTYPE CONSOLE}
uses Windows, WinSVC; //Не используем SysUtils и прочий хлам - размер екзешника у нас будет всего ~25 килобайт
Function IsWin2KorHigher: BOOL;
Var aVer: OSVERSIONINFO;
Begin
ZeroMemory(@aVer, SizeOf(aVer));
aVer.dwOSVersionInfoSize := SizeOf(aVer);
GetVersionEx(aVer);
Result := (aVer.dwPlatformId = VER_PLATFORM_WIN32_NT) And (aVer.dwMajorVersion >= 5);
End;
Const
ServicesCount = 1;
ServiceName = 'SimpleServ';
DisplayName = 'Simple Service';
ServiceType = SERVICE_WIN32_OWN_PROCESS;
ServiceDescription = 'SimpleServ from Handlex ;-))) This simple program will help you to understand WinNT Services. Mail to alex_wh@mail.ru.';
Var
ServThrHndl: THandle = 0;
StopEvent: THandle = 0;
aServHndl: DWord = 0;
aServStatus: SERVICE_STATUS;
Function IntToStr(Value: Integer): String; //Included because we don't use SysUtils
Var aSign: Bool;
Begin
Result := '';
aSign := Value >= 0;
If Not aSign Then Value := -Value;
Repeat
Result := Char(Value - (Value Div 10) * 10 + Byte('0')) + Result;
Value := Value Div 10;
Until Value = 0;
If Not aSign Then Result := '-' + Result;
End;
// Helper function for windows error strings
function SysErrorMessage(ErrorCode: Integer): string; //Included because we don't use SysUtils
var
Len: Integer;
Buffer: array[0..255] of Char;
begin
Len := FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM or
FORMAT_MESSAGE_ARGUMENT_ARRAY, nil, ErrorCode, 0, Buffer,
SizeOf(Buffer), nil);
while (Len > 0) and (Buffer[Len - 1] in [#0..#32]) do Dec(Len);
SetString(Result, Buffer, Len);
UniqueString(Result);
ANSItoOEM(PChar(Result), PChar(Result));
If Result <> '' Then
Result := '(' + IntToStr(ErrorCode) + ') ' + Result;
end;
Procedure ShowInfo;
Begin
WriteLn;
WriteLn(' -=* SIMPLE TRAINING SERVICE BY HandleX *=-');
End;
Procedure ProcessStartupParams; //Реакция на install, uninstall
Function SetServiceDescription(aSHndl: THandle; aDesc: String): Bool; //Устанавливает "описание" для службы, Win2k и выше
Const SERVICE_CONFIG_DESCRIPTION: DWord = 1;
Var
DynChangeServiceConfig2: Function(
hService: SC_HANDLE; // handle to service
dwInfoLevel: DWORD; // information level
lpInfo: Pointer): Bool; StdCall; // new data
aLibHndl: THandle;
TempP: PChar;
Begin
aLibHndl := GetModuleHandle(advapi32);
Result := aLibHndl <> 0; If Not Result Then Exit;
DynChangeServiceConfig2 := GetProcAddress(aLibHndl, 'ChangeServiceConfig2A');
Result := @DynChangeServiceConfig2 <> Nil; If Not Result Then Exit;
TempP := PChar(aDesc); //ChangeServiceConfig2 хочет вместо строки указатель на указатель ;-)
Result := DynChangeServiceConfig2(aSHndl, SERVICE_CONFIG_DESCRIPTION, @TempP);
End;
Type
TToDo = (tdError, tdInstall, tdUninstall);
TToDo_s = Set of TToDo;
Const
ParamStrings: Array[tdInstall..tdUninstall] of String = ('install', 'uninstall');
Function MapParam(aParam: String): TToDo; //Узнаёт из параметра о вашем желании ;-)
Var
J: TToDo;
TempStr: String;
Begin
Result := tdError;
TempStr := aParam;
If TempStr[1] In ['/', '-'] Then
TempStr := Copy(TempStr, 2, Length(TempStr) - 1);
UniqueString(TempStr);
CharLower(PChar(TempStr));
For J := Low(ParamStrings) to High(ParamStrings) Do
If ParamStrings[J] = TempStr Then
Begin
Result := J;
Exit;
End;
End;
Var
J: Integer;
scHndl, sHndl: THandle;
aStatus: TServiceStatus;
toDo: TTodo_s;
Begin
toDo := [];
For J := 1 to ParamCount Do
Begin
Include(ToDo, MapParam(ParamStr(J)));
If tdError in toDo Then
Begin
ExitCode := ERROR_INVALID_PARAMETER;
WriteLn('Unknown parameter - ' + ParamStr(J) + '. RTFM, please...');
Exit;
End;
End;
If [tdInstall, tdUninstall] <= toDo Then
Begin
ExitCode := ERROR_INVALID_PARAMETER;
WriteLn('Error: you can not install and uninstall service simultaniosly. Check params.');
Exit;
End;
If tdInstall in toDo Then //Устанавливаем сервис
Begin
Write('Connecting Service Control Manager...');
scHndl := OpenSCManager(Nil, Nil, SC_MANAGER_CREATE_SERVICE);
If scHndl = 0 Then
Begin
ExitCode := GetLastError;
WriteLn('Failed!'); WriteLn('Error: ', SysErrorMessage(ExitCode));
Exit;
End;
Try
WriteLn('Ok');
Write('Creating service database record...');
sHndl := CreateService(
SCHndl, ServiceName, DisplayName,
SERVICE_QUERY_CONFIG Or SERVICE_CHANGE_CONFIG, ServiceType, SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL, PChar(ParamStr(0)), Nil, Nil, Nil, Nil, Nil);
If sHndl = 0 Then
Begin
ExitCode := GetLastError;
WriteLn('Failed!'); WriteLn('Error: ', SysErrorMessage(ExitCode));
Exit;
End;
Try
WriteLn('Ok');
If ServiceDescription <> '' Then
Begin
Write('Setting service description...');
If Not SetServiceDescription(sHndl, ServiceDescription) Then
Begin
WriteLn('Failed!');
WriteLn('Warning: ', SysErrorMessage(GetLastError));
WriteLn('Warning: SetServiceDesc() failed, but service is installed!');
End;
WriteLn('Ok');
End;
Finally
CloseServiceHandle(sHndl);
End;
Finally
CloseServiceHandle(SCHndl);
End;
WriteLn('Service "', DisplayName, '" install success.');
End;
If tdUninstall in toDo Then //Удаляем сервис...
Begin
Write('Connecting Service Control Manager...');
scHndl := OpenSCManager(Nil, Nil, GENERIC_EXECUTE);
If scHndl = 0 Then
Begin
ExitCode := GetLastError;
WriteLn('Failed!');
WriteLn('Error: ', SysErrorMessage(ExitCode));
Exit;
End;
Try
WriteLn('Ok');
Write('Opening and Quering Service...');
sHndl := OpenService(SCHndl, ServiceName, STANDARD_RIGHTS_REQUIRED Or SERVICE_QUERY_STATUS Or SERVICE_STOP);
If sHndl = 0 Then
Begin
ExitCode := GetLastError;
WriteLn('Failed!'); WriteLn('Error: ', SysErrorMessage(ExitCode));
Exit;
End;
Try
If Not QueryServiceStatus(sHndl, aStatus) Then
Begin
ExitCode := GetLastError;
WriteLn('Failed!'); WriteLn('Error: ', SysErrorMessage(ExitCode));
Exit;
End;
WriteLn('Ok');
If aStatus.dwCurrentState <> SERVICE_STOPPED Then
Begin
Write('Service is running, wait until stopped...');
If Not ControlService(sHndl, SERVICE_CONTROL_STOP, aStatus) Then
Begin
ExitCode := GetLastError;
WriteLn('Failed!'); WriteLn('Error: ', SysErrorMessage(ExitCode));
Exit;
End;
While aStatus.dwCurrentState <> SERVICE_STOPPED Do
Begin
Sleep(250); Write('.');
If Not QueryServiceStatus(sHndl, aStatus) Then
Begin
ExitCode := GetLastError;
WriteLn('Failed!'); WriteLn('Error: ', SysErrorMessage(ExitCode));
Exit;
End;
End;
WriteLn('Stopped');
End;
Write('Deleting Service...');
If Not DeleteService(sHndl) Then
Begin
ExitCode := GetLastError;
WriteLn('Failed!'); WriteLn('Error: ', SysErrorMessage(ExitCode));
Exit;
End;
WriteLn('Ok');
Finally
CloseServiceHandle(sHndl);
End;
Finally
CloseServiceHandle(SCHndl);
End;
WriteLn('Service uninstall success.');
End;
End;
Function SetState(aState: DWORD): DWORD;
Begin
aServStatus.dwCurrentState := aState;
If aServHndl <> 0 Then
SetServiceStatus(aServHndl, aServStatus);
Result := aServStatus.dwCurrentState;
End;
Procedure ServiceHandler(fdwControl: DWORD); StdCall;
Begin
Case fdwControl Of
SERVICE_CONTROL_STOP: Begin //Requests the service to stop.
SetState(SERVICE_STOP_PENDING);
SetEvent(StopEvent);
ResumeThread(ServThrHndl); //Если сервис был в паузе, то рабочий поток надо возобновить
End;
SERVICE_CONTROL_PAUSE: Begin //Requests the service to pause.
SetState(SERVICE_PAUSE_PENDING);
SuspendThread(ServThrHndl); //Останавливаем рабочий поток сервиса
SetState(SERVICE_PAUSED);
End;
SERVICE_CONTROL_CONTINUE: Begin //Requests the paused service to resume.
SetState(SERVICE_CONTINUE_PENDING);
ResumeThread(ServThrHndl); //Восстанавливаем рабочий поток сервиса
SetState(SERVICE_RUNNING);
End;
SERVICE_CONTROL_INTERROGATE: Begin //Requests the service to update immediately its current status information to the service control manager.
SetState(aServStatus.dwCurrentState); //Говорим SCM о том, в каком состоянии находится наша служба
End;
128..255: Begin //The service defines the action associated with the control code.
SuspendThread(ServThrHndl); //Протяжно пищим в спикер, потому что кто-то послал USER DEFINED CONTROL CODE ;-)
Windows.Beep(1000, 500);
ResumeThread(ServThrHndl);
End;
End;
End;
Procedure MainServiceProc( // Каждая служба может иметь параметры своего запуска. У нас не используется ;-)
dwArgc: DWORD; // number of arguments
lpszArgv: Pointer); StdCall // array of arguments
Begin
aServHndl := RegisterServiceCtrlHandler(ServiceName, @ServiceHandler);
If aServHndl = 0 Then
Begin
ExitCode := GetLastError;
Exit; //Какая-то ошибка, срочно выходим, SCM будет ругаться, но сообщить мы ему ничего не можем...
End;
ZeroMemory(@aServStatus, SizeOf(aServStatus));
aServStatus.dwServiceType := ServiceType;
aServStatus.dwControlsAccepted := SERVICE_ACCEPT_STOP Or SERVICE_ACCEPT_PAUSE_CONTINUE;
//aServStatus.dwWaitHint := 500; //Здесь может быть подсказка для небыстрых служб о том, как долго она реагирует на команды
SetState(SERVICE_START_PENDING); //Извещаем SCM, что мы начали стартовать именно этот сервис...
// Тут идёт рутина инициализации...
If Not DuplicateHandle(GetCurrentProcess, GetCurrentThread, GetCurrentProcess, @ServThrHndl, 0, FALSE, DUPLICATE_SAME_ACCESS) Then
Begin //Нам нужен реальный дескриптор потока службы, делаем его...
aServStatus.dwWin32ExitCode := GetLastError;
SetState(SERVICE_STOPPED);
Exit;
End;
//Нам нужен unnamed event для реагирования на останов из ControlHandler...
StopEvent := CreateEvent(Nil, True, False, Nil);
If StopEvent = 0 Then //Какая-то ошибка - срочно останавливаемся и выходим...
Begin
aServStatus.dwWin32ExitCode := GetLastError;
SetState(SERVICE_STOPPED);
Exit;
End;
// Инит прошёл, пошла работа сервиса...
SetState(SERVICE_RUNNING); //Момент истины - извещаем SCM что мы работаем!!!
While WaitForSingleObject(StopEvent, 500) = WAIT_TIMEOUT Do
Windows.Beep(10000, 10); //Крутим цикл, бипаем по таймауту, иначе выходим...
// Выполняем остановку сервиса - вычищаемся и выходим...
CloseHandle(ServThrHndl);
ServThrHndl := 0;
CloseHandle(StopEvent);
StopEvent := 0;
SetState(SERVICE_STOPPED); // Работа сервиса закончена...
End;
Var
ServTableEntryArray: Array[0..ServicesCount] Of TServiceTableEntryA;
begin
{$R *.res}
//Старт программы...
If ParamCount > 0 Then //От нас что-то хотят...
Begin
ShowInfo;
ProcessStartupParams; //Выясняем что и выходим...
Exit;
End;
//Готовимся к вызову StartServiceCtrlDispatcher
ZeroMemory(@ServTableEntryArray, SizeOf(ServTableEntryArray));
ServTableEntryArray[0].lpServiceName := ServiceName;
ServTableEntryArray[0].lpServiceProc := @MainServiceProc;
If Not StartServiceCtrlDispatcher(ServTableEntryArray[0]) Then
Begin
ExitCode := GetLastError;
ShowInfo; //Какой-то косяк, поэтому выводим в консоль сообщение и выходим...
WriteLn('Error: ', SysErrorMessage(ExitCode));
WriteLn('This program is Windows NT Service, so it CAN NOT be run from command prompt.');
WriteLn('You can install it with "/install" parameter.');
Exit;
End;
// Все сервисы завершили свою работу, выходим...
end.
---
|
|
|