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