Створення сервісу, Додатки, DLL, COM, ActiveX, C / C + +, статті

Додаток, що управляє сервісом Код, описаний у цій статті, працює
тільки в Windows NT / 2000 / XP, оскільки Windows 98 не підтримує роботу з
сервісами.

Створення сервісу


Як правило сервіс є консольний додаток, тому
програма повинна містити функцію main ().

Спочатку оголосимо глобальні змінні:

DWORD dwErrCode; / / код помилки
SERVICE_STATUS ss; / / поточне сосотояние сервісу
SERVICE_STATUS_HANDLE ssHandle; / / дескриптор статусу сервісу

LPTSTR SomeServiceName = “SomeService”; / / ім’я сервісу


Функція main ()

void main(int argc, char* argv[]) {
/ / Таблиця точок входу
SERVICE_TABLE_ENTRY DispatcherTable[] =
{{SomeServiceName, / / ​​ім’я сервісу
(LPSERVICE_MAIN_FUNCTION) ServiceMain}, / / ​​головна функція сервісу
{ NULL, NULL }
};

Тут ми створюємо таблицю точок входу сервісу.

Структура типу SERVICE_TABLE_ENTRY дозволяє задати функцію ServiceMain () для
сервісу, що носить вказане в структурі ім’я.

 / / Запуск диспетчера
if(!StartServiceCtrlDispatcher(DispatcherTable)) {
printf("StartServiceCtrlDispatcher: Error %ld
",
GetLastError());
getch();
return;
}
}

Функція StartServiceCtrlDispatcher () пов’язує головний потік сервісу з
менеджером сервісів (service control manager, SCM).

Коли SCM запускає сервіс, він очікує виконання сервісом функції
StartServiceCtrlDispatcher (). Головний потік сервісу має викликати цю функцію
як можна швидше. Якщо функція виконується успішно, вона пов’язує викликає її
потік з SCM і не завершується, поки не буде зупинений сервіс. SCM використовує
це з’єднання, щоб посилати сервісу керуючі запити.

Якщо сервіс запускається як окремий процес (як, наприклад, в нашому
випадку), він повинен викликати функцію StartServiceCtrlDispatcher () негайно.
Тому всю ініціалізацію слід робити пізніше, у функції ServiceMain (). Якщо в
рамках одного процесу запускається кілька сервісів, головний потік повинен
викликати функцію StartServiceCtrlDispatcher () не пізніше, ніж через 30 секунд.

Функція ServiceMain ()


Функція ServiceMain () визначається процесом як точка входу для
даного сервісу. Ця функція може носити будь-яке ім’я, задане додатком.

void WINAPI ServiceMain(DWORD argc, LPSTR* argv) {
/ / Реєстрація керуючої функції сервісу
ssHandle = RegisterServiceCtrlHandler(SomeServiceName, ServiceControl);
if(!ssHandle) {
printf("Error registering ServiceControl
");
getch();
return;
}

Аргументи функції ServiceMain () (аналогічні аргументам функції main ()):
dwArgc – кількість аргументів, lpszArgv – покажчик на масив, який містить
покажчики на рядкові аргументи функції.

Функція RegisterServiceCtrlHandler () реєструє функцію, яка буде
обробляти керуючі запити до сервісу (такі, наприклад, як зупинка
сервісу). У разі успішного завершення функція повертає дескриптор статусу
сервісу (service status handle). При невдалому завершенні функція повертає
нульове значення.

 / / Заповнюємо структуру, що визначає стан сервісу:
/ / Сервіс виконується як окремий процес
ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
/ / Встановлюємо стан очікування запуску сервісу
SetSomeServiceStatus(SERVICE_START_PENDING, NO_ERROR, 4000);

/ / Ініціалізація для SomeService
InitSomeServiceData(argc, argv);

/ / Встановлюємо сосотояние працюючого сервісу
SetSomeServiceStatus(SERVICE_RUNNING, NO_ERROR, 0);

/ / Основний код програми
//…

return;
}


Поле dwServiceType структури ss встановлюємо в SERVICE_WIN32_OWN_PROCESS.
Це означає, що сервіс буде виконуватися як окремий процес, тобто буде
мати власний адресний простір.

Потім встановлюємо стан очікування запуску сервісу c допомогою створеної
нами допоміжної функції SetSomeServiceStatus (). Ця функція змінює
вміст структури ss, яке використовує SCM для отримання інформації про
сервісі.

Далі можна виконати всю необхідну ініціалізацію для сервісу (обумовлена
користувачем функція InitSomeServiceData ()).

Коли ініціалізація виконана, викликаємо функцію SetSomeServiceStatus () з
параметром SERVICE_RUNNING, щоб встановити стан працюючого сервісу.

Після зміни стану сервісу виконується основний код програми.

Функція ServiceControl ()


Ця функція є керуючою функцією сервісу і може носити будь-яке ім’я,
певне додатком.

void WINAPI ServiceControl(DWORD dwControlCode) {
/ / Аналізуємо код команди і виконуємо її
switch(dwControlCode) {
/ / Команда зупинки сервісу
case SERVICE_CONTROL_STOP: {
/ / Встановлюємо стан очікування зупинки
SetSomeServiceStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);

/ / Зупинка сервісу
StopSomeService();

/ / Встановлюємо сосотояние зупинки сервісу
SetSomeServiceStatus(SERVICE_STOPPED, NO_ERROR, 0);
break;
}


За допомогою функції SetSomeServiceStatus () ми встановлюємо стан очікування
зупинки сервісу, після чого можна викликати визначається користувачем функцію
StopSomeService (), яка виконає всі необхідні дії перед зупинкою
сервісу. Вміст цієї функції залежить від конкретного сервісу.

Далі за допомогою функції SetSomeServiceStatus () повідомляємо SCM, що сервіс
зупинений.

 / / Визначення поточного стану сервісу
case SERVICE_CONTROL_INTERROGATE: {
SetSomeServiceStatus(ss.dwCurrentState, NO_ERROR, 0);
break;
}
default: {
SetSomeServiceStatus(ss.dwCurrentState, NO_ERROR, 0);
break;
}
}
}

