Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: 'Złośliwiec'
Biblioteki C++

Usługi

[lekcja]

Wstęp

Usługa to typ aplikacji, która pracuje w tle i jest podobna do aplikacji typu demon w systemie Unix. Aplikacje usług udostępniają użytkownikom typowe funkcje, takie jak aplikacje klient/serwer, serwery sieci Web, serwery baz danych i inne aplikacje oparte na serwerach, zarówno lokalnie, jak i w sieci.

Tyle teoria. Otwórzmy teraz Panel Sterowania\Narzędzia Administracyjne\Usługi (albo Start\Uruchom\services.msc ;) Naszym oczom powinien ukazać się podobny obraz:

Applet Usługi (Windows XP)
Applet Usługi (Windows XP)


Widzimy tutaj sporą listę usług, niektóre zainstalowane razem z systemem, inne wraz instalowanymi później programami.

Znajdują się na tej liście takie usługi jak Harmonogram zadań czy Aktualizacje automatyczne, z którymi z pewnością się spotkaliście. Działają one w tle i wykonują zadania, do których zostały stworzone.

Jak to działa?

Usługa jest prawie zwykłą aplikacją. Prawie, bo musi współpracować z Service Control Manager (SCM, znanym jako services.exe), który uruchamiany jest razem z systemem i odpowiedzialny jest za uruchamianie i kontrolę zainstalowanych usług.

SCM po uruchomieniu aplikacji czeka, aż ta przedstawi mu się jako usługa, do której została przypisana. Proces usługi powinien utworzyć dodatkowy wątek do pracy ponieważ główny wątek jest wykorzystywany do przetwarzania komunikatów od SCM.

Inne aplikacje mogą komunikować się z usługami dzięki szeroko rozumianym mechanizmom IPC (Interprocess Communications).

Aplikacja usługi

Usługi są zazwyczaj pisane jako aplikacje konsolowe z punktem wejścia w postaci funkcji main, której zadaniem jest wywołanie funkcji StartServiceCtrlDispatcher.
C/C++
int main( int argc, char * argv[ ] )
{
   
SERVICE_TABLE_ENTRY svc[ ] =
   
{
        {
"TestService", ServiceMain },
       
{ NULL, NULL }
    }
;
   
StartServiceCtrlDispatcher( svc );
   
return 0;
}
StartServiceCtrlDispatcher przyjmuje tablicę struktur SERVICE_TABLE_ENTRY. Struktura ta ma dwa pola, z których pierwsze jest wskaźnikiem na nazwę usługi, a drugie właściwym punktem wejścia. Ostatni element tablicy zawiera dwa puste wskaźniki.

Funkcja StartServiceCtrlDispatcher wraca dopiero po zatrzymaniu wszystkich usług świadczonych przez proces.

Punkt wejścia

Punktem wejścia usługi będzie w naszym przypadku funkcja ServiceMain (nazwa dowolna).
C/C++
VOID CALLBACK ServiceMain( DWORD dwArgc, LPTSTR * lpszArgv )
Widzimy tu pewne podobieństwo do funkcji main, mianowicie przyjmuje ona dwa argumenty, z czego pierwszy jest liczbą argumentów uruchomieniowych usługi a drugi ich tablicą.
Zadaniem tej funkcji jest zarejestrowanie funkcji obsługi komunikatów sterujących, ustawienie początkowego statusu usługi i jej zainicjowanie.

Do zarejestrowania funkcji przyjmującej komunikaty posłuży nam funkcja RegisterServiceCtrlHandlerEx.
C/C++
SERVICE_STATUS_HANDLE hStatus = RegisterServiceCtrlHandlerEx( "TestService", SvcHandlerEx, 0 );
Pierwszym argumentem tej funkcji jest nazwa usługi, drugi to nasza funkcja obsługi komunikatów, trzeci będzie przekazywany do naszej funkcji wraz z komunikatem.
Funkcja zwróci nam uchwyt statusu, najlepiej zapisać go w zmiennej globalnej.

Ustawianie statusu

Po zarejestrowaniu funkcji obsługi komunikatów należy pierwszy raz ustawić status usługi.
Do tego celu wykorzystamy funkcję SetServiceStatus. Przekazujemy jej dwa argumenty: uchwyt zwrócony przez RegisterServiceCtrlHandlerEx oraz wskaźnik na strukturę SERVICE_STATUS.

Struktura SERVICE_STATUS posiada następujące pola:


