Delphi és Win32 API – Nem csak Delphi felhasználóknak: VIII. rész

Windows NT Szolgáltatások – 2.

 

A Borland Delphi rendszer számos, a Windowsban (és a Linuxban) használt rendszereszközhöz tartalmaz komponenseket, osztályokat. Mégis érhetetlen módon több elem hiányzik belőle, melyek sokszor vagy csak ritkán, de jól jönnének egy program fejlesztése során. Cikksorozatunkban ilyen komponenseket fogunk készíteni. A sorozatot más programnyelvek használóinak is ajánlom, hiszen a bemutatott Windows API függvények bizonyára más nyelveken is elérhetőek.

Előző számunkban megnéztük, miként készíthetjük el a számítógépünkön található Windows NT szolgáltatások listáját. A következőkben megnézzük, miként tudunk az egyes szolgáltatásokról adatokat lekérdezni, vezérelni őket.

 

Szolgáltatás elérése

 

Az előző számban láthattuk, hogy minden szolgáltatásnak van egy belső neve, amit kulcsnak nevezünk. Ezzel a kulcs névvel tudunk hozzáférni, beállítani, vezérelni. Ahhoz, hogy ezeket megtegyük, először el kell érnünk, meg kell nyitnunk a szolgáltatást. Ennek során egy kezelő számot (handle) kapunk a rendszertől, mellyel a továbbiakban hivatkozni lehet a szolgáltatásra. Ehhez az OpenService függvényre van szükségünk:

function OpenService(hSCManager: SC_HANDLE; lpServiceName: PChar;

  dwDesiredAccess: DWORD): SC_HANDLE; stdcall;

Az első paraméterben az OpenSCManager függvény által szolgáltatott azonosítót kell megadnunk. Ezt követően adjuk meg az elérni kívánt szolgáltatás nevét. Attól függően, hogy a bejelentkezett felhasználó milyen jogokkal rendelkezik a számítógépen, bizonyos információk, műveletek nem biztos, hogy elvégezhetőek. Ennek érdekében a dwDesiredAccess paraméterben kell megadnunk, hogy milyen műveleteket szeretnénk végezni a megnyitás után. Ez az alábbi konstansok tetszőleges kombinációja lehet:

Þ      SERVICE_QUERY_CONFIG: engedélyt kérünk, hogy lekérdezzük a szolgáltatás beállításait.

Þ      SERVICE_CHANGE_CONFIG: nem csak olvasni, de módosítani is szeretnénk a beállításokat.

Þ      SERVICE_QUERY_STATUS: szeretnénk lekérdezni a szolgáltatás állapotát (pl. fut-e?)

Þ      SERVICE_ENUMERATE_DEPENDENTS: bizonyos szolgáltatások csak akkor futhatnak, ha más szolgáltatások már futnak. Például Windows 2000-en a Telnet szolgáltatást csak akkor lehet elindítani, ha a Távoli eljáráshívás szolgáltatás már fut, mert használja annak funkcióit. Ezt a függőségi listát is le tudjuk kérni, melyet ezzel a joggal tudunk megadni.

Þ      SERVICE_START: el szeretnénk indítani a szolgáltatást, ha még nem fut

Þ      SERVICE_STOP: le szeretnénk állítani a szolgáltatást, ha már fut

Þ      SERVICE_PAUSE_CONTINUE: bizonyos szolgáltatásokat fel lehet függeszteni, szüneteltetni. Ilyenkor egy olyan állapotba kerülnek, mint ha nem futnának, a folytatás alkalmával azonban nem kell újra beolvasniuk beállításaikat, onnan futhatnak tovább, ahol fel lettek függesztve.

Þ      SERVICE_INTERROGATE: Az „Interrogate” vezérlőutasítással arra kényszeríthetjük a szolgáltatást, hogy állapotát haladéktalanul jelezze a SCM-nek. Ahhoz, hogy ezt megtegyük, kérni kell ezt a jogot.

Þ      SERVICE_USER_DEFINED_CONTROL: Lehetőség van arra is, hogy a rendszer által előre nem definiált vezérlő kódokat küldjünk a szolgáltatásnak (előre definiált például a leállítás, felfüggesztés, stb. parancsok). Az ilyen utasítások küldéséhez ezt a jogot kell igényelnünk.

Visszatérési értéke a függvénynek a már említett kezelőszám, melyet az alábbiakban fogunk felhasználni.

 

