Читайте также: |
|
Основной задачей ServiceMain является инициализация сервиса и передача адреса callback-функции Handler сервиса, посредством вызова функции RegisterServiceCtrlHandler:
SERVICE_STATUS_HANDLE RegisterServiceCtrlHandler(LPCTSTR lpServiceName, LPHANDLER_FUNCTION lpHandlerProc);
в которой первый параметр указывает за каким сервисом закреплена Handler-функция, а второй - адрес Handler-функции. Параметр lpServiceName должен быть таким же, как имя сервиса в массиве структур SERVICE_TABLE_ENTRY, который передаётся функции StartServiceCtrlDispatcher. После этого функция RegisterServiceCtrlHandler возвращает значение SERVICE_STATUS_HANDLE, которое является просто 32-битным значением и используется SCM для уникальной идентификации сервиса. Если у сервиса появляется необходимость сообщить SCM свой текущий статус, то необходимо передать эту величину в качестве идентификатора сервиса (service handle) желаемой API-функции. В отличие от других идентификаторов объектов, присутствующих в системе Windows, закрывать явным образом идентификатор сервиса, возвращаемый функцией RegisterServiceCtrlHandler, не следует.
По возвращении функцией RegisterServiceCtrlHandler управления функции ServiceMain, поток последней должен незамедлительно сообщить SCM о том, что инициализация сервиса продолжается. Для этого используется функция SetServiceStatus, имеющая следующий прототип:
BOOL SetServiceStatus(SERVICE_STATUS_HANDLE hService, LPSERVICE_STATUS lpServiceStatus);
Эта функция требует в качестве первого параметра идентификатор сервиса, который был получен ранее при вызове функции RegisterServiceCtrlHandler, а в качестве второго параметра - адрес подготовленной структуры 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 показывает тип сервиса и может принимать значения, перечисленные в таблице 2.
Таблица 2. Значения флага типа сервиса.
Флаг | Значение | Описание |
SERVICE_WIN32_OWN_PROCESS | 0x00000010 | Сервис запускается в своем собственном процессе |
SERVICE_WIN32_SHARE_PROCESS | 0x00000020 | Сервис делит процесс с другими сервисами |
SERVICE_WIN32 | SERVICE_WIN32_OWN_PROCESS| SERVICE_WIN32_SHARE_PROCESS | Является комбинацией указанных выше типов сервисов |
SERVICE_KERNEL_DRIVER | 0x00000001 | Сервис является драйвером устройства |
SERVICE_FILE_SYSTEM_DRIVER | 0x00000002 | Сервис является драйвером файловой системы |
SERVICE_ADAPTER | 0x00000004 | Сервис является драйвером адаптера |
SERVICE_RECOGNIZER_DRIVER | 0x00000008 | Сервис является драйвером распознающего устройства |
SERVICE_DRIVER | SERVICE_KERNEL_DRIVER| SERVICE_FILE_SYSTEM_DRIVER| SERVICE_RECOGNIZER_DRIVER | Комбинация драйверных сервисов |
SERVICE_INTERACTIVE_PROCESS | 0x00000100 | Сервис может взаимодействовать с рабочим столом пользователя |
SERVICE_TYPE_ALL | SERVICE_WIN32| SERVICE_ADAPTER| SERVICE_DRIVER| SERVICE_INTERACTIVE_PROCESS | Является комбинацией всех вышеперечисленных типов сервисов |
Флаг SERVICE_WIN32_OWN_PROCESS устанавливается в случае, если исполняемый файл содержит только один сервис, если же сервисов более одного, то устанавливайте флаг SERVICE_WIN32_SHARE_PROCESS. В дополнение к этим двум флагам, с помощью оператора OR, можно установить флаг SERVICE_INTERACTIVE_PROCESS. Это позволит сервису отправлять диалоговые сообщения пользователю (но использовать флаг SERVICE_INTERACTIVE_PROCESS следует как можно реже). После установки флага (набора флагов), его значение не следует менять до окончания работы сервиса.
Член структуры dwCurrentState - наиболее важный член структуры SERVICE_STATUS. Он "подсказывает" SCM текущий статус сервиса. Для указания того, что сервис находится в процессе инициализации, необходимо установить значение этого члена в SERVICE_START_PENDING. Остальные возможные значения можно увидеть в таблице 3.
Таблица 3. Флаг текущего статуса сервиса.
Флаг | Значение | Описание |
SERVICE_STOPPED | 0x00000001 | Сервис не запущен |
SERVICE_START_PENDING | 0x00000002 | Сервис находится в процессе запуска |
SERVICE_STOP_PENDING | 0x00000003 | Сервис находится в процессе остановки |
SERVICE_RUNNING | 0x00000004 | Сервис запущен и работает |
SERVICE_CONTINUE_PENDING | 0x00000005 | Сервис находится в процессе повторного запуска |
SERVICE_PAUSE_PENDING | 0x00000006 | Сервис находится в процессе установки паузы |
SERVICE_PAUSED | 0x00000007 | Сервис приостановлен |
Член структуры dwControlsAccepted показывает какого рода уведомления готов получать сервис. Если в дальнейшем предполагается использование опции SCP-приложения "приостановить/продолжить", (pause/continue), то используется флаг SERVICE_ACCEPT_PAUSE_CONTINUE. Многие сервисы не поддерживают возможность приостановки сервиса, поэтому необходимо решить нужна ли сервису такая возможность. При желании дать возможность SCP-приложению останавливать сервис (stop), устанавливается флаг SERVICE_ACCEPT_STOP. Если же сервис должен принимать уведомления о прекращении работы операционной системы, устанавливается флаг SERVICE_ACCEPT_SHUTDOWN. Можно также использовать оператор OR для комбинирования флагов. Все они перечислены в таблице 4.
Таблица 4. Флаг управляющего кода.
Флаг управляющего кода | Значение флага | Описание |
SERVICE_ACCEPT_STOP | 0x00000001 | Сервис может быть остановлен. Флаг позволяет сервису получать уведомление SERVICE_CONTROL_STOP |
SERVICE_ACCEPT_PAUSE_CONTINUE | 0x00000002 | Сервис может быть приостановлен и запущен повторно. Флаг позволяет сервису получать уведомления SERVICE_CONTROL_PAUSE и SERVICE_CONTROL_CONTINUE |
SERVICE_ACCEPT_SHUTDOWN | 0x00000004 | Сервис получает уведомление о завершении работы ОС. Флаг позволяет сервису получать уведомление SERVICE_CONTROL_SHUTDOWN. Имейте в виду: такое уведомление посылается только самой операционной системой |
SERVICE_ACCEPT_PARAMCHANGE | 0x00000008 | Windows 2000/XP: Сервис может считывать свои параметры настроек из реестра без необходимости остановки и перезапуска. Флаг позволяет сервису получать уведомление SERVICE_CONTROL_PARAMCHANGE |
SERVICE_ACCEPT_NETBINDCHANGE | 0x00000010 | Windows 2000/XP: Сервис является сетевым компонентом, который может реагировать на изменения привязки к сетевым ресурсам без необходимости остановки и перезапуска. Флаг позволяет сервису получать уведомления SERVICE_CONTROL_NETBINDADD, SERVICE_CONTROL_NETBINDREMOVE, SERVICE_CONTROL_NETBINDENABLE и SERVICE_CONTROL_NETBINDDISABLE |
SERVICE_ACCEPT_HARDWAREPROFILECHANGE | 0x00000020 | Windows 2000/XP: Сервис получает уведомления об изменении профиля оборудования компьютера. Это позволяет системе отправлять сервису уведомление SERVICE_CONTROL_HARDWAREPROFILECHANGE. Сервис может получить это уведомление только, если был запущен функцией RegisterServiceCtrlHandlerEx. Функция ControlService не может послать такое уведомление. |
SERVICE_ACCEPT_POWEREVENT | 0x00000040 | Windows 2000/XP: Сервис получает уведомления о смене режима питания компьютера. Это позволяет системе посылать сервису уведомление SERVICE_CONTROL_POWEREVENT. Сервис может получить это уведомление только, если был запущен функцией RegisterServiceCtrlHandlerEx. Функция ControlService не может послать такое уведомление. |
SERVICE_ACCEPT_SESSIONCHANGE | 0x00000080 | Windows XP: Сервис получает уведомления об изменении статуса сессии. Это позволяет системе посылать сервису уведомление SERVICE_CONTROL_SESSIONCHANGE. |
Члены структуры dwWin32ExitCode и dwServiceSpecificExitCode позволяют сервису рапортовать об ошибках. Если возникает ошибка, код которой описан в файле WinError.h, код ошибки присваивается члену dwWin32ExitCode. В случае возникновения специфичной (не описанной в WinError.h) ошибки, члену dwWin32ExitCode присваивается ERROR_SERVICE_SPECIFIC_ERROR, а члену структуры dwServiceSpecificExitCode присваивается значение специфичной ошибки. Если сервис работает нормально и без сбоев, члену структуры dwWin32ExitCode необходимо присвоить значение NO_ERROR.
О процессе запуска сервиса можно узнать благодаря двум последним членам структуры - dwCheckPoint и dwWaitHint. Если устанавливается dwCurrentState в SERVICE_START_PENDING, то нужно установить значение члена dwCheckPoint в 0, а значению dwWaitHint присвоить количество миллисекунд, необходимых сервису для полной загрузки и старта. После полной инициализации сервиса, нужно реинициализировать структуру SERVICE_STATUS, установив dwCurrentState в SERVICE_RUNNING, а dwCheckPoint и dwWaitHint обнулить.
Наличие члена dwCheckPoint позволяет сервису сообщать о процессе инициализации. Каждый раз, при вызове SetServiceStatus, можно увеличивать на единицу значение dwCheckPoint. В результате значение dwCheckPoint покажет на какой стадии загрузки находится сервис в данный момент времени. Для реализации возможности получения сообщения о состоянии сервиса используется член структуры dwWaitHint. Нужно установить его в значение, указывающее количество миллисекунд, которое априори необходимо для завершения очередного шага инициализации.
По завершении инициализации, сервис вызывает функцию SetServiceStatus с параметром SERVICE_RUNNING - с этого момента сервис уже работает. Обычно работа сервисов состоит в выполнени цикла. Внутри этого цикла поток сервиса "спит" в ожидании запроса из сети, либо уведомления об остановке, паузе, завершении работы операционной системы и т.п. При получении запроса или уведомления, поток сервиса "просыпается", выполняет необходимые операции, затем снова входит в цикл и "засыпает" в ожидании нового запроса/уведомления.
В случае, если сервис получает уведомление об остановке или о завершении работы системы, его поток должен выйти из цикла и совершить очистку занимаемых сервисом ресурсов. На этом работа сервисного потока прекращается. По завершению работы потока функции ServiceMain, "просыпается" поток, находящийся внутри функции StartServiceCtrlDispatcher. Его работа заключается в том, чтобы уменьшить счетчик работающих сервисов, как было сказано ранее.
Если для выполнения полезной работы сервису не нужен постоянно существующий поток, после успешной инициализации можно осуществить выход из ServiceMain.
Точка входа по обработке команд (Handler)
SCM получает и сохраняет адрес этой callback-функции, когда ServiceMain вызывает функцию RegisterServiceCtrlHandler. SCP-приложение вызывает API-функцию, которая предписывает SCM каким образом контролировать сервис. Фирма Microsoft определила следующие значения флагов уведомления сервиса (таблица 5).
Таблица 5. Флаг и значение кода уведомления.
Флаг и значение кода уведомления | Описание |
SERVICE_CONTROL_CONTINUE 0x00000003 | Уведомляет приостановленную службу, что следует возобновить работу. |
SERVICE_CONTROL_INTERROGATE 0x00000004 | Уведомляет службу, что она должна сообщить сведения о своем текущем состоянии SCM. Обработчик должен просто вернуть NO_ERROR |
SERVICE_CONTROL_NETBINDADD 0x00000007 | Уведомляет сетевой сервис, что имеется новый компонент для компоновки. Служба соединиться с новым компонентом. |
SERVICE_CONTROL_NETBINDDISABLE 0x0000000A | Уведомляет сетевой сервис, что один из конпонентов сети был заблокирован. Служба должна проверить свои связи и удалить не нужную связь. |
SERVICE_CONTROL_NETBINDENABLE 0x00000009 | Уведомляет сетевой сервис о том, что заблокированный до этого момента компонент сети, стал доступен. Сервис должен проверить свои связи и совершить привязку к появившемуся компоненту сети. |
SERVICE_CONTROL_NETBINDREMOVE 0x00000008 | Уведомляет сетевой сервис об удалении связанного компонента сети. Сервис должен проверить все свои связи и разорвать связь с несуществующим компонентом. |
SERVICE_CONTROL_PARAMCHANGE 0x00000006 | Уведомляет сервис об изменении одного из параметров настройки. Сервис должен заново считать параметры из реестра. |
SERVICE_CONTROL_PAUSE 0x00000002 | Предписывает сервису сделать паузу. |
SERVICE_CONTROL_SHUTDOWN 0x00000005 | Сообщает сервису, что система выключается и сервис может очистить задачи. Если служба принимает этот контрольный код, она должена остановиться после ее очистки выполняемых задачь и внуть NO_ERROR. После того как SCM посылает данный контрольный код, он не будет отправить этому сервису другие управляющие коды. |
SERVICE_CONTROL_STOP 0x00000001 | Уведомляет службу, что она должна остановиться. Если служба принимает этот контрольный код, она должена остановиться после получения и вернуть NO_ERROR. Затем SCM посылает ей контрольный код, он не посылает другие управляющие коды. Windows XP/2000:Если служба возвращает NO_ERROR и продолжает работать, она продолжает получать управляющие коды. Такое поведение изменилось, начиная с Windows Server 2003 и Windows XP с SP2. |
В дополнение к указанным выше кодам уведомлений, программист может определить свои собственные, которые должны иметь значение от 128 до 255. Функция Handler полностью отвечает за обработку посланных сервису уведомлений. Как долго она будет обрабатывать то или иное уведомление, зависит от характера уведомления.
Когда функция Handler получает уведомление SERVICE_CONTROL_STOP, SERVICE_CONTROL_PAUSE или SERVICE_CONTROL_CONTINUE, она вызывает функцию SetServiceStatus для изменения статуса сервиса и получения информации о том, как много времени сервису понадобится для выполнения операции. На то время, пока сервис будет обрабатывать уведомление, необходимо установить значение члена dwCurrentState структуры SERVICE_ STATUS в SERVICE_STOP_PENDING, SERVICE_PAUSE_PENDING или SERVICE_START_PENDING соответственно.
На время остановки сервиса, необходимо также определиться сколько это займет времени. Это нужно по причине того, что сервис может быть занят какой-нибудь длительной операцией - ожидать ответа от сетевого компонента, базы данных или находиться в режиме копирования данных на диск и т.п. Отследить время, которое ему понадобится на завершение операции, можно воспользовавшись членами структуры SERVICE_ STATUS - dwCheckPoint и dwWaitHint.
По завершении выполнения остановки, паузы или запуска сервиса, нужно снова вызвать SetServiceStatus, но на этот раз установить значение члена dwCurrentState структуры SERVICE_ STATUS в SERVICE_STOPPED, SERVICE_PAUSED или SERVICE_RUNNING соответственно. После этого обязательно обнулите dwCheckPoint и dwWaitHint.
Когда Handler получает уведомление SERVICE_INTERROGATE, она должна просто подтвердить текущий статус сервиса, путем установки dwCurrentState в текущий статус сервиса, перед вызовом SetServiceStatus. Перед вызовом последней следует обнулить dwCheckPoint и dwWaitHint.
При завершении работы операционной системы функция Handler получает уведомление SERVICE_SHUTDOWN. Это уведомление не нуждается в подтверждении. Сервис в этом случае должен как можно быстрее выполнить определенный для него минимум операций для сохранения данных и очистки ресурсов. По умолчанию система выделяет сервисам всего 20 секунд для завершения работы. Если сервис не уложился в выделенное ему время, система вызывает TerminateProcess и насильственным образом "убивает" процесс сервиса. Изменить временной интервал, выделяемый системой можно в ключе реестра HKEY_LOCAL_MACHINE\SYSTEM\ CurrentControlSet\Control.
При получении уведомления, определенного пользователем (в интервале от 128 до 255), функция Handler должна вызвать, пользовательскую процедуру обработки данного уведомления. В этом случае нельзя вызывать SetServiceStatus, если только пользовательская процедура не влияет на вышеперечисленные состояния сервиса. Основной поток сервиса, получая уведомления, запускает функцию Handler. Но при этом поток функции ServiceMain должен соответствующим образом обработать это уведомление. Например, написанный Вами сервис может заниматься обработкой клиентских запросов, поступающих по именованному каналу. Поток сервиса "засыпает" в ожидании клиентского запроса. В это время поток функции Handler получает уведомление SERVICE_CONTROL_STOP. Как в этом случае правильно завершить работу сервиса? Не следует в этом случае просто вызывать TerminateThread непосредственно из функции Handler, так как функция TerminateThread не дает никакой возможности потоку выполнить очистку ресурсов. Стэк потока не уничтожается, поток не может освободить объекты ядра, которые он использовал до вызова функции, задействованные DLL не получают никакого уведомления о завершении работы потока и т.д.
Правильным способом остановить сервис в этом случае является следующий. Нужно каким-то образом "разбудить" поток, убедиться в том, что он готов в настоящий момент к остановке, последовательно очистить ресурсы и затем завершить работу потока и, соответственно, сервиса. Все это означает, что необходимо иметь своего рода связь между функцией Handler и функцией ServiceMain. Лучшим средством связи в этом случае могут быть порты завершения ввода/вывода (I/O completion ports). Но программист может использовать любой из механизмов, включая очередь асинхронных вызовов (asynchronous procedure call - APC), сокеты (sockets) или оконные сообщения (windows messages).
Кроме этого, вызывает сомнение принцип, согласно которому, статус сервиса должен меняться только путем вызова SetServiceStatus. До сих пор не утихают споры по поводу того, где размещать вызовы SetServiceStatus. Практикой подтверждается следующая последовательность действий: сначала из функции Handler сервиса вызывается функция SetServiceStatus для установки флага SERVICE_STOP_PENDING и передаётся уведомление основному потоку сервиса; затем, перед самым завершением работы потока, снова вызывается функция SetServiceStatus и устанавливается флаг SERVICE_STOPPED. Это позволяет сервису, во-первых, подтвердить код уведомления и занятся его обработкой, когда у него появляется свободное время, а, во-вторых, все функции Handler, имеющиеся в исполняемом файле сервиса вызываются только из основного потока сервиса. Если всей обработкой будет заниматься основной поток, то это освобождает потоки сервисов от периодической обработки уведомлений. Однако, здесь возможны и неувязки.
Рассмотрим пример. Пусть, сервис получает уведомление SERVICE_CONTROL_PAUSE. В этом случае функция Handler устанавливает статус в SERVICE_PAUSE_PENDING и передает уведомление функции ServiceMain для обработки. Поток ServiceMain начинает обрабатывать уведомление, как вдруг поток функции Handler прерывает работу потока ServiceMain, т.к. получает уведомление SERVICE_CONTROL_STOP. Естественно Handler устанавливает статус SERVICE_STOP_PENDING и ставит уведомление в очередь для обработки функцией ServiceMain. Когда поток ServiceMain снова получает процессорное время, он завершает обработку уведомления SERVICE_CONTROL_PAUSE и возвращает код SERVICE_PAUSED. Затем, он видит, что в очереди для обработки находится SERVICE_CONTROL_STOP, останавливает сервис и рапортует кодом SERVICE_STOPPED. В результате всех этих операций, SCM получает статусы сервиса в следующем порядке:
SERVICE_PAUSE_PENDING
SERVICE_STOP_PENDING
SERVICE_PAUSED
SERVICE_STOPPED
Сервисы, которые работают таким образом, тем не менее работают по причине, малой вероятности остановки сервиса пока он находится в режиме «пауза» - но гарантий никаких нет. Можно предположить, что SCM сам предотвращает такого рода накладки. Но эксперименты Дж. Рихтера [2] показали, что это не так. Фактом является то, что SCM не делает ничего для упорядочивания уведомлений. Вот что имеется в виду: если сервис переведен в режим «пауза», то послать ему уведомление SERVICE_CONTROL_PAUSE. Конечно, с помощью SCP-апплета нельзя, т.к. он отслеживая состояния сервиса, делает кнопку "пауза" неактивной. Но если попробовать использовать утилиту наподобие SC.exe, то ничто не помешает осуществить такую операцию. Можно было-бы ожидать, что SCM пошлет утилите какой-нибудь код ошибки, но вместо этого он просто-напросто вызывает сервисную функцию Handler, передавая ей уведомление SERVICE_CONTROL_PAUSE.
Для устранения такой проблемы, когда сервис получаете уведомление на остановку (stop), приостановку (pause) или продолжение (continue) работы, необходимо выполнить проверку не находится ли уже сервис в вызываемом состоянии. Если так, то не следует вызывать SetServiceStatus и выполнять код изменения статуса – нужно просто сделайть в этом месте выход из обработки.
Рассмотрим еще одну ошибку, которую часто делают при разработке сервисов. Когда функция Handler получает уведомление SERVICE_CONTROL_PAUSE, она вызывает SetServiceStatus для установки SERVICE_PAUSE_PENDING. Затем Handler вызывает API-функцию SuspendThread для приостановки потока сервиса, а затем снова вызывает SetServiceStatus для установки SERVICE_PAUSED. Это позволяет избежать накладок при поступлении однотипных уведомлений, поскольку все выполняется в одном потоке. Но контролирует ли на паузу приостанавливаемого потока сервиса сам сервис? Да, но что тогда означает "приостановить сервис"? В этом случае ответ зависит от сервиса.
Если разрабатываемый сервис, обрабатывает клиентские запросы, приходящие из сети, то пауза - это прекращение получения каких-либо запросов. А что тогда делать с запросом, который обрабатывается в данный момент? Можно ли прекратить обработку, чтобы не "завис" клиент? Если в этот момент функция Handler вызовет API-функцию SuspendThread, то сервисный поток окажется посредине непонятно чего; может быть он в процессе вызова malloc, пытаясь зарезервировать некоторое количество памяти. Если в это время другой поток, выполняющийся в этом же процессе также вызовет malloc, то и он также "замерзнет".
Можно ли остановить поток, находящийся в режиме паузы? Фирма Microsoft написала SCM-апплет так, что можно нажать кнопку «Стоп» (Stop) для сервиса, который находится в состоянии «пауза». Лучший способ справиться со всеми этими вопросами - это постоянно иметь под рукой свободный идентификатор потока. И это должен быть поток процесса, а не поток функции Handler. Когда Handler получает код уведомления, то должн использоваться какой-нибудь механизм коммуникации для постановки кода уведомления в очередь обработки потоком сервиса, и затем просто возвратить управление. Функция Handler никогда не должна сама вызывать SetServiceStatus. В этом случае сервис постоянно будет под контролем. Не будет проблем с однотипными уведомлениями, сервис сам будет принимать решение о том, что для него означает переход в режим паузы, он позволит остановить себя, даже если стоит на паузе, только сервис будет решать какой механизм коммуникации для него является наилучшим, а код функции Handler должен всего лишь соответствовать этому механизму.
Единственный недостаток этого подхода в том, что сервис должен быстро обработать полученное уведомление. Если поток сервиса занят, например, обработкой клиентского запроса, код уведомления будет ждать в очереди и функция SetServiceStatus не будет вызвана в течении продолжительного отрезка времени. Если SetServiceStatus не будет вызван за отведенный промежуток времени, SCP-приложение решит, что сервис не отвечает и пошлет пользователю сообщение об ошибке. Однако, в сервисе ошибок нет и он пордолжает работать. Сервис обработает уведомление, когда до него дойдет очередь. Но все равно, в реальной жизни, SCP-приложение отсылает пользователю сообщение об ошибке. Такие ошибки обнаружить не возможно. Они выявляются в процессе эксплуатации сервиса, так как разработчик сервиса - это не является разработчиком SCP-приложения. Самым простым решением данной проблемы является написание кода сервиса, обеспечивающего эффективную и быструю его работу. Кроме того сервис должен иметь поток, находящийся в постоянной готовности для получения сообщений.
Следует отметить, что вызов функции SetServiceStatus из функции Handler не решает этой проблемы. Если внутри Handler будет устанавлено состояние сервиса в SERVICE_START_PENDING и будет задан временной интервал dwWaitHint 5000 миллисекунд до момента вызова SetServiceStatus, то нет никакой гарантии, что поток сервиса в течении этого времени "проснется" и обработает уведомление. А если сервис не обработает уведомление в течение указанного времени, SCP-приложение решит, что сервис не отвечает.
Дата добавления: 2015-08-13; просмотров: 88 | Нарушение авторских прав
<== предыдущая страница | | | следующая страница ==> |
Точка входа main | | | Пример функций сервиса. |