* dwServiceType Typ usługi. Mamy do wyboru kilka wartości, opiszę tutaj dwie:

Wartość Opis
SERVICE_WIN32_OWN_PROCESS Usługa posiada własny proces przeznaczony tylko dla niej.
SERVICE_WIN32_SHARE_PROCESS Usługa współdzieli proces z innymi usługami.

Oprócz tego można tu dodać flagę SERVICE_INTERACTIVE_PROCESS, która pozwoli usłudze wyświetlać własne okienka na pulpicie aktualnie zalogowanego użytkownika. Aby móc ustawić tę flagę usługa musi być uruchomiona na koncie LocalSystem.


* dwCurrentState Obecny stan usługi. Może przyjąć następujące wartości:

Wartość Opis
SERVICE_RUNNING Usługa działa.
SERVICE_STOPPED Usługa została zatrzymana. Jeśli wszystkie usługi danego procesu się zatrzymają to SCM go zakończy.
SERVICE_PAUSED Usługa została wstrzymana.
SERVICE_START_PENDING Usługa jest w trakcie uruchamiania.
SERVICE_STOP_PENDING Usługa jest w trakcie zatrzymywania.
SERVICE_PAUSE_PENDING Usługa jest w trakcie wstrzymywania pracy.
SERVICE_CONTINUE_PENDING Usługa jest w trakcie wznawiania pracy.

* dwControlsAccepted Akceptowane kody kontroli. Najważniejsze flagi:

Flaga Komunikat Opis
SERVICE_ACCEPT_STOP SERVICE_CONTROL_STOP Usługę można zatrzymać ręcznie.
SERVICE_ACCEPT_PAUSE_CONTINUE SERVICE_CONTROL_PAUSE SERVICE_CONTROL_CONTINUE Usługa może zostać wstrzymana oraz wznowiona bez potrzeby całkowitego zatrzymywania i ponownego uruchamiania.
SERVICE_ACCEPT_SHUTDOWN SERVICE_CONTROL_SHUTDOWN Usługa jest informowana o zamykaniu systemu.
SERVICE_ACCEPT_PARAMCHANGE SERVICE_CONTROL_PARAMCHANGE Można zmienić parametry usługi bez jej zatrzymywania.

* dwWin32ExitCode Kod błędu. Podczas normalnego działania i zatrzymywania należy ustawić na NO_ERROR. Jeśli jest równy ERROR_SERVICE_SPECIFIC_ERROR to brane jest pod uwagę pole dwServiceSpecificExitCode.


* dwServiceSpecificExitCode Kod błędu specyficzny dla usługi. Ignorowany jeśli dwWin32ExitCode jest różne od ERROR_SERVICE_SPECIFIC_ERROR.


* dwCheckPoint Punkt kontrolny. Musi być zwiększany podczas uruchamiania, zatrzymywania, wstrzymywania oraz wznawiania. Podczas normalnej pracy i stanu wstrzymania musi być równy 0.


* dwWaitHint Czas oczekiwania na uruchomienie, zatrzymanie, wstrzymanie lub wznowienie usługi. Przed jego upływem należy ponownie ustawić status ze zwiększonym dwCheckPoint lub zmienionym dwCurrentState. Jeśli się tego nie zrobi SCM uznaje, że nastąpił błąd i wyłącza usługę.
C/C++
SERVICE_STATUS status;
status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
status.dwCurrentState = SERVICE_RUNNING;
status.dwControlsAccepted = SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_STOP;
status.dwWin32ExitCode = NO_ERROR;
status.dwServiceSpecificExitCode = 0;
status.dwCheckPoint = 0;
status.dwWaitHint = 0;
SetServiceStatus( hStatus, & status );

Obsługa komunikatów

Do obsługi komunikatów napiszemy sobie funkcję SvcHandlerEx (nazwa oczywiście dowolna).
C/C++
DWORD CALLBACK SvcHandlerEx( DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext )
Argumenty:

* dwControl Kod kontroli. Najważniejsze kody:

Kod Opis
SERVICE_CONTROL_INTERROGATE Usługa powinna zgłosić swój obecny stan do SCM.
SERVICE_CONTROL_STOP Usługa musi się zatrzymać.
SERVICE_CONTROL_PAUSE Usługa powinna wstrzymać pracę.
SERVICE_CONTROL_CONTINUE Usługa powinna wznowić pracę.
SERVICE_CONTROL_PARAMCHANGE Zmieniono parametry usługi.
SERVICE_CONTROL_SHUTDOWN System jest zamykany, usługa powinna się zatrzymać.
Od 128 do 255 Kody zdefiniowane przez użytkownika.

