Сообщений 8 Оценка 640 [+1/-0] Оценить |
Управление службами Состояния системной службы Установка и удаление службОпределение текущего состояния службы Запуск службы Использование команд управления Блокировка базы данных служб Заключение Ссылки |
Демонстрационные программы
Исходный код программ
В первой части статьи мы рассматривали службы как статичные объекты - записи в конфигурационной базе данных SCM. Настало время "оживить" их: в этой части статьи мы рассмотрим запуск и остановку системных служб, а также другие вопросы, непосредственно связанные с их выполнением.
Как объект исполнения, системная служба может находиться в одном из семи состояний, которые обозначаются константами, перечисленными в таблице 1.
Константа | Состояние службы |
---|---|
SERVICE_STOPPED | Служба остановлена |
SERVICE_RUNNING | Служба работает |
SERVICE_PAUSED | Служба приостановлена |
SERVICE_STOP_PENDING | Служба находится в процессе остановки |
SERVICE_START_PENDING | Служба находится в процессе запуска |
SERVICE_PAUSE_PENDING | Служба находится в процессе приостановки |
SERVICE_CONTINUE_PENDING | Служба находится в процессе возобновления после приостановки |
Сразу после загрузки компьютера служба находится в состоянии SERVICE_STOPPED. Она остается в этом состоянии до тех пор, пока не будет запущена, либо автоматически в процессе инициализации системы, либо с помощью StartService позднее. Когда поступает команда на запуск службы, она автоматически переводится менеджером системных служб в состояние SERVICE_START_PENDING. Это один из немногих случаев, когда менеджер системных служб изменяет состояние службы; как правило, службы сами должны отслеживать свое состояние и сообщать его SCM. В состоянии SERVICE_START_PENDING служба находится, пока не завершит свою инициализацию и не сообщит о переходе в состояние SERVICE_RUNNING.
Работающая служба, помимо своей основной работы, постоянно следит за командами управления, которые ей могут посылать приложения или операционная система. Для посылки команд управления из приложений служит функция ControlService, которую мы подробно рассмотрим позднее в этой части статьи. Некоторые команды, а именно команды остановки, приостановки и возобновления, непосредственным образом влияют на состояние службы.
При поступлении команды остановки служба переходит сначала в состояние SERVICE_STOP_PENDING, говорящее о том, что она находится в процессе завершения работы, а затем в состояние SERVICE_STOPPED. Многие службы, которые завершаются достаточно быстро, не используют промежуточное состояние SERVICE_STOP_PENDING, а сразу рапортуют о своей остановке, сообщая о переходе в состояние SERVICE_STOPPED. Если процесс службы завершается непредвиденно, например, в результате сбоя, SCM автоматически переводит службу в состояние SERVICE_STOPPED.
При поступлении команды приостановки служба сначала переходит в состояние SERVICE_PAUSE_PENDING, а затем, когда приостановка выполнена, в состояние SERVICE_PAUSED. В приостановленном состоянии процесс службы существует, но служба не обрабатывает запросы клиентов. Точное определение этого состояния зависит от конкретной службы. Например, служба Web-сервера в этом состоянии может перестать принимать соединения от клиентов, а может и принимать соединения, но всегда возвращать код ошибки. Как и в случае с остановкой, промежуточное состояние SERVICE_PAUSE_PENDING является необязательным.
Наконец, при поступлении команды возобновления служба возвращается в состояние SERVICE_RUNNING, возможно, побывав в состоянии SERVICE_CONTINUE_PENDING. На рис. 1 показаны состояния службы и переходы между ними.
Рис. 1. Состояния службы
Следует отметить, что не все службы поддерживают все состояния и все команды управления. Команду приостановки и приостановленное состояние поддерживают лишь некоторые из стандартных служб Windows NT. Некоторые службы не поддерживают команду остановки; будучи однажды запущенными, они продолжают работу до выключения компьютера.
Для определения текущего состояния службы предназначена функция QueryServiceStatus:
BOOL QueryServiceStatus( IN SC_HANDLE hService, // handle службы OUT LPSERVICE_STATUS pServiceStatus // состояние службы ); |
Параметрами функции являются: handle службы, который должен быть открыт с правом доступа SERVICE_QUERY_STATUS, и указатель на структуру SERVICE_STATUS, в которую функция при успешном завершении заносит информацию о состоянии службы:
typedef struct _SERVICE_STATUS { DWORD dwServiceType; DWORD dwCurrentState; DWORD dwControlsAccepted; DWORD dwWin32ExitCode; DWORD dwServiceSpecificExitCode; DWORD dwCheckPoint; DWORD dwWaitHint; } SERVICE_STATUS, * LPSERVICE_STATUS; |
Давайте детально изучим поля этой структуры.
dwServiceTypeЭто поле содержит тип системной службы. Как мы помним, службы Win32 бывают двух типов: SERVICE_WIN32_OWN_PROCESS и SERVICE_WIN32_SHARE_PROCESS, а также может быть указан флаг SERVICE_INTERACTIVE_PROCESS.
dwCurrentStateЗначением этого поля является одна из семи констант состояния службы, такая как SERVICE_STOPPED или SERVICE_RUNNING. Именно это поле описывает текущее состояние службы.
dwControlsAcceptedВ этом поле служба возвращает набор флагов, который показывает, какие команды управления обрабатываются службой. Мы вернемся к содержимому этого поля, когда будем рассматривать функцию ControlService.
dwWin32ExitCode и dwServiceSpecificExitCodeПоле dwWin32ExitCode используется только при запуске и остановке службы. Если служба не может запуститься или останавливается в результате ошибки, в это поле заносится код ошибки Win32, который поясняет причину произошедшего. Один из кодов ошибок, ERROR_SERVICE_SPECIFIC_ERROR, говорит о том, что произошла ошибка, которая не описывается ни одним из стандартных кодов ошибок, a поле dwServiceSpecificExitCode содержит код ошибки, определенный службой. Интерпретация этого кода ошибки целиком и полностью зависит от конкретной службы.
dwCheckPoint и dwWaitHintЭти поля используется службами, когда исполнение команды управления занимает длительное время. SCM ожидает ответа службы на команду управления в течение фиксированного интервала времени (60 секунд) и, если служба не успеет обновить свое состояние, считает, что служба зависла и не реагирует на сигналы управления. Если это произошло при запуске службы, менеджер принудительно завершает процесс службы и возвращает ее в остановленное состояние.
Интервал в 60 секунд не всегда является достаточным. Например, служба базы данных, может загружать некоторые служебные таблицы целиком в память при запуске, и этот процесс может занять длительное время. Чтобы сообщить SCM, что служба не зависла, а всего лишь выполняет длительную операцию, служба должна периодически обновлять свое состояние, каждый раз увеличивая значение dwCheckPoint. Кроме того, служба заносит в поле dwWaitHint время в миллисекундах, через которое она предполагает вновь обновить свое состояние. Программы управления службами могут использовать это поле для отображения индикатора прогресса при выполнении длительных операций.
Функция QueryServiceStatusEx, впервые появившаяся в Windows 2000, является расширением функции QueryServiceStatus. Она возвращает всю ту информацию, которую выдает QueryServiceStatus, плюс идентификатор процесса службы, а также дополнительные флаги.
BOOL QueryServiceStatusEx( IN SC_HANDLE hService, // handle службы IN SC_STATUS_TYPE InfoLevel, // вид информации OUT PBYTE pBuffer, // приемный буфер IN DWORD cbBufSize, // размер приемного буфера OUT PDWORD pcbBytesNeeded // требуемый размер буфера ); |
Интерфейс этой функции, в отличие от QueryServiceStatus, более гибок и приспособлен к расширению: параметр InfoLevel задает вид информации, который необходимо получить. Хотя в настоящее время определено только одно значение для этого параметра, SC_STATUS_PROCESS_INFO, ничто не мешает добавлению новых видов информации в следующих версиях системы.
Параметр pBuffer указывает на буфер, в который функция помещает информацию о состоянии службы, в настоящее время это должна быть структура SERVICE_STATUS_PROCESS. В параметре cbBufSize следует передать размер структуры, а параметр pcbBytesNeeded должен указывать на переменную, в которую функция заносит необходимый размер буфера. На сегодняшний день от последнего параметра мало пользы, поскольку структура SERVICE_STATUS_PROCESS не содержит данных переменного размера (таких, как строки), но он может пригодиться в дальнейших версиях операционной системы.
Структура SERVICE_STATUS_PROCESS содержит все поля, которые есть в структуре SERVICE_STATUS, и два дополнительных поля:
typedef struct _SERVICE_STATUS_PROCESS { DWORD dwServiceType; DWORD dwCurrentState; DWORD dwControlsAccepted; DWORD dwWin32ExitCode; DWORD dwServiceSpecificExitCode; DWORD dwCheckPoint; DWORD dwWaitHint; DWORD dwProcessId; DWORD dwServiceFlags; } SERVICE_STATUS_PROCESS, * LPSERVICE_STATUS_PROCESS; |
dwProcessId
Это поле содержит идентификатор процесса службы, если, конечно, служба находится в состоянии, отличном от SERVICE_STOPPED. Если служба остановлена, это поле содержит ноль.
dwServiceFlagsЭто поле содержит дополнительные флаги службы. В настоящее время определен только один такой флаг, SERVICE_RUNS_IN_SYSTEM_PROCESS, который, если установлен, говорит о том, что служба выполняется в системном процессе. К таким процессам относятся lsass.exe, services.exe и winlogon.exe. Этот флаг устанавливается, только если служба работает. Он не будет установлен в состоянии SERVICE_STOPPED, даже если служба зарегистрирована для исполнения в одном из системных процессов.
Теперь мы имеем достаточно информации, чтобы написать код, отображающий состояние произвольной службы (см. листинг 1).
//----------------------------------------------------------------------- // DemoServiceStatus // // Отображает текущее состояние службы. // // Параметры: // hSCM - handle SCM // pszServiceName - имя службы // // Возвращает: // ноль - при успешном завершении, отличное от нуля значение - в // противном случае. // int DemoServiceStatus( IN SC_HANDLE hSCM, IN PCTSTR pszServiceName ) { _ASSERTE(hSCM != NULL); _ASSERTE(pszServiceName != NULL); SC_HANDLE hService; SERVICE_STATUS Status; // открываем handle службы с доступом SERVICE_QUERY_STATUS hService = OpenService(hSCM, pszServiceName, SERVICE_QUERY_STATUS); if (hService == NULL) return PrintError(_T("OpenService failed"), GetLastError()); // получаем состояние службы if (!QueryServiceStatus(hService, &Status)) { PrintError(_T("QueryServiceStatus failed"), GetLastError()); CloseServiceHandle(hService); return 1; } PCTSTR pszServiceType; PCTSTR pszInteractive; PCTSTR pszStatus; switch (Status.dwServiceType & ~SERVICE_INTERACTIVE_PROCESS) { case SERVICE_KERNEL_DRIVER: pszServiceType = _T("Kernel Driver"); break; case SERVICE_FILE_SYSTEM_DRIVER: pszServiceType = _T("File System Driver"); break; case SERVICE_WIN32_OWN_PROCESS: pszServiceType = _T("Win32 Own Process"); break; case SERVICE_WIN32_SHARE_PROCESS: pszServiceType = _T("Win32 Share Process"); break; default: pszServiceType = _T("Unknown"); break; } if (Status.dwServiceType & SERVICE_INTERACTIVE_PROCESS) pszInteractive = _T("(Interactive)"); else pszInteractive = _T(""); switch (Status.dwCurrentState) { case SERVICE_STOPPED: pszStatus = _T("Stopped"); break; case SERVICE_RUNNING: pszStatus = _T("Running"); break; case SERVICE_PAUSED: pszStatus = _T("Paused"); break; case SERVICE_STOP_PENDING: pszStatus = _T("Stop Pending"); break; case SERVICE_START_PENDING: pszStatus = _T("Start Pending"); break; case SERVICE_PAUSE_PENDING: pszStatus = _T("Pause Pending"); break; case SERVICE_CONTINUE_PENDING: pszStatus = _T("Continue Pending"); break; default: pszStatus = _T("Unknown"); break; } // выводим состояние службы _tprintf(_T("Type: %s %s\n"), pszServiceType, pszInteractive); _tprintf(_T("State: %s\n"), pszStatus); _tprintf(_T("Controls Accepted:\n")); if (Status.dwControlsAccepted & SERVICE_ACCEPT_STOP) _tprintf(_T("\tStop\n")); if (Status.dwControlsAccepted & SERVICE_ACCEPT_PAUSE_CONTINUE) _tprintf(_T("\tPause and Continue\n")); if (Status.dwControlsAccepted & SERVICE_ACCEPT_SHUTDOWN) _tprintf(_T("\tShutdown\n")); if (Status.dwControlsAccepted & SERVICE_ACCEPT_PARAMCHANGE) _tprintf(_T("\tParam Change\n")); if (Status.dwControlsAccepted & SERVICE_ACCEPT_NETBINDCHANGE) _tprintf(_T("\tNet Bind Change\n")); if (Status.dwControlsAccepted & SERVICE_ACCEPT_HARDWAREPROFILECHANGE) _tprintf(_T("\tHardware Profile Change\n")); if (Status.dwControlsAccepted & SERVICE_ACCEPT_POWEREVENT) _tprintf(_T("\tPower Event\n")); _tprintf(_T("Win 32 Exit Code: %d\n"), Status.dwWin32ExitCode); _tprintf(_T("Service Exit Code: %d\n"), Status.dwServiceSpecificExitCode); _tprintf(_T("Check Point: %d\n"), Status.dwCheckPoint); _tprintf(_T("Wait Hint: %d\n"), Status.dwWaitHint); #if _WIN32_WINNT >= 0x500 // получаем и отображаем расширенную информацию о состоянии SERVICE_STATUS_PROCESS StatusEx; DWORD cbNeeded; if (!QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, (PBYTE)&StatusEx, sizeof(StatusEx), &cbNeeded)) { PrintError(_T("QueryServiceStatus failed"), GetLastError()); CloseServiceHandle(hService); return 1; } PCTSTR pszSystemProcess; if (StatusEx.dwServiceFlags & SERVICE_RUNS_IN_SYSTEM_PROCESS) pszSystemProcess = _T("Yes"); else pszSystemProcess = _T("No"); _tprintf(_T("Process Id: %d\n"), StatusEx.dwProcessId); _tprintf(_T("System Process: %s\n"), pszSystemProcess); #endif CloseServiceHandle(hService); return 0; } |
Функция DemoServiceStatus, представленная в этом примере, входит в демонстрационную программу svcctrl.exe, которая содержит все примеры для этой части статьи. DemoServiceStatus будет вызвана, если svcctrl.exe запускается с параметром status в командной строке:
svcctrl status <имя службы> |
В этом примере я ограничился директивами препроцессора, чтобы отделить код, предназначенный для исполнения в Windows 2000 и более поздних версиях. В реальной программе следовало бы находить адрес функции QueryServiceStatusEx в процессе выполнения с помощью GetProcAddress в зависимости от версии Windows.
Для запуска службы Win32 API предоставляет функцию StartService:
BOOL StartService( IN SC_HANDLE hService, // handle службы IN DWORD dwArgc, // количество аргументов IN PCTSTR * pArgv // массив аргументов ); |
Функция принимает handle службы, который должен быть открыт с правом доступа SERVICE_START, а также массив аргументов, который будет передан службе. Каждая служба начинает свое выполнение с функции под условным названием ServiceMain, прототип которой напоминает прототип функции main:
VOID WINAPI ServiceMain( IN DWORD dwArgc, // количество аргументов IN PCTSTR * pArgv // массив аргументов ); |
Когда вызывается точка входа службы, pArgv[0] содержит имя службы, а остальные параметры соответствуют параметрам, переданным при вызове StartService.
Возможность передачи параметров службе при запуске используется не очень широко. Мне не известно, чтобы какая-либо из служб, поставляемых в составе Windows NT, принимала параметры таким образом. Если передавать параметры не требуется, то dwArgc следует указать как 0, а pArgv - как NULL.
На рис. 2 показано что происходит, когда приложение вызывает функцию StartService и на момент вызова StartService процесс службы еще не существует.
Рис. 2. Процесс запуска службы
Существенное значение с точки зрения программы управления службами имеет то, что функция StartService лишь инициирует запуск службы, она не дожидается, пока служба завершит свою инициализацию. Следует иметь это в виду, если приложение должно сразу после запуска начать каким-либо образом взаимодействовать со службой. Например, если служба является Web-сервером, то попытка установить TCP/IP соединение с ней сразу после вызова StartService скорее всего закончится неудачей, поскольку служба еще не успела создать сокет, принимающий соединения от клиентов.
В таких случаях следует дождаться завершения инициализации службы, вызывая QueryServiceStatus в цикле до тех пор, пока состояние службы не изменится на SERVICE_RUNNING или SERVICE_STOPPED (последнее возможно, если служба по каким-либо причинам не сможет произвести инициализацию и завершится с ошибкой). Приводимый далее пример как раз иллюстрирует этот механизм.
Как вы, наверное, помните из первой части статьи, нормальное функционирование некоторых служб может зависеть от других служб и существует специальный конфигурационный параметр, который описывает зависимости службы. Все службы, перечисленные как зависимости данной службы, должны быть запущены, прежде чем служба сможет начать выполнение. К счастью, SCM берет на себя заботу по запуску всех зависимостей. Все что нужно сделать - это вызвать StartService, а SCM проверит все необходимые службы, и если какая-то из этих служб еще не запущена, запустит ее.
Функция DemoStartService, исходный код которой приведен в листинге 2, реализует команду start в демонстрационном приложении svcctrl.exe. Чтобы увидеть ее в действии, следует запустить программу следующим образом:
svcctrl start <имя службы> |
//----------------------------------------------------------------------- // DemoServiceStart // // Запускает указанную службу. // // Параметры: // hSCM - handle SCM // pszServiceName - имя службы // // Возвращает: // ноль - при успешном завершении, отличное от нуля значение - в // противном случае. // int DemoServiceStart( IN SC_HANDLE hSCM, IN PCTSTR pszServiceName ) { _ASSERTE(hSCM != NULL); _ASSERTE(pszServiceName != NULL); SC_HANDLE hService; SERVICE_STATUS Status; // открываем handle службы с правами SERVICE_START и // SERVICE_QUERY_STATUS hService = OpenService(hSCM, pszServiceName, SERVICE_START|SERVICE_QUERY_STATUS); if (hService == NULL) return PrintError(_T("OpenService failed"), GetLastError()); // запускаем службу if (!StartService(hService, 0, NULL)) { PrintError(_T("StartService failed"), GetLastError()); CloseServiceHandle(hService); return 1; } // дожидаемся завершения инициализации службы for (;;) { if (!QueryServiceStatus(hService, &Status)) { PrintError(_T("QueryServiceStatus failed"), GetLastError()); _VERIFY(CloseServiceHandle(hService)); return 1; } if (Status.dwCurrentState != SERVICE_START_PENDING) break; DWORD dwWait = Status.dwWaitHint; if (dwWait == 0) dwWait = 1000; Sleep(dwWait); } CloseServiceHandle(hService); if (Status.dwCurrentState != SERVICE_RUNNING) { PrintError(_T("Unable to start service"), Status.dwWin32ExitCode); return 1; } return 0; } |
Во время работы служба постоянно следит за командами управления от операционной системы и управляющих программ (за это отвечает функция StartServiceCtrlDispatcher, которая, как вы помните, работает в главном потоке процесса службы, в то время как сами службы работают в своих собственных потоках). Чтобы послать службе управляющую команду, нужно воспользоваться функцией ControlService:
BOOL ControlService( IN SC_HANDLE hService, // описатель службы IN DWORD dwControl, // код управления OUT LPSERVICE_STATUS pServiceStatus // состояние службы ); |
Первый параметр этой функции идентифицирует службу, которой посылается команда управления, второй - задает код посылаемой команды, а последний параметр указывает на структуру, в которую функция заносит информацию о текущем состоянии службы, избавляя тем самым программиста от лишнего вызова QueryServiceStatus. Ниже перечислены команды управления, которые могут быть посланы с помощью этой функции:
SERVICE_CONTROL_STOP
Остановка службы. Если это единственная служба в процессе, то процесс службы
тоже завершается.
SERVICE_CONTROL_PAUSE
Приостанавливает выполнение службы. Точный смысл этой команды зависит
от конкретной службы. В простейшем случае эта команда может просто
приостанавливать поток службы. В более сложных случаях, например,
в случае службы Web-сервера, эта команда может приводить к тому, что
перестают приниматься новые запросы от клиентов, а уже начатые
запросы завершаются как обычно.
SERVICE_CONTROL_CONTINUE
Эта команда естественным образом дополняет предыдущую. Она
восстанавливает нормальное функционирование службы, находящейся
в приостановленном состоянии.
SERVICE_CONTROL_INTERROGATE
Запрашивает текущее состояние службы. Обрабатывая эту команду, служба
должна вызвать SetServiceStatus, чтобы сообщить SCM и приложению
свое текущее состояние. Этим команда отличается от функции
QueryServiceStatus, которая не запрашивает саму службу, а возвращает
последнее известное SCM состояние.
SERVICE_CONTROL_PARAMCHANGE
Некоторые службы имеют настраиваемые параметры в реестре или конфигурационных
файлах, которые так или иначе влияют на выполнение службы. Эта команда,
появившаяся в Windows 2000, позволяет сообщить службе, что изменился один
или несколько таких параметров. Обрабатывая эту команду, служба заново
считывает параметры и модифицирует свою работу соответствующим образом.
Чтобы достичь такого же эффекта в предыдущих версиях системы, разработчики определяли свои собственные команды управления с кодами, лежащими в диапазоне 128-255, зарезервированном для пользовательских команд управления. Таким образом, добавив команду SERVICE_CONTROL_PARAMCHANGE в Windows 2000, Microsoft решила стандартизовать эту практику.
SERVICE_CONTROL_NETBINDADD
Оповещает сетевую службу, что появился новый компонент для связывания.
SERVICE_CONTROL_NETBINDREMOVE
Оповещает сетевую службу, что связываемый компонент был удален.
SERVICE_CONTROL_NETBINDDISABLE
Оповещает сетевую службу, что одна из связей блокирована.
SERVICE_CONTROL_NETBINDENABLE
Оповещает сетевую службу, что блокированная связь снова работает.
Чтобы посылаемая команда достигла службы, необходимо выполнение двух условий. Во-первых, handle службы должен быть открыт с подходящим правом доступа. Во-вторых, служба должна сообщить SCM, что она поддерживает этот вид команд. Служба сообщает, какие команды она поддерживает, в поле dwControlsAccepted структуры SERVICE_STATUS. В таблице 2 сведены вместе команды управления, соответствующие им флаги, возвращаемые в поле dwControlsAccepted, и необходимые для выполнения команды права доступа.
Команда SERVICE_CONTROL_... | Флаг SERVICE_ACCEPT_... | Право доступа SERVICE_... |
---|---|---|
STOP | STOP | STOP |
PAUSE | PAUSE_CONTINUE | PAUSE_CONTINUE |
CONTINUE | PAUSE_CONTINUE | PAUSE_CONTINUE |
INTERROGATE | не определено* | INTERROGATE |
PARAMCHANGE** | PARAMCHANGE | PAUSE_CONTINUE |
NETBINDADD** | NETBINDCHANGE | PAUSE_CONTINUE |
NETBINDREMOVE** | NETBINDCHANGE | PAUSE_CONTINUE |
NETBINDENABLE** | NETBINDCHANGE | PAUSE_CONTINUE |
NETBINDDISABLE** | NETBINDCHANGE | PAUSE_CONTINUE |
SHUTDOWN | SHUTDOWN | не определено*** |
HARDWAREPROFILECHANGE** | HARDWAREPROFILECHANGE | не определено*** |
POWEREVENT** | POWEREVENT | не определено*** |
SESSIONCHANGE**** | SESSIONCHANGE | не определено*** |
Пользовательские команды | не определено | USER_DEFINED_CONTROL |
Как уже отмечалось, команды остановки, приостановки и возобновления непосредственным образом влияют на состояние службы. Как и в случае запуска, служба меняет свое состояние не мгновенно. На рис. 3 показаны основные этапы обработки этих команд службой.
Рис. 3. Обработка команд остановки, приостановки и возобновления
Мы рассмотрели наиболее общий вариант обработки команд службой. Некоторые службы могут поступать проще и поместить всю обработку команды в функцию Handler, в которой сразу же сообщать о новом состоянии. Тем не менее, в программе управления службами мы не можем рассчитывать на то, что служба всегда ведет себя таким образом. Мы должны быть готовы к тому, что с момента возврата из ControlService до момента, когда служба примет конечное состояние, может пройти некоторое время.
Если нужно дождаться момента, когда служба закончит обработку команды, придется вызывать в цикле функцию QueryServiceStatus до тех пор, пока служба не примет нужное нам состояние. Поскольку многие команды обрабатываются сходным образом, я решил написать универсальную функцию под названием ControlServiceAndWait (листинг 3), которая посылает команду управления службе и дожидается, пока служба не примет указанное состояние.
//----------------------------------------------------------------------- // ControlServiceAndWait // // Посылает команду управления службы и ждет, пока служба не примет // указанное cостояние. // // Параметры: // hService - handle службы // dwControl - код команды управления // dwState - состояние, которого следует ждать // dwTimeout - таймаут в миллисекундах // // Возвращает: // TRUE - при успешном завершении, FALSE - в случае ошибки. // BOOL ControlServiceAndWait( IN SC_HANDLE hService, IN DWORD dwControl, IN DWORD dwState, IN DWORD dwTimeout ) { _ASSERTE(hService != NULL); SERVICE_STATUS Status; DWORD dwStart; DWORD dwCheckPoint = (DWORD)-1; DWORD dwWait; // посылаем команду управления if (!ControlService(hService, dwControl, &Status)) return FALSE; // отмечаем время начала операции dwStart = GetTickCount(); // ожидаем пока служба примет требуемое состояние while (Status.dwCurrentState != dwState && Status.dwCurrentState != SERVICE_STOPPED) { // проверяем, не истек ли таймаут if (dwTimeout != INFINITE) { if (GetTickCount() - dwStart >= dwTimeout) return SetLastError(ERROR_TIMEOUT), FALSE; } // определяем величину задержки до следующей проверки // состояния if (dwCheckPoint != Status.dwCheckPoint) { dwCheckPoint = Status.dwCheckPoint; dwWait = Status.dwWaitHint; } else { dwWait = 1000; } // пауза Sleep(dwWait); // получаем состояние службы if (!QueryServiceStatus(hService, &Status)) return FALSE; } if (Status.dwCurrentState == SERVICE_STOPPED && Status.dwWin32ExitCode != ERROR_SUCCESS) { SetLastError(Status.dwWin32ExitCode); return FALSE; } return TRUE; } |
Функция первым делом вызывает ControlService, чтобы послать команду управления службе. Кроме того, ControlService возвращает текущее состояние службы, так что если служба сразу выполнила команду и перешла в нужное состояние, ControlServiceAndWait сразу же завершается, не делая никаких дополнительных вызовов.
В цикле ожидания проверяется, не истек ли срок, отведенный на выполнение команды, а также определяется, через какой интервал времени следует снова проверить состояние. Для этого используются поля dwCheckPoint и dwWaitHint структуры SERVICE_STATUS. При выполнении длительной операции, служба периодически увеличивает значение dwCheckPoint, занося при этом в поле dwWaitHint количество миллисекунд до следующей контрольной точки. Если служба не использует эти поля, или если служба не успела обновить свое состояние за время ожидания, делается пауза в одну секунду.
Условие выхода из цикла включает в себя не только требуемое состояние, но и состояние SERVICE_STOPPED. Это сделано для того, чтобы учесть вариант остановки службы из-за ошибки. В таком случае функция анализирует поле dwWin32ExitCode, чтобы сообщить код ошибки вызывающей программе.
При остановке службы вновь возникает вопрос о зависимостях, но теперь нас интересуют службы, зависящие от останавливаемой. Зависимые службы тоже нужно остановить, поскольку в противном случае их корректная работа не гарантируется. В отличие от запуска служб, SCM не останавливает зависимые службы автоматически. Программа, производящая остановку службы, должна самостоятельно определить, работают ли зависимые службы, и принять решение: остановить эти службы или отменить операцию.
В конфигурации службы нет явной информации о зависимых от нее службах. К счастью Win32 API предоставляет функцию, которая одним вызовом возвращает сразу все зависимые службы. Эта функция называется EnumDependentServices:
BOOL EnumDependentServices( SC_HANDLE hService, // handle службы DWORD dwServiceState, // состояние служб LPENUM_SERVICE_STATUS pServices, // приемный буфер DWORD cbBufSize, // размер приемного буфера PDWORD pcbBytesNeeded, // требуемый размер буфера PDWORD pServicesReturned // количество служб ); |
Параметр dwServiceState указывает интересующее нас состояние служб. Если его указать как SERVICE_ACTIVE, то функция перечислит только те зависимые службы, которые выполняются в данный момент (или, более точно, состояние которых отлично от SERVICE_STOPPED). Другими допустимыми значениями для этого параметра являются SERVICE_INACTIVE и SERVICE_STATE_ALL. В первом случае функция перечислит только остановленные службы, а во втором - все зависимые службы, независимо от их состояния.
При успешном завершении, функция заполняет буфер, указываемый параметром pServices, структурами ENUM_SERVICE_STATUS, занося при этом в переменную, адрес которой задан в качестве pServicesReturned, количество записанных структур. Если размер буфера недостаточен, чтобы принять информацию обо всех службах, то функция завершается с ошибкой ERROR_MORE_DATA, при этом в переменную, указываемую параметром pcbBytesNeeded, заносится требуемый размер буфера.
Структура ENUM_SERVICE_STATUS определена в заголовочном файле winsvc.h следующим образом:
typedef struct _ENUM_SERVICE_STATUS { PTSTR lpServiceName; // имя службы PTSTR lpDisplayName; // название службы SERVICE_STATUS ServiceStatus; // текущее состояния службы } ENUM_SERVICE_STATUS, * LPENUM_SERVICE_STATUS; |
Как видно из определения, структура содержит внутреннее имя службы и ее название, а также текущее состояние службы, которое описывается уже известной нам структурой SERVICE_STATUS.
Важным свойством функции EnumDependentServices является то, что она возвращает зависимые службы в том порядке, в котором их следует останавливать, учитывая возможные зависимости между службами и порядок, задаваемый группами загрузки.
Теперь мы можем расширить программу svcctrl.exe функциями остановки, приостановки и возобновления. В частности, функция остановки может выглядеть, как показано в листинге 4.
//----------------------------------------------------------------------- // DemoServiceStop // // Останавливает указанную службу. // // Параметры: // hSCM - handle SCM // pszServiceName - имя службы // // Возвращает: // ноль - при успешном завершении, отличное от нуля значение - в // противном случае. // int DemoServiceStop( IN SC_HANDLE hSCM, IN PCTSTR pszServiceName ) { _ASSERTE(hSCM != NULL); _ASSERTE(pszServiceName != NULL); SC_HANDLE hService; // открываем handle службы с правами SERVICE_STOP и // SERVICE_QUERY_STATUS hService = OpenService(hSCM, pszServiceName, SERVICE_STOP|SERVICE_QUERY_STATUS); if (hService == NULL) return PrintError(_T("OpenService failed"), GetLastError()); // посылаем службе команду SERVICE_CONTROL_STOP и дожидаемся // пока она примет состояние SERVICE_STOPPED if (!ControlServiceAndWait(hService, SERVICE_CONTROL_STOP, SERVICE_STOPPED, 120000)) { PrintError(_T("ControlServiceAndWait failed"), GetLastError()); CloseServiceHandle(hService); return 1; } CloseServiceHandle(hService); return 0; } |
Как видно из приведенного примера, использование функции ControlServiceAndWait делает реализацию остановки службы тривиальной. Все, что нужно сделать - это открыть handle службы с подходящими правами доступа и вызвать СontrolServiceAndWait, указав ей код команды и конечное состояние, которое должна принять служба в результате выполнения команды.
Если мы теперь скомбинируем эту функцию и функцию EnumDependentServices, то получим полноценную функцию остановки службы, учитывающую зависимости. Текст этой функции приведен в листинге 5.
//----------------------------------------------------------------------- // DemoServiceStopWithDependents // // Останавливает указанную службу и все зависимые от нее службы. // // Параметры: // hSCM - handle SCM // pszServiceName - имя службы // // Возвращает: // ноль - при успешном завершении, отличное от нуля значение - в // противном случае. // int DemoServiceStopWithDependents( IN SC_HANDLE hSCM, IN PCTSTR pszServiceName ) { _ASSERTE(hSCM != NULL); _ASSERTE(pszServiceName != NULL); SC_HANDLE hService; LPENUM_SERVICE_STATUS pStatus; DWORD cbNeeded, i; DWORD cServices = 0; // открываем handle службы с правом доступа // SERVICE_ENUMERATE_DEPENDENTS hService = OpenService(hSCM, pszServiceName, SERVICE_ENUMERATE_DEPENDENTS); if (hService == NULL) return PrintError(_T("OpenService failed"), GetLastError()); // определяем требуемый размер выходного буфера if (!EnumDependentServices(hService, SERVICE_ACTIVE, NULL, 0, &cbNeeded, &cServices)) { if (GetLastError() != ERROR_MORE_DATA) { PrintError(_T("EnumDependentServices failed"), GetLastError()); CloseServiceHandle(hService); return 1; } } if (cbNeeded > 0) { // выделяем память для выходного буфера pStatus = (LPENUM_SERVICE_STATUS)LocalAlloc(LMEM_FIXED, cbNeeded); if (pStatus == NULL) { _tprintf(_T("Not enough memory\n")); CloseServiceHandle(hService); return 1; } // получаем список зависимых служб if (!EnumDependentServices(hService, SERVICE_ACTIVE, pStatus, cbNeeded, &cbNeeded, &cServices)) { PrintError(_T("EnumDependentServices failed"), GetLastError()); LocalFree((HLOCAL)pStatus); CloseServiceHandle(hService); return 1; } } // останавливаем все зависимые службы for (i = 0; i < cServices; i++) { _tprintf(_T("Stopping %s...\n"), pStatus[i].lpDisplayName); if (DemoServiceStop(hSCM, pStatus[i].lpServiceName)) break; } LocalFree((HLOCAL)pStatus); CloseServiceHandle(hService); if (i < cServices) return 1; // наконец, останавливаем указанную службу _tprintf(_T("Stopping %s...\n"), pszServiceName); return DemoServiceStop(hSCM, pszServiceName); } |
Эта функция вызывается, если svcctrl.exe запускается с параметром командной строки stop:
svcctrl stop <имя службы> |
Демонстрационная программа svcctrl.exe содержит также функции для приостановки и возобновления служб. Эти функции практически ничем не отличаются от функции DemoServiceStop, поэтому мы не будем здесь на них останавливаться.
Когда база данных SCM заблокирована, ни одна служба не может быть запущена - любая попытка вызвать StartService завершится с ошибкой ERROR_SERVICE_DATABASE_LOCKED. Зачем это может понадобиться? Например, ваша программа может изменять конфигурацию нескольких взаимосвязанных служб, и вы не хотите, чтобы какая-либо из служб была запущена в процессе изменения конфигурации. Особенно это актуально, когда запуск службы не находится под вашим контролем; например, если служба является COM-сервером, то она может быть запущена в результате запроса на активацию, поступившего по сети. Другими словами, блокировка базы данных SCM должна использоваться всякий раз, когда существует вероятность запуска службы в процессе изменения конфигурации.
Для блокировки БД SCM служит функция LockServiceDatabase:
SC_LOCK LockServiceDatabase(
SC_HANDLE hSCM // handle SCM
);
|
Единственным параметром этой функции является handle SCM, который должен быть открыт с правом доступа SC_MANAGER_LOCK. При успешном завершении функция возвращает значение, идентифицирующее блокировку - оно понадобится позже, чтобы снять блокировку с помощью функции UnlockServiceDatabase:
BOOL UnlockServiceDatabase(
SC_LOCK ScLock // идентификатор блокировки
);
|
В каждый момент времени БД SCM может блокировать только один процесс, поэтому следует блокировать ее на как можно более короткий промежуток времени, чтобы не вызывать конфликтов с другими приложениями. Если процесс, владеющий блокировкой, завершается, не освободив ее, то менеджер системных служб снимает блокировку автоматически.
Чтобы узнать текущее состояние блокировки, можно воспользоваться функцией QueryServiceLockStatus:
BOOL QueryServiceLockStatus( SC_HANDLE hSCM, // handle SCM LPQUERY_SERVICE_LOCK_STATUS pLockStatus, // приемный буфер DWORD cbBufSize, // размер буфера в байтах PDWORD pcbBytesNeeded // требуемый размер буфера ); |
Функция заносит информацию о блокировке в буфер, формат которого описывается структурой QUERY_SERVICE_LOCK_STATUS:
typedef struct _QUERY_SERVICE_LOCK_STATUS { DWORD fIsLocked; // признак наличия блокировки PTSTR lpLockOwner; // владелец блокировки DWORD dwLockDuration; // длительность владения блокировкой } QUERY_SERVICE_LOCK_STATUS, *LPQUERY_SERVICE_LOCK_STATUS; |
Если база данных SCM не заблокирована, то поле fIsLocked будет установлено в FALSE, а содержимое остальных полей не будет иметь значения. В противном случае, в поле lpLockOwner заносится имя пользователя, владеющего блокировкой, а в поле dwLockDuration - время владения блокировкой в секундах. Когда я впервые познакомился с функцией QueryServiceLockStatus, я был несколько удивлен составом возвращаемой ею информации. Я ожидал увидеть в структуре QUERY_SERVICE_LOCK_STATUS идентификатор процесса, владеющего блокировкой, поскольку блокировка связана с захватившим ее процессом, а не пользователем. Eсли какой-либо процесс захватит блокировку и забудет о ней, продолжая работать, единственный способ найти нарушителя - это поочередно завершать все запущенные процессы. Если бы функция возвращала идентификатор процесса-владельца, задача была бы намного проще.
В заключение хочу еще раз отметить, что блокировка базы данных SCM оказывает влияние лишь на способность запуска новых служб с помощью функции StartService, она никак не влияет на остальные функции управления службами.
До этого момента мы не задавались вопросом, каким образом службы появляются на компьютере пользователя. Большинство служб устанавливается вместе с Windows NT, однако для независимых разработчиков существует возможность установки дополнительных служб. В этой части статьи мы рассмотрим механизмы установки и удаления системных служб в Windows NT.
Для установки служб Win32 API предоставляет функцию CreateService, которая имеет довольно много параметров, но подавляющее большинство из них уже обсуждалось выше:
SC_HANDLE CreateService( IN SC_HANDLE hSCM, // handle SCM IN PCTSTR pServiceName, // имя службы IN PCTSTR pDisplayName, // название службы IN DWORD dwDesiredAccess, // доступ к объекту службы IN DWORD dwServiceType, // тип службы IN DWORD dwStartType, // способ запуска службы IN DWORD dwErrorControl, // реакция на ошибки при загрузке IN PCTSTR pBinaryPathName, // исполняемый файл IN PCTSTR pLoadOrderGroup, // группа загрузки IN PDWORD pdwTagId, // тег (NULL для служб Win32) IN PCTSTR pDependencies, // зависимости IN PCTSTR pServiceStartName, // учетная запись службы IN PCTSTR pPassword // пароль учетной записи ); |
При успешном завершении функция возвращает handle службы. В случае ошибки функция возвращает NULL, и код ошибки может быть получен с помощью GetLastError.
Если посмотреть на описание функции ChangeServiceConfig, описанной в первой части статьи, то можно обнаружить, что большая часть параметров этих функций совпадает. Все совпадающие параметры имеют тот же смысл - в функции CreateService они задают начальную конфигурацию устанавливаемой службы. Параметры, которые не пересекаются с определением ChangeServiceConfig, перечислены ниже.
hSCMЭтот параметр - handle SCM, который, напомню, возвращается функцией OpenSCManager. Фактически, этот параметр идентифицирует компьютер, на котором устанавливается служба. Для успешного выполнения CreateService, handle SCM должен быть открыт с правом доступа SC_MANAGER_CREATE_SERVICE, которое имеют только администраторы компьютера.
pServiceNameЗадает имя службы. Это единственное место, где можно задать имя службы. После того, как служба установлена, единственный способ изменить ее внутреннее имя - удалить ее и установить заново.
dwDesiredAccessЭтот параметр имеет тот же смысл, что и аналогичный параметр OpenService - он задает набор прав доступа к объекту службы. Поскольку функцию CreateService могут вызывать только администраторы, они могут указать в этом параметре любой набор прав доступа, не опасаясь отказа. Тем не менее, хороший стиль работы с объектами Windows NT состоит в том, чтобы запрашивать только те права доступа, которые действительно необходимы.
Одно из мест, где может вызываться функция CreateService - это программа-инсталлятор. Также очень часто разработчики включают код для установки службы в саму службу. В этом случае, чтобы произвести установку, необходимо запустить исполняемый файл службы с определенным параметром командной строки. Например, ATL Wizard поступает именно так, когда генерирует код для проекта службы.
Тестовая служба voidsvc, которая упоминалась в первой части статьи, не является исключением из этого правила. Если ее запустить с параметром командной строки "/install",
voidsvc /install:<имя службы> |
она установит себя в качестве службы. voidsvc позволяет зарегистрировать себя под произвольным именем, поэтому параметр командной строки "/install" имеет дополнительный аргумент, указывающий имя службы. Это нетипичная ситуация: подавляющее большинство служб имеют фиксированное имя.
Обнаружив параметр "/install" в командной строке, voidsvc вызывает функцию Install (см. листинг 6).
//----------------------------------------------------------------------- // Install // // Устанавливает службу. // // Параметры: // pszName - имя службы // // Возвращает: // TRUE - при успешном завершении, FALSE - в случае ошибки. // BOOL Install( IN PCTSTR pszName ) { SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE); if (hSCM == NULL) return FALSE; TCHAR szPathName[MAX_PATH]; GetModuleFileName(NULL, szPathName + 1, countof(szPathName) - 3 - lstrlen(pszName)); szPathName[0] = _T('\"'); lstrcat(szPathName, _T("\" ")); lstrcat(szPathName, pszName); SC_HANDLE hService = CreateService(hSCM, pszName, pszName, 0, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, szPathName, NULL, NULL, NULL, NULL, NULL); if (hService != NULL) CloseServiceHandle(hService); CloseServiceHandle(hSCM); return (hService != NULL); } |
Обратите внимание на вызов GetModuleFileName, с помощью которого voidsvc определяет свое текущее местоположение, чтобы сформировать командную строку, передаваемую в качестве параметра CreateService. Это стандартный прием, используемый в функциях самоустановки служб.
Иногда в голову пользователя приходят совершенно сумасшедшие идеи. Например, его может осенить совершенно дикая идея - удалить со своего компьютера созданный вами шедевр. В этом случае, программа удаления должна удалить и службу, если таковая есть в составе продукта. Для удаления службы предназначена функция DeleteService:
BOOL DeleteService(
SC_HANDLE hService // handle службы
);
|
Единственным параметром этой функции является handle удаляемой службы, который должен быть открыт с правом доступа DELETE.
Важно заметить, что DeleteService лишь помечает службу для удаления. Фактически служба будет удалена, когда будет закрыт последний указывающий на нее handle. Один открытый handle, очевидно, удерживает программа, вызывающая DeleteService. Другой handle удерживает SCM до тех пор, пока служба не будет остановлена. Таким образом, чтобы окончательно удалить службу, необходимо убедиться, что она остановлена. Впрочем, мы уже умеем останавливать службы, так что это не должно быть проблемой.
Листинг 7 содержит исходный код функции Uninstall. Эту функцию программа voidsvcвызывает при запуске с параметром командной строки "/uninstall".
//----------------------------------------------------------------------- // Uninstall // // Удаляет службу. // // Параметры: // pszName - имя службы // // Возвращает: // TRUE - при успешном завершении, FALSE - в случае ошибки. // BOOL Uninstall( IN PCTSTR pszName ) { SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT); if (hSCM == NULL) return FALSE; SC_HANDLE hService = OpenService(hSCM, pszName, SERVICE_STOP|DELETE); if (hService == NULL) { CloseServiceHandle(hSCM); return FALSE; } SERVICE_STATUS Status; ControlService(hService, SERVICE_CONTROL_STOP, &Status); Sleep(200); BOOL bOk = DeleteService(hService); CloseServiceHandle(hService); CloseServiceHandle(hSCM); return bOk; } |
Функция Uninstall немного нарушает правила в целях упрощения. Во-первых, она вызывает ControlService вне зависимости от текущего состояния службы, полагаясь на то, что функция завершится с ошибкой, если служба в данный момент не запущена. По этой причине возвращаемое значение ControlService не анализируется. Во-вторых, она вызывает Sleep(200), вместо того, чтобы честно дождаться завершения службы, считая, что 200 миллисекунд - более чем достаточно для завершения службы, которая ничего не делает. Если хотите делать все по правилам - вы знаете как.
Иногда может встретиться необходимость перечислить все службы, установленные в системе. Например, это понадобится, если вы захотите написать свою программу управления службами, такую, как демонстрационная программа svcadmin, прилагающаяся к этой статье. В это случае придется воспользоваться функцией EnumServicesStatus:
BOOL EnumServicesStatus( IN SC_HANDLE hSCM, // handle SCM IN DWORD dwServiceType, // тип перечисляемых служб IN DWORD dwServiceState, // состояние перечисляемых служб OUT LPENUM_SERVICE_STATUS pServices, // приемный буфер IN DWORD cbBufSize, // размер буфера в байтах OUT PDWORD pcbBytesNeeded, // требуемый размер буфера OUT PDWORD pServicesReturned, // количество служб IN OUT PDWORD pResumeHandle // идентифицирует перечисление ); |
Ниже перечислены параметры этой функции.
hSCMИдентифицирует SCM, службы которого требуется перечислить. Этот handle должен быть открыт с правом доступа SC_MANAGER_ENUMERATE_SERVICES.
dwServiceTypeЗадает тип перечисляемых служб. В качестве этого параметра может быть указан флаг SERVICE_DRIVER, либо флаг SERVICE_WIN32, либо оба. В первом случае будут перечислены только службы ядра, во втором - только службы Win32, а в третьем - и те, и другие.
dwServiceStateЭтот параметр позволяет отфильровать службы по состоянию. Если в качестве этого параметра указано значение SERVICE_INACTIVE, будут перечислены только службы в состоянии SERVICE_STOPPED, то есть остановленные. Если же этот параметр указать как SERVICE_ACTIVE, то будут перечислены все запущенные службы, состояние которых отлично от SERVICE_STOPPED. Наконец, если значением этого параметра является SERVICE_STATE_ALL, будут перечислены все службы.
pServicesУказывает на буфер, принимающий информацию об установленных службах. Формат буфера описывается структурой ENUM_SERVICE_STATUS, с которой мы уже знакомы.
cbBufSizeЗадает размер приемного буфера в байтах.
pcbBytesNeededЭтот параметр является указателем на переменную, в которую функция заносит размер буфера, необходимый для того, чтобы принять информацию обо всех службах. Приложение может использовать это значение, чтобы выделить буфер подходящего размера.
pServicesReturnedУказывает на переменную, в которую функция заносит количество служб, информация о которых поместилась в указанный буфер. Этот параметр необходим, так как количество служб нельзя рассчитать, как cbBufSize / sizeof(ENUM_SERVICE_STATUS). Дело в том, что структура ENUM_SERVICE_STATUS содержит указатели на строки переменной длины, которые также помещаются в выходной буфер.
pResumeHandleДанная функция позволяет считывать информацию об установленных службах по частям. Через этот параметр возвращается информация, с помощью которой можно продолжить чтение списка. Если функция возвращает FALSE и код ошибки, возвращаемый GetLastError, равен ERROR_MORE_DATA, значит, не вся информация о службах поместилась в выходной буфер, и следует произвести повторное чтение.
Есть две стратегии использования этой функции. Первая состоит в том, чтобы заранее определить размер буфера необходимый для вмещения информации о всех службах. Для этого нужно вызвать EnumServicesStatus, передав NULL в параметр pServices и 0 в cbBufSize. В этом случае функция вернет необходимый размер буфера в переменную, адрес которой необходимо указать в параметре pcbBytesNeeded. После этого нужно выделить буфер соответствующего размера и вызвать функцию еще раз.
Вторая стратегия заключается в том, чтобы заранее выделить буфер, гарантированно вмещающий информацию об одной или более службах. Поскольку длина внутреннего имени и названия службы ограничены 256 символами плюс один символ для завершающего нулевого символа, то минимальный размер буфера можно рассчитать как sizeof(ENUM_SERVICE_STATUS) + (256 + 1) * 2 * sizeof(TCHAR). Теперь, если вызвать функцию EnumServicesStatus, она поместит в буфер порцию информации о состоянии служб, а в переменную по адресу pResumeHandle запишет значение, идентифицирующее текущую позицию в перечислении. При первом вызове EnumServicesStatus нужно инициализировать эту переменную нулем. При последующих вызовах функция будет использовать ее значение, чтобы определить, информация о каких службах уже выдана и возвращать очередную порцию данных.
Чтобы окончательно разобраться в этом вопросе, давайте рассмотрим второй подход на примере. В листинге 8 приведен текст программы, которая перечисляет все установленные службы (вы также можете найти его под именем svclist в архиве с исходным кодом; скомпилированная версия программы доступна в архиве с демонстрационными программами).
int _tmain( int argc, _TCHAR * argv[] ) { SC_HANDLE hSCM; int nRet = 0; // соединяемся со SCM на локальной машине hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ENUMERATE_SERVICE); if (hSCM == NULL) return PrintError(_T("OpenSCManager failed"), GetLastError()); BYTE bBuffer[2048]; DWORD dwResumeHandle = 0; DWORD cbNeeded; DWORD cServices; BOOL bContinue; DWORD dwError; LPENUM_SERVICE_STATUS pStatus; do { bContinue = FALSE; pStatus = (LPENUM_SERVICE_STATUS)bBuffer; // извлекаем очередную порцию if (!EnumServicesStatus(hSCM, SERVICE_WIN32, SERVICE_STATE_ALL, pStatus, sizeof(bBuffer), &cbNeeded, &cServices, &dwResumeHandle)) { dwError = GetLastError(); if (dwError != ERROR_MORE_DATA) { nRet = PrintError(_T("EnumServicesStatus failed"), dwError); break; } bContinue = TRUE; } // отображаем информацию о службах в этой порции for (DWORD i = 0; i < cServices; i++) { PCTSTR pszStatus; switch (pStatus->ServiceStatus.dwCurrentState) { case SERVICE_STOPPED: pszStatus = _T("Stopped"); break; case SERVICE_RUNNING: pszStatus = _T("Running"); break; case SERVICE_PAUSED: pszStatus = _T("Paused"); break; case SERVICE_STOP_PENDING: pszStatus = _T("Stop Pending"); break; case SERVICE_START_PENDING: pszStatus = _T("Start Pending"); break; case SERVICE_PAUSE_PENDING: pszStatus = _T("Pause Pending"); break; case SERVICE_CONTINUE_PENDING: pszStatus = _T("Continue Pending"); break; default: pszStatus = _T("Unknown"); break; } if (lstrlen(pStatus->lpDisplayName) > 60) pStatus->lpDisplayName[60] = 0; _tprintf(_T("%-60s %s\n"), pStatus->lpDisplayName, pszStatus); // переходим к следующему элементу pStatus++; } } while (bContinue); CloseServiceHandle(hSCM); return nRet; } |
В Windows NT 4 EnumServicesStatus была единственной функцией, которая позволяла перечислить установленные службы. Начиная с Windows 2000, это можно сделать еще и с помощью функции EnumServicesStatusEx, которая является расширенным вариантом предыдущей функции.
BOOL EnumServicesStatusEx( IN SC_HANDLE hSCManager, // handle SCM IN SC_ENUM_TYPE InfoLevel, // вид информации IN DWORD dwServiceType, // тип перечисляемых служб IN DWORD dwServiceState, // состояние перечисляемых служб OUT PBYTE pServices, // приемный буфер IN DWORD cbBufSize, // размер приемного буфера OUT PDWORD pcbBytesNeeded, // требуемый размер буфера OUT PDWORD pServicesReturned, // количество служб IN OUT PDWORD pResumeHandle, // идентифицирует перечисление IN PCTSTR pszGroupName // имя группы загрузки ); |
Эта функция отличается от EnumServicesStatus в двух аспектах. Во-первых, она позволяет перечислить службы, принадлежащие конкретной группе загрузки, для чего служит параметр pszGroupName. Если этот параметр указан как NULL, то, как и раньше, будут перечислены все службы. Если указана пустая строка, то будут перечислены службы, которые не принадлежат никакой из групп загрузки.
Во-вторых, функция позволяет управлять видом возвращаемой информации посредством параметра InfoLevel. Хотя в настоящее время определено только одно значение для этого параметра - SC_ENUM_PROCESS_INFO - его наличие позволит Microsoft избежать добавления новой функции, когда в следующий раз потребуется расширить информацию о службе. Сегодня же, формат выходного буфера описывается структурой ENUM_SERVICE_STATUS_PROCESS:
typedef struct _ENUM_SERVICE_STATUS_PROCESS { LPTSTR lpServiceName; // имя службы LPTSTR lpDisplayName; // название службы SERVICE_STATUS_PROCESS ServiceStatusProcess; // состояние службы } ENUM_SERVICE_STATUS_PROCESS, * LPENUM_SERVICE_STATUS_PROCESS; |
Структура содержит все те же внутреннее имя и название службы, а также расширенную информацию о состоянии в виде структуры SERVICE_STATUS_PROCESS, о которой говорилось при описании функции QueryServiceStatusEx. Напомню, помимо основной информации о состоянии, эта структура содержит еще идентификатор процесса службы и флаг, показывающий, является ли этот процесс системным процессом Windows.
Вот и все, о чем я хотел вам рассказать про управление системными службами в Windows NT. Согласитесь, это не было очень сложно.
Если вы оглянетесь назад, то увидите, что мы затронули практически все функции Win32 API, так или иначе связанные с управлением службами. Для каждой функции вы можете найти работающий пример, а для некоторых функций - даже несколько, что позволит вам использовать эту статью в качестве практического руководства. В то же время, мы подробно изучили все конфигурационные параметры служб, а также рассмотрели по шагам процесс запуска и остановки службы. Эти знания помогут вам принять правильные решения при проектировании ваших служб.
Наконец, утилита svcadmin, которая изначально задумывалась как демонстрационное приложение, но быстро переросла в полноценную программу управления службами, просто поможет вам в повседневной работе, открывая доступ к тем параметрам конфигурации, от которых решили оградить нас разработчики MMC snap-in "Службы".
Управление системными службами - важная, но все же часть в общей архитектуре системных служб Windows NT. Если вы серьезно занимаетесь разработкой системных служб, то вам обязательно надо изучить использование журнала событий, счетчиков производительности, и, конечно, систему безопасности Windows NT. Статьи об этом и о многом другом обязательно появятся в нашем журнале. Stay tuned.
Сообщений 8 Оценка 640 [+1/-0] Оценить |