Amennyiben már nem foglalkozunk egy megnyitott szolgáltatással, le kell zárni vele a kapcsolatot a CloseService függvénnyel:

function CloseServiceHandle(hSCObject: SC_HANDLE): BOOL; stdcall;

Talán túl sok magyarázatot nem igényel a függvény, egyetlen paramétere a korábban kapott kezelőszám.

 

Egy szolgáltatás beállításainak lekérdezése

 

Ha sikeresen megnyitottunk egy szolgáltatást, a QueryServiceConfig függvény segítségével tudjuk lekérni beállításait:

function QueryServiceConfig(hService: SC_HANDLE; lpServiceConfig: PQueryServiceConfig; cbBufSize: DWORD; var pcbBytesNeeded: DWORD): BOOL; stdcall;

Az adatokat egy rekordban kapjuk meg, a kéréshez ugyan úgy tudjuk megadni a cbBufSize paraméter értékét, ahogy az előző számban már bemutattam az EnumServices függvény használatánál. A kapott rekord felépítése:

   TQueryServiceConfig = record

    dwServiceType: DWORD;

    dwStartType: DWORD;

    dwErrorControl: DWORD;

    lpBinaryPathName: PAnsiChar;

    lpLoadOrderGroup: PAnsiChar;

    dwTagId: DWORD;

    lpDependencies: PAnsiChar;

    lpServiceStartName: PAnsiChar;

    lpDisplayName: PAnsiChar;

  end;

Þ      dwServiceType: a szolgáltatás típusa. A mező a következő értékek közül vesz fel egy értéket:

o        SERVICE_FILE_SYSTEM_DRIVER: Fájl rendszer meghajtó program. Az ilyen szolgáltatás felelős, hogy a különböző fájlrendszerek kezelését ellássák.

o        SERVICE_KERNEL_DRIVER: Egyéb meghajtó program: videokártya, hangkártya, egyéb eszközök kezelőprogramja.

o        SERVICE_WIN32_OWN_PROCESS: Olyan program, mely külön folyamatként fut, így ha valamilyen hibát generál, akkor (elméletileg) nem veszélyezteti más folyamatok futását.

o        SERVICE_WIN32_SHARE_PROCESS: Ezek a szolgáltatások megosztják memóriaterületüket más folyamatokkal.

Þ      dwStartType: Az előző számban már végig tárgyaltuk, milyen indítási módjai lehetnek egy-egy szolgáltatásnak (Automatikus, kézi, tiltott, stb.), így most csak felsorolás szinten írnám le a mező lehetséges értékeit: SERVICE_AUTO_START (Automatikusan), SERVICE_BOOT_START (a rendszer betöltése folyamán), SERVICE_DEMAND_START (kézi), SERVICE_DISABLED (tiltott), SERVICE_SYSTEM_START (a rendszer indítása során).

Þ      dwErrorControl: A hibakezelés módjait szintén leírtam az előző számban, a mező lehetséges értékei: SERVICE_ERROR_IGNORE, SERVICE_ERROR_NORMAL, SERVICE_ERROR_SEVERE, SERVICE_ERROR_CRITICAL.

Þ      lpBinaryPathName: a szolgáltatást tartalmazó program fájl neve és teljes elérési útvonala. Ez a mező tartalmazza továbbá az esetlegesen szükséges parancssori paramétereket a program indulásához.

Þ      lpLoadOrderGroup: A rendszer egyes szolgáltatásokat csoportokba szervez, hogy nyilván tudja tartani azok indítási sorrendjét, vagyis azt, hogy milyen sorrendben futtassa őket. Ez a mező tartalmazza, hogy melyik ilyen csoport tagja a szolgáltatás. Ha egyiknek se, a mező értéke nil.

Þ      dwTagId: Ha tagja egy ilyen csoportnak, akkor azon belül egy egyedi azonosító számot kap, melyet ebből a mezőből tudhatunk meg.

Þ      lpDependencies: Ebben a mezőben kapjuk meg a szolgáltatás függőségeit, vagyis azon szolgáltatások neveit, amelyeket mindenképpen futtatni kell, mielőtt ezt a szolgáltatást futtatjuk. A listát egyetlen karakterláncba kapjuk, melyben az elemelválasztó a 0 kódú karakter, a lista végét pedig két 0 kódú karakter zárja. Ha ez a mező nil vagy egy üres string, akkor nincsenek függőségei.