* dwEventType Typ zdarzenia.
* lpEventData Dane zdarzenia.
* lpContext Ostatni argument RegisterServiceCtrlHandlerEx.

Funkcja powinna ustawić nowy status i zwrócić NO_ERROR.

Wysyłanie komunikatów

Do wysyłania komunikatów do usług służy funkcja ControlService, jednak zanim zaczniemy wysyłać komunikaty musimy otworzyć usługę, a żeby otworzyć usługę musimy otworzyć manager usług.
C/C++
SC_HANDLE hSCM = OpenSCManager( NULL, NULL, SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE ); //1
SC_HANDLE hSvc = OpenService( hSCM, "TestService", SERVICE_USER_DEFINED_CONTROL ); //2
SERVICE_STATUS stat;
ControlService( hSvc, 128, & stat ); //3
CloseServiceHandle( hSvc ); //4
CloseServiceHandle( hSCM );
# Łączymy się z SCM. Prawo SC_MANAGER_CONNECT jest wymagane przez sama sunkcję OpenSCManager, natomiast SC_MANAGER_ENUMERATE_SERVICE przez OpenService.
# Otwieramy naszą usługę z prawem do wysyłania zdefiniowanych przez nas komunikatów (od 128 do 255).
# Wysyłamy do usługi komunikat nr 128, jest to komunikat, który sami definiujemy i jego działanie jest zależne od tego jak usługa go interpretuje. Oprócz wysłania komunikatu, funkcja ControlService pobiera ostatnio ustawiony status. Jeśli chcielibyśmy pobrać sam status moglibyśmy wysłać komunikat SERVICE_CONTROL_INTERROGATE (potrzebne jest do tego prawo SERVICE_INTERROGATE) lub użyć funckji QueryServiceStatus[Ex] (potrzebne prawo to SERVICE_QUERY_STATUS).
# Zamykamy uchwyty.

Przykład

C/C++
#include <windows.h>

SERVICE_STATUS_HANDLE g_hStatus;
SERVICE_STATUS g_Status;

DWORD CALLBACK SvcHandlerEx( DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext )
{
   
switch( dwControl )
   
{
   
case SERVICE_CONTROL_PAUSE:
       
g_Status.dwCurrentState = SERVICE_PAUSED;
       
break;
   
case SERVICE_CONTROL_CONTINUE:
       
g_Status.dwCurrentState = SERVICE_RUNNING;
       
break;
   
case SERVICE_CONTROL_STOP:
   
case SERVICE_CONTROL_SHUTDOWN:
       
g_Status.dwCurrentState = SERVICE_STOPPED;
       
break;
   
}
   
SetServiceStatus( g_hStatus, & g_Status );
   
return NO_ERROR;
}

VOID CALLBACK ServiceMain( DWORD dwArgc, LPTSTR * lpszArgv )
{
   
g_Status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
   
g_Status.dwCurrentState = SERVICE_RUNNING;
   
g_Status.dwControlsAccepted = SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_STOP |
   
SERVICE_ACCEPT_SHUTDOWN;
   
g_Status.dwWin32ExitCode = NO_ERROR;
   
g_Status.dwServiceSpecificExitCode = 0;
   
g_Status.dwCheckPoint = 0;
   
g_Status.dwWaitHint = 0;
   
g_hStatus = RegisterServiceCtrlHandlerEx( "TestService", SvcHandlerEx, 0 );
   
SetServiceStatus( g_hStatus, & g_Status );
}

int main( int argc, char * argv[ ] )
{
   
SERVICE_TABLE_ENTRY svc[ ] =
   
{
        {
"TestService", ServiceMain },
       
{ NULL, NULL }
    }
;
   
StartServiceCtrlDispatcher( svc );
   
return 0;
}

Instalacja usługi

Do zainstalowania usługi służy funkcja CreateService, jednak zanim jej użyjemy musimy połączyć się z SCM funkcją OpenSCManager.
C/C++
SC_HANDLE hSCM = OpenSCManager( NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS );
Pierwszy argument to nazwa komputera, jeśli damy NULL lub pusty string to połączy nas z naszym komputerem. Drugi argument zawiera nazwę bazy danych, na której chcemy pracować, SERVICES_ACTIVE_DATABASE to aktywna baza, jeśli damy NULL to domyślnie wybierze aktywną bazę. Ostatnim argumentem są uprawnienia jakie chcemy uzyskać, SC_MANAGER_ALL_ACCESS to pełne uprawnienia, możemy oczywiście żądać mniejszych praw pamiętając, że OpenSCManager sama wymaga uprawnienia SC_MANAGER_CONNECT.

Gdy już dobraliśmy się do managera usług to możemy przystąpić do instalacji usługi. Funkcja CreateService przyjmuje 13 argumentów, oto one:

Argument Opis
SC_HANDLE hSCManager Uchwyt zwrócony przez OpenSCManager.
LPCTSTR lpServiceName Nazwa usługi, musi być unikalna i niezależna od języka.
LPCTSTR lpDisplayName Nazwa wyświetlana, może być zależna od języka ale musi być unikalna.
DWORD dwDesiredAccess Uprawnienia do nowo utworzonej usługi. SERVICE_ALL_ACCESS powinno wystarczyć ;)
DWORD dwServiceType Typ usługi. Dajemy tu to samo co w polu dwServiceType struktury SERVICE_STATUS.
DWORD dwStartType Sposób uruchamiania usługi:
* SERVICE_AUTO_START Usługa jest uruchamiana automatycznie podczas startu systemu.
* SERVICE_DEMAND_START Usługę nalezy uruchamiać "ręcznie" funkcją StartService.
* SERVICE_DISABLED Usługa jest wyłączona i nie mozna jej uruchomić.
DWORD dwErrorControl Sposób obsługi błędów podczas uruchamiania:
* SERVICE_ERROR_IGNORE Błąd jest ignorowany a uruchamianie kontynuowane.
* SERVICE_ERROR_NORMAL Błąd jest zapisywany w dzienniku, uruchamianie kontynuowane.
* SERVICE_ERROR_SEVERE Zapisuje błąd w dzienniku i uruchamia ponownie system w ostatniej-znanej-dobrej-konfiguracji. Jeśli system jest już uruchomiony w ostatniej-znanej-dobrej-konfiguracji to kontynuuje uruchamianie.
* SERVICE_ERROR_CRITICAL Jak wyżej zapisuje i uruchamia w ostatniej-znanej-dobrej-konfiguracji. Jednak jeśli system jest uruchomiony na ostatniej-znanej-dobraj-konfiguracji to przerywa uruchamianie.
LPCTSTR lpBinaryPathName Ścieżka do pliku wykonywalnego usługi, jeśli zawiera spacje musi być objęta cudzysłowem, może zawierać argumenty dla programu. Np. "\"D:\\moje usługi\\moja usługa.exe\" -lubię placki"
LPCTSTR lpLoadOrderGroup Grupa, do której należy usługa. Można dać NULL, wtedy usługa nie należy do żadnej grupy.
LPDWORD lpdwTagId Wskaźnik na zmienną, do której zostanie zapisany numerek usługi wewnątrz grupy. Może być NULL.
LPCTSTR lpDependencies Lista usług i grup, od których usługa jest zależna. Zostaną one uruchomione przed tą usługą. Nazwy rozdziela się kodem '\0' a cała lista jest zakończona dwoma takimi kodami. Przed nazwą grupy należy umieścić SC_GROUP_IDENTIFIER, czyli '+'(znak plusa). Np. "Messenger\0W32Time\0+Video\0".
LPCTSTR lpServiceStartName Nazwa konta użytkownika, na którym usługa będzie uruchamiana, w formacie <domena>\<użytkownik>. Jako domenę można podać '.'(kropkę) wtedy zostanie użyta lokalna domena. Usługi można uruchamiać też na specjalnych kontach:
* LocalSystem - podajemy NULL lub ".\\LocalSystem" - Konto o najwyższych uprawnieniach w systemie, możliwa interakcja z użytkownikiem.
* LocalService - "NT AUTHORITY\\LocalService" - Konto ograniczone.
* NetworkService - "NT AUTHORITY\\NetworkService" - Konto ograniczone z dostępem do sieci.
* Od Windows 7 mozliwe jest uruchamianie usług na wirtualnych kontach NT SERVICE\<usługa>.
LPCTSTR lpPassword Hasło do konta podanego w lpServiceStartName. Dla kont bez hasła podajemy pusty ciąg lub NULL.

CreateService zwraca uchwyt usługi typu SC_HANDLE. Uchwyty tego typu zamykamy funkcją CloseServiceHandle

Opis usługi

W aplecie Usługi widzieliśmy, że przy każdej usłudze jest jej opis, jednak CreateService nie przyjmowała żadnego argumentu z opisem, więc jak go dodać?

Mamy do dyspozycji funkcję ChangeServiceConfig, jednak przyjmuje ona praktycznie te same argumenty co CreateService, żeby ustawić opis należy użyć ChangeServiceConfig2. Ale sama funkcja nam nie wystaczy, potrzebujemy jeszcze struktury SERVICE_DESCRIPTION, jest to bardzo prosta struktura, posiada aż jedno pole będące wskaźnikiem na opis naszej usługi.
C/C++
SERVICE_DESCRIPTION desc = { "Taka sobie przykładowa usługa, która nic nie robi" };
ChangeServiceConfig2( hService, SERVICE_CONFIG_DESCRIPTION, & desc );
Pierwszy argument to uchwyt do usługi, który zwróciła nam funkcja CreateService, a jeśli zdążyłeś już go zamknąć to wpierw zawołaj OpenService po nowy uchwyt.
Drugi argument określa, które ustawienie chcemy zmienić, ponieważ zmieniamy opis więc dajemy SERVICE_CONFIG_DESCRIPTION.
W ostatnim argumence podajemy wskaźnik na nowe ustawienie, w naszym przypadku jest to struktura SERVICE_DESCRIPTION.

Nasza usługa na liście usług (Windows XP)
Nasza usługa na liście usług (Windows XP)

Narzędzie SC

SC jest programem działającym w wierszu poleceń, posiada on większe możliwości niż applet Usługi.
W szczególności pozwala na instalację oraz usuwanie usług, dzięki czemu nie musimy pisać osobnego programu do tego celu, wystarczy prosty skrypt.

Używa się go w następujący sposób:
sc <komputer> [polecenie] [usługa] <arg1> <arg2>...
<komputer> jest nazwą komputera, na który ma być wykonane polecenie, jeśli nie zostanie podana to brany jest lokalny komputer.

Najważniejsze polecenia:
* create Instaluje nową usługę.

Opcja Wartości
type Typ usługi:
* own Usługa posiada własny proces.
* share Usługa współdzieli proces z innymi usługami.
* interact Usługa interaktywna.
Domyślnie: own
start Sposób uruchamiania:
* auto Automatycznie podczas startu systemu.
* demand Na żądanie.
* disabled Usługa jest całkowicie wyłączona.
Domyślnie: demand
error Sposób obsługi błędów:
* ignore
* normal
* severe
* critical
Domyślnie: normal
binPath Ścieżka do pliku wykonywalnego.
group Grupa usług.
tag yes
* no
depend Zależności oddzielone '/' (ukośnikiem).
obj Konto, na którym ma działać usługa. Domyślnie LocalSystem.
DisplayName Nazwa wyświetlana.
password Hasło do konta.

Przykład:
sc create TestService start= auto error= ignore binPath= c:\testsvc.exe
obj= "NT AUTHORITY\LocalService" DisplayName= "Przykładowa usługa"
* config Zmienia konfigurację usługi. Argumenty takie same jak przy poleceniu create.
* description Ustawia opis usługi. Przykład:
sc description TestService "Taka sobie usługa"


* start Uruchamia usługę. Argumenty są przekazywane do usługi. Przykład:
sc start TestService -lol 42


* control Wysyła komunikat do usługi. Argument: kod od 128 do 255 lub paramchange. Przykład:
sc control TestService 128


* pause Wstrzymuje usługę.
* continue Wznawia usługę.
* stop Zatrzymuje usługę.
* delete Oznacza usługę do usunięcia, zostanie usunięta po zatrzymaniu.
Przykład:
sc stop TestService


* query oraz queryex Wyświetla (rozszerzony) status usług(i). Jeśli nie zostanie podana nazwa usługi to wyświetla listę usług pasujących do filtra.

Opcja Wartości
type Typ:
* service Usługi.
* driver Sterowniki.
* all Wszystko.
Domyślnie: service
state Stan:
* inactive Zatrzymane.
* all Wszystkie.
Domyślnie: active Niezatrzymane (nie można jawnie wybrać tej opcji).
group Grupa usług. Bez grupy: "" (pusty napis). Domyślnie wszystkie grupy.
ri Indeks, od którego ma zacząć wypisywanie. Domyślnie 0.
bufsize Rozmiar bufora enumeracji. Domyślnie 4096.

Przykład:
sc queryex state= all group= ""
Poprzedni dokument Następny dokument
Drukowanie Winsock