Коли сервіс отримує команду SERVICE_CONTROL_INTERROGATE, це означає, що
він повинен негайно оновити інформацію про статус сервісу, використовувану SCM.

Функція SetSomeServiceStatus ()


Ця функція змінює вміст структури ss, яке використовує SCM для
отримання інформації про статус сервісу.

void SetSomeServiceStatus(DWORD dwCurrentState,
DWORD dwWin32ExitCode,
DWORD dwWaitHint)
{
/ / Лічильник кроків тривалих операцій
static DWORD dwCheckPoint = 1;

/ / Якщо сервіс не знаходиться в процесі запуску, його можна зупинити
if(dwCurrentState == SERVICE_START_PENDING)
ss.dwControlsAccepted = 0;
else
ss.dwControlsAccepted = SERVICE_ACCEPT_STOP;

ss.dwCurrentState = dwCurrentState;
ss.dwWin32ExitCode = dwWin32ExitCode;
/ / Час очікування запуску сервісу
ss.dwWaitHint = dwWaitHint;

/ / Якщо сервіс не працює і не зупинений, збільшуємо значення лічильника
/ / Кроків тривалих операцій
if(dwCurrentState == SERVICE_RUNNING || dwCurrentState == SERVICE_STOPPED)
ss.dwCheckPoint = 0;
else
ss.dwCheckPoint = dwCheckPoint++;

/ / Оновити інформацію про сервіс
SetServiceStatus(ssHandle, &ss);
}


Значення поля dwCheckPoint слід періодично змінювати під час тривалого
запуску або зупинки. Наприклад, якщо при запуску сервісу ініціалізація
виконується в кілька етапів, то потрібно збільшувати значення dwCheckPoint після
кожного етапу. При цьому програма, яка запросила послуги сервісу, може
дізнатися, який обсяг роботи вже виконаний сервісом. Поле dwCheckPoint повинно бути
дорівнює нулю, коли сервіс не знаходиться в стані запуску, зупинки або
виконання тривалої операції.

Поле dwControlsAccepted визначає керуючий код, який може приймати і
обробляти даний сервіс. За замовчуванням всі сервіси можуть приймати команду
SERVICE_CONTROL_INTERROGATE. Якщо це поле має значення SERVICE_ACCEPT_STOP,
то сервіс може бути зупинений за допомогою команди SERVICE_CONTROL_STOP.

Опції InitSomeServiceData () і StopSomeService ()

BOOL InitSomeServiceData() {
//…
return TRUE;
}

BOOL StopSomeService() {
//…
return TRUE;
}


За допомогою цих функцій проводиться ініціалізація, необхідна для даного
сервісу, а також дії, які необхідно виконати перед зупинкою
сервісу. Вміст цих функцій залежить від конкретного сервісу.

Додаток, що управляє сервісом


Додаток SomeSrvcApp може інсталювати сервіс в систему, запускати і
зупиняти його, отримувати інформацію про сервіс і видаляти сервіс з системи.

Тут не будуть описуватися функції WinMain () і InitApp (), оскільки вони не
володіють ніякими особливостями, вартими уваги. Функція WinMain ()
створює головне вікно з шістьма кнопками: “Install Service”, “Start Service”, “Get
Service Configuration “,” Stop Service “,” Remove Service “і” Exit “.

Функція WndProc ()


