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:
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.
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).
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.
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:
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:
*
dwControlsAccepted Akceptowane kody kontroli. Najważniejsze flagi:
*
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ę.
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).
DWORD CALLBACK SvcHandlerEx( DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext )
Argumenty:
*
dwControl Kod kontroli. Najważniejsze kody:
*
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.
SC_HANDLE hSCM = OpenSCManager( NULL, NULL, SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE ); SC_HANDLE hSvc = OpenService( hSCM, "TestService", SERVICE_USER_DEFINED_CONTROL ); SERVICE_STATUS stat;
ControlService( hSvc, 128, & stat ); CloseServiceHandle( hSvc ); 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
#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.
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:
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.
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.
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ę.
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.
Przykład:
sc queryex state= all group= ""