Þ      lpServiceStartName: Ebben a mezőben adja vissza nekünk a rendszer, hogy milyen felhasználóként jelentkezik be és fut a szolgáltatás, amikor elindul. A karakterláncot „TartományNév\FelhasználóiNév” formában kapjuk meg, de ha a felhasználó ahhoz a tartományhoz tartozik, amelyhez a számítógép is, akkor „.\FelhasználóiNév” formában jelenik meg. Ha a mező értéke nil, akkor a szolgáltatás a LocalSystem felhasználó környezetében fut.

Þ      lpDisplayName: Ebben a mezőben ugyanazt a stringet kapjuk vissza, melyet már az EnumServices függvénnyel is már megkaptunk. Amennyiben a programunkban valahol meg kell jeleníteni a szolgáltatás nevét, akkor az ebben a mezőben található értéket kell felhasználnunk.

 

A példa továbbfejlesztése

 

Az előző részben bemutatott példát fejlesztjük most tovább, hogy a gyakorlatba ültessük át az eddig leírtakat.

Nyissuk meg az előző példaprogramot, és módosítsuk úgy, hogy a ListBox1-be a szolgáltatások belső elnevezéseit jelenítse meg. Dobjunk még egy gombot a listázó gomb mellé, valamint nyolc TLabel-t és még egy TListBox-ot a Form-ra. Ha a most elhelyezett gombra klikkelünk, a lista alatt megjelennek az elérhető információk, valamint a függőségek az új ListBox-ban. Az új gomb OnClick eseménykezelője:

procedure TForm1.Button2Click(Sender: TObject);

var

  scHandle : SC_HANDLE;

  Service : SC_Handle;

 

  NeedBytes, ErrorCode : Cardinal;

  Buffer : PQueryServiceConfig;