У функції WndProc () використовуються макроси для обробки повідомлень WM_COMMAND і
WM_DESTROY:

LRESULT WINAPI
WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg) {
HANDLE_MSG(hWnd, WM_COMMAND, WndProcOnCommand);
HANDLE_MSG(hWnd, WM_DESTROY, WndProcOnDestroy);
default:
return DefWindowProc(hWnd, msg, wParam, lParam);
}
}

Функція WndProcOnCommand ()


Функція WndProcOnCommand () викликається функцією WndProc (), якщо вікно отримує
повідомлення WM_COMMAND. Функція WndProcOnCommand () містить код, що виконується при
натисканні на кнопки головного вікна програми.

Наведемо повністю код цієї функції:

void
WndProcOnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify) {
SC_HANDLE hService; / / дескриптор сервісу
SC_HANDLE hSCManager; / / дескриптор Service Control Manager
LPQUERY_SERVICE_CONFIG lpBufConfig;
DWORD dwBytesNeeded;
char szBufConfig[4096];

switch(id) {
/ / Установка сервісу в систему
case IDB_INSTALL: {
/ / Відкриваємо SCM
hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
if(!hSCManager) {
MessageBox(NULL, "Can”t open Service Control Manager", "Error",
MB_OK | MB_ICONERROR);
break;
}
/ / Створюємо сервіс SomeService
hService = CreateService(hSCManager, SomeServiceName,
SomeServiceName, SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
"c:ServicesSomeSrvc.exe",
NULL, NULL, NULL, NULL, NULL);
if(!hService) {
MessageBox(NULL, "Can”t create service", "Error",
MB_OK | MB_ICONERROR);
CloseServiceHandle(hSCManager);
break;
}
/ / Закриваємо дескриптор сервісу
CloseServiceHandle(hService);
/ / Закриваємо дескриптор SCM
CloseServiceHandle(hSCManager);
break;
}


Тут ми відриваємо базу даних Service Control Manager (SCM) за допомогою функції
OpenSCManager () з рівнем доступу SC_MANAGER_CREATE_SERVICE, який дозволяє
створювати сервіси.

Для створення сервісу використовується функція CreateService (), яка повертає
дескриптор створеного сервісу або NULL в разі невдалого виконання. Одним з
параметрів функції є шлях до сервісу, який повинен відповідати
розташуванню сервісу у Вашому комп’ютері. Прослідкуйте, щоб шлях був зазначений
правильно, інакше створити сервіс не вийде.

В якості типу сервісу ми вказали SERVICE_WIN32_OWN_PROCESS. Це означає,
що сервіс буде виконуватися як окремий процес. При успішному завершенні
функція CreateService () додає сервіс в базу даних SCM. Ви можете в цьому
переконатися, відкривши системну панель управління сервісами (Панель управління ->
Адміністрування -> Служби).

 / / Запуск сервісу
case IDB_START: {
hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if(!hSCManager) {
MessageBox(NULL, "Can”t open Service Control Manager",
"Error", MB_OK | MB_ICONERROR);
break;
}
hService = OpenService(hSCManager, SomeServiceName,
SERVICE_START);
if(!hService) {
GetSomeSvcError();
break;
}
if(!StartService(hService, 0, NULL))
MessageBox(NULL, "Can”t start service", "Error",
MB_OK | MB_ICONERROR);
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
break;
}

Для відкриття сервісу ми використовуємо функцію OpenService (). Третім параметром
цієї функції є тип доступу до сервісу. Вказуючи тип доступу SERVICE_START,
ми отримуємо можливість запускати сервіс.

Запуск сервісу здійснюється за допомогою функції StartService (), яка
повертає нульове значення, якщо запустити сервіс не вдалося.

 / / Отримати інформацію про сервіс
case IDB_CONFIG: {
hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if(!hSCManager) {
MessageBox(NULL, "Can”t open Service Control Manager",
"Error", MB_OK | MB_ICONERROR);
break;
}
hService = OpenService(hSCManager, SomeServiceName,
SERVICE_QUERY_CONFIG);
if(!hService) {
GetSomeSvcError();
break;
}
lpBufConfig = (LPQUERY_SERVICE_CONFIG)malloc(4096);
if(lpBufConfig != NULL) {
/ / Зберігаємо конфігурацію в буфері
QueryServiceConfig(hService, lpBufConfig, 4096,
&dwBytesNeeded);
/ / Відображаємо поля конфігурації
wsprintf(szBufConfig,
"Binary path: %s
Start Name: %s
Display Name: %s",
lpBufConfig->lpBinaryPathName,
lpBufConfig->lpServiceStartName,
lpBufConfig->lpDisplayName);
MessageBox(NULL, szBufConfig, szWindowTitle,
MB_OK | MB_ICONINFORMATION);
free(lpBufConfig);
}
else {
MessageBox(NULL, "Can”t create buffer.
Not enough memory.",
"Error", MB_OK | MB_ICONERROR);
}
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
break;
}

На цей раз, відкриваючи сервіс, ми вказуємо тип доступу SERVICE_QUERY_CONFIG,
щоб отримати інформацію про конфігурацію сервісу. Цю інформацію ми поміщаємо в
структуру типу QUERY_SERVICE_CONFIG (через вказівник на цю структуру
lpBufConfig, використовуючи динамічно виділяється пам’ять) за допомогою функції
QueryServiceConfig (). Функція QueryServiceConfig () поміщає в структуру типу
QUERY_SERVICE_CONFIG інформацію про сервіс, яка знаходиться в реєстрі. Ця
інформація була поміщена до реєстру функцією CreateService ().

Далі ми поміщаємо вміст деяких полів структури до буфера szBufConfig і
виводимо його вміст за допомогою функції MessageBox ().

 / / Зупинка сервісу
case IDB_STOP: {
hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if(!hSCManager) {
MessageBox(NULL, "Can”t open Service Control Manager",
"Error", MB_OK | MB_ICONERROR);
break;
}
hService = OpenService(hSCManager, SomeServiceName,
SERVICE_STOP);
if(!hService) {
GetSomeSvcError();
break;
}
/ / Зупиняємо сервіс
if(!ControlService(hService, SERVICE_CONTROL_STOP, &ss))
MessageBox(NULL, "Can”t stop service", "Error",
MB_OK | MB_ICONERROR);
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
break;
}

Тут ми відкриваємо сервіс, використовуючи рівень доступу SERVICE_STOP.

Для зупинки сервісу використовується функція ControlService (). Ця функція
посилає сервісу керуючий код, який вона отримує в якості другого
параметра. У нашому випадку керуючий код дорівнює константі SERVICE_CONTROL_STOP.
Третім параметром функції є адреса структури типу SERVICE_STATUS, в
якій міститься інформація про поточний статус сервісу. Вміст цієї
структури використовує SCM. Якщо сервіс зупинити не вдалося функція
ControlService () повертає нульове значення.

 / / Видалення сервісу з системи
case IDB_REMOVE: {
hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if(!hSCManager) {
MessageBox(NULL, "Can”t open Service Control Manager",
"Error", MB_OK | MB_ICONERROR);
break;
}
hService = OpenService(hSCManager, SomeServiceName,
SERVICE_STOP | DELETE);
if(!hService) {
GetSomeSvcError();
break;
}
if(ss.dwCurrentState != SERVICE_STOPPED)
ControlService(hService, SERVICE_CONTROL_STOP, &ss);
/ / Видаляємо сервіс з системи
DeleteService(hService);
CloseServiceHandle(hService);
CloseServiceHandle(hSCManager);
break;
}

У цьому випадку, відкриваючи сервіс, ми використовуємо комбінацію прапорів SERVICE_STOP
і DELETE, оскільки якщо сервіс в даний момент працює, то перш ніж його
видаляти з системи, його необхідно зупинити. Для видалення сервісу з системи
використовується функція DeleteService ().

 / / Завершуємо роботу програми
case IDB_EXIT: {
PostQuitMessage(0);
return;
}
default: break;
}
return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify, DefWindowProc);
}