begin

  if ListBox1.ItemIndex = -1 then

    exit;

   

  scHandle := OpenSCManager('',nil,SC_MANAGER_ALL_ACCESS);

  if scHandle = 0 then

    begin

      MessageDlg(SysErrorMessage(GetLastError),mtError,[mbOK],0);

      exit;

    end;

 

  try

    Service := OpenService(scHandle,PChar(ListBox1.Items[ListBox1.ItemIndex]),SERVICE_QUERY_CONFIG);

    if Service = 0 then

      begin

        MessageDlg(SysErrorMessage(GetLastError),mtError,[mbOK],0);

        exit;

      end;

 

    try

      Buffer := nil;

      if not QueryServiceConfig(Service,nil,0,NeedBytes) then

        begin

          ErrorCode := GetLastError;

          if ErrorCode = ERROR_INSUFFICIENT_BUFFER then

            begin

              GetMem(Buffer,NeedBytes + 1);

 

              if not QueryServiceConfig(Service,Buffer,NeedBytes,NeedBytes) then

                begin

                  MessageDlg(SysErrorMessage(GetLastError),mtError,[mbOK],0);

                  exit;

                end;

            end

            else

            begin

              MessageDlg(SysErrorMessage(ErrorCode),mtError,[mbOK],0);

              exit;

            end;

        end;

 

      with Buffer^ do

        begin

          case dwServiceType of

            SERVICE_FILE_SYSTEM_DRIVER : SrvType.Caption := 'Fájlrendszer meghajtó';

            SERVICE_KERNEL_DRIVER: SrvType.Caption := 'Egyéb meghajtó';

            SERVICE_WIN32_OWN_PROCESS: SrvType.Caption := 'Win32 saját folyamatában futó';

            SERVICE_WIN32_SHARE_PROCESS: SrvType.Caption := 'Win32 megosztott folyamatban futó';

          end;

 

          case dwStartType of

            SERVICE_AUTO_START : SrvStart.Caption := 'Automatikus';

            SERVICE_BOOT_START: SrvStart.Caption := 'Rendszer betöltéskor';

            SERVICE_DEMAND_START: SrvStart.Caption := 'Kézi';

            SERVICE_DISABLED: SrvStart.Caption := 'Letiltva';

            SERVICE_SYSTEM_START: SrvStart.Caption := 'Rendszer induláskor';

          end;

 

          case dwErrorControl of

            SERVICE_ERROR_IGNORE: ErrorCtrl.Caption := 'Ignore';

            SERVICE_ERROR_NORMAL: ErrorCtrl.Caption := 'Normal';

            SERVICE_ERROR_SEVERE: ErrorCtrl.Caption := 'Severe';

            SERVICE_ERROR_CRITICAL: ErrorCtrl.Caption := 'Critical';

          end;

 

          BinPath.Caption := lpBinaryPathName;

          GroupName.Caption := lpLoadOrderGroup;

          GroupID.Caption := IntToStr(dwTagId);

          AccountName.Caption := lpServiceStartName;

          DispName.Caption := lpDisplayName;

 

          ListBox2.Items.Clear;

 

          while (lpDependencies[1] <> #0) do

            begin

              if (lpDependencies[0] = #0) and (lpDependencies[1] <> #0) then

                lpDependencies := lpDependencies + 1;

              ListBox2.Items.Add(StrPas(lpDependencies));

              lpDependencies := lpDependencies + StrLen(lpDependencies);

            end;

        end;

    finally

      if not CloseServiceHandle(Service) then

        MessageDlg(SysErrorMessage(GetLastError),mtError,[mbOK],0);

    end;

  finally

    if not CloseServiceHandle(scHandle) then

      MessageDlg(SysErrorMessage(GetLastError),mtError,[mbOK],0);

  end;

 

Attól most eltekintenék, hogy leírjam miként kapcsolódunk egy SCM-hez.

A sikeres kapcsolódás után megpróbáljuk elérni a ListBox1-ben kijelölt szolgáltatást az OpenService függvény segítségével, majd az lekérjük az adatok lekérdezéséhez szükséges méretet az első QueryServiceConfig hívással, végül az adatokat a másodikkal. Ha az adatok lekérdezése sikeres volt, úgy folyamatosan töltjük ki a Label-eket a kapott értékekkel.

Kicsit bővebb magyarázatra szorul talán a függőségek karakterláncának feldolgozása.

Az eseménykezelő végén található while ciklus végzi el nekünk ezt a munkát oly módon, hogy kihasználjuk, hogy egy PChar változó valójában csak egy mutató, melyhez, ha például 1-et hozzáadunk, a soron következő memóriacímen található adatot kapjuk meg. A ciklus addig fut, amíg a mutatót követő bájt nem a nulla bájt. Ez azt jelenti, hogy a soron következő karakter a dupla nulla karakter második tagja, vagyis akkor vége a listának. Ha karakterláncként kezeljük a PChar-t, akkor az első nulla kódig tartó karakterláncot láthatjuk mindig. Ez pontosan egy elem a listában (hiszen a listaelemeket a nullakódú karakter választja el). Ha tehát éppen nem egy string vége karakteren állunk és a következő karakter sem az, akkor ezt a stringet egyszerűen átalakítjuk Pascal stringgé és betesszük a ListBox2-be, majd a mutató értékét az éppen hozzáadott karakterlánc hosszával növeljük.

Ez így most nyilván zavaros, így nézzük meg a ciklust működés közben:

Tegyük fel, hogy az lpDependencies mező értéke a következő (jelölje #0 a nullakódú karaktert):

 

Valami#0Proba#0Szoveg#0#0

 

A ciklus elején a mutató a „Valami1” szöveg első „V” betűjére mutat. A cikluson belüli if nem lesz igaz, mert lpDependencies[0] = ’V’ <> #0 és lpDependencies[1] = ’a’ <> #0. Az StrPas függvény az első talált #0 karakterig alakítja át a PChar-t stringgé, így a „Proba” szöveg „P” betűje előtti #0-ig dolgozik. Ezzel tehát visszakaptuk a „Valami” elemet a listából. A ciklus végén a mutatót (ami eddig a „V” betűre mutatott) megnöveljük a „Valami” szöveg hosszával, vagyis 6-al, ezzel a „Proba” string „P” betűje előtti #0-ra fog mostantól mutatni.

A ciklus második lefutásakor az if feltétel első tagja így igaz értékű lesz (lpDependencies[0] = #0) és a második tagja is, mivel lpDependencies[1] = „P” <> #0. Így tehát még eggyel megnöveljük lpDependencies értékét, hogy a „P” betűre mutasson.

 

Ezzel tehát feldolgozzuk a „Proba” és a „Szoveg” elemeket, majd a ciklus harmadik lefutásának végére lpDependencies a „Szoveg” string „g” betűje után következő #0-ra fog mutatni. Mivel ekkor lpDependencies =#0 és lpDependencies[1] = #0, ezért nem lesz több iteráció, a ciklus lefutott, az elemeket sikeresen feldolgoztuk.

 

A következő részben megnézzük, hogyan kérdezhetjük le egy szolgáltatás állapotát és megnézzük, miként is vezérelhetjük azokat.

 

Geiger Tamás - info@gsc.hu