Опції WndProcOnDestroy () і GetSomeSvcError ()


Наведемо код функції WndProcOnDestroy () і функції GetSomeSvcError (), яка
використовується для виведення повідомлень про помилки:

void
WndProcOnDestroy(HWND hWnd) {
PostQuitMessage(0);
}

void GetSomeSvcError() {
DWORD dwError = GetLastError();
char errMess[100];
switch(dwError) {
case ERROR_ACCESS_DENIED: {
sprintf(errMess, "The specified service control manager"
"database handle does not have access to"
"the service.");
break;
}
case ERROR_INVALID_HANDLE: {
sprintf(errMess, "The specified handle is invalid.");
break;
}
case ERROR_INVALID_NAME: {
sprintf(errMess, "The specified service name is invalid.");
break;
}
case ERROR_SERVICE_DOES_NOT_EXIST: {
sprintf(errMess, "The specified service does not exist.");
break;
}
default: {
sprintf(errMess, "Can”t open service.
Error number not found.");
}
}
MessageBox(NULL, errMess, "Error", MB_OK | MB_ICONERROR);
return;
}


У функції GetSomeSvcError () аналізується код помилки, отриманий за допомогою
функції Win API GetLastError (). Повний список кодів помилок та їх значень можна
подивитися у файлі WinError.h або в документації по Win API.

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


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

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

Ваш отзыв

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

*

*