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

Rejestr

[lekcja] Rozdział 18.

Rejestr


W opisywanie, czym jest Rejestr i do czego służy nie będę się tutaj bawić - wszystko to powinieneś już dawno wiedzieć, a jeśli nie wiesz, to pan Google chętnie powie. W tym kursie zakładam, że odpowiednią terminologię znasz, a i Edytor Rejestru jest ci nieobcy. Dlatego też bez zbędnych ceregieli przechodzimy od razu do grzebania w tym największym windowsowym śmietnisku ;-)

Otwieranie i tworzenie kluczy

Żeby cokolwiek zrobić z danym kluczem Rejestru, musi on być najpierw otwarty. Trochę to dziwne, zwłaszcza dla kogoś kto przyzwyczaił się, że klucze to przyrządy do otwierania czegoś, a nie odwrotnie. Mniejsza jednak o szczegóły. Można dokonać otwarcia klucza przy pomocy funkcji RegOpenKeyEx lub RegCreateKeyEx. Łatwo się domyślić, że ta druga dodatkowo tworzy klucz, jeśli wcześniej on nie istniał. W obu przypadkach po otwarciu danego klucza uzyskujemy do niego uchwyt (typu HKEY). Posługując się tym uchwytem możemy z kluczem zrobić niemal wszystko, co nam potrzebne.
 
Przejdźmy teraz do praktyki. Odpalmy Edytor Rejestru (co najprościej uczynić, wciskając Win+R, a następnie wpisując 'regedit' i Enter). Otwieramy klucz HKEY_CURRENT_USER/Software, oczom naszym ukazuje się mniej więcej taki widok:

Edytor Rejestru, otwarty klucz HKEY_CURRENT_USER/Software (Windows 98)
Edytor Rejestru, otwarty klucz HKEY_CURRENT_USER/Software (Windows 98)

Następnie tworzymy w nim nowy klucz, np. o nazwie Test:
 
Tyle nam na razie wystarczy, spróbujemy teraz otworzyć ten klucz z poziomu C++. Funkcja RegOpenKeyEx prezentuje się następująco:

Składnia:
RegOpenKeyEx( hKey, lpSubKey, ulOptions, samDesired, phkResult )

Argument Znaczenie
hKey Uchwyt otwartego klucza
lpSubKey Nazwa podklucza, którey otwieramy/tworzymy
ulOptions Zarezerwowane - nie używać
samDesired Maska bezpieczeństwa dostępu
phkResult Adres zmiennej na uchwyt klucza

Warto wiedzieć, że istnieje też funkcja RegOpenKey o znacznie prostszej składni, jednak pochodzi ona jeszcze z czasów Windows 3.1 (gdzie, jak wiemy, Rejestru w znanej nam formie właściwie nie było) i jej stosowanie nie jest zalecane.
 
Z powyższych argumentów najbardziej interesuje nas pierwszy. Jest to, jak widać w opisie, uchwyt do otwartego klucza. Więc jak to - mógłby ktoś trzeźwo zapytać - żeby otworzyć klucz i uzyskać do niego uchwyt, musimy podać uchwyt do już otwartego klucza? Przecież można w ten sposób w nieskończoność... Na szczęście nikt nie wymaga od programistów, żeby próbowali gonić własny ogon ;-). Okazuje się bowiem, że niektóre klucze są otwarte przez cały czas, a uchwyty do nich są stałymi: HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_USERS (przy okazji dowiedzieliśmy się, skąd wzięły się te nazwy w Regedicie, co pewnie niejednego nurtowało ;-)). Te cztery akurat są wspólne dla wszystkich 32-bitowych Windowsów, natomiast w zależności od konkretnej platformy mogą wystąpić także inne predefiniowane klucze (np. w Windows 98 są jeszcze: HKEY_CURRENT_CONFIG i HKEY_DYN_DATA).
 
Tak więc, skoro mamy dane uchwyty kluczy pierwszego i najwyższego poziomu, to przy ich pomocy możemy otworzyć klucze drugiego poziomu, np. w naszym przypadku klucz Software. Uczyńmy to zatem:

C/C++
HKEY hkSoftware;
LONG result;
result = RegOpenKeyEx( HKEY_CURRENT_USER, "software", 0, KEY_ALL_ACCESS, & hkSoftware );
if( result == ERROR_SUCCESS )
   
 MessageBox( hwnd, "Udało się otworzyć klucz.", "Test", MB_ICONINFORMATION );

Z parametrów funkcji RegOpenKeyEx niewiele jest do omawiania; w sumie rola każdego z nich powinna być oczywista. Nieco mniej jasne wydaje się użycie stałej KEY_ALL_ACCESS - oznacza ona, że życzymy sobie pełnego dostępu do danego klucza. Mogłoby się okazać, że np. klucz jest zabezpieczony przed zapisem i wtedy otwarcie go z flagą KEY_ALL_ACCESS zapewne by się nie powiodło. W przeciwnym razie funkcja zwraca wartość o dość schizofrenicznej nazwie ERROR_SUCCESS (jawny dowód na to, że w systemie Windows sukces jest sytuacją wyjątkową ;-)), co oznacza, że klucz jest już gotowy do użycia.
 
W samym kluczu Software grzebać trochę nie wypada, gdyż jest on wspólny dla wszystkich aplikacji w systemie, w tym również dla samych aplikacji systemowych. Dużo grzeczniej z naszej strony będzie, jeśli stworzymy sobie własny klucz do prywatnego użytku. Tutaj znowu mamy do wyboru dwa warianty: prostszy RegCreateKey oraz dość skomplikowany RegCreateKeyEx. I znowu wybieramy ten drugi, gdyż jesteśmy akurat chorobliwie ambitni:

C/C++
HKEY hkTest;
LONG result;
DWORD dwDisp;

result = RegCreateKeyEx( hkSoftware, "test", 0, NULL, REG_OPTION_NON_VOLATILE,
KEY_ALL_ACCESS, NULL, & hkTest, & dwDisp );

if( result == ERROR_SUCCESS )
if( dwDisp == REG_CREATED_NEW_KEY )
   
 MessageBox( hwnd, "Udało się stworzyć nowy klucz.", "Test", MB_ICONINFORMATION );
else if( dwDisp == REG_OPENED_EXISTING_KEY )
   
 MessageBox( hwnd, "Udało się otworzyć istniejący klucz.", "Test", MB_ICONINFORMATION );

Trzy pierwsze argumenty funkcji są identyczne jak w przypadku RegOpenKeyEx. Czwarty, lpClass, oznacza stringa z nazwą klasy klucza - możemy tu dać NULL. Kolejny argument to specjalne opcje tworzenia klucza - znów nie musimy się tym specjalnie przejmować, gdyż mają one znaczenie (i to niewielkie) tylko w systemach klasy NT (co prawda teraz prawie wszyscy mają takie systemy... no, nieistotne :-)). Daliśmy więc REG_OPTION_NON_VOLATILE tylko po to, żeby ładnie wyglądało ;-). Argument samDesired już znamy - to opcje dostępu do nowo tworzonego klucza. Ponieważ to właśnie my tworzymy ten klucz, więc raczej chcemy mieć do niego pełny dostęp, czyli dajemy KEY_ALL_ACCESS.
 
Pozostały trzy argumenty. Ten figurujący jako lpSecurityAttributes znowu bezczelnie olejemy, podając NULL. Wreszcie dochodzimy do wskaźnika do zmiennej typu HKEY, która otrzyma uchwyt nowo utworzonego klucza oraz wskaźnika do zmiennej typu DWORD, która otrzyma wartość mówiącą, czy klucz istniał przed wywołaniem funkcji RegCreateKeyEx czy też nie istniał. Trzeba ci bowiem wiedzieć, że funkcja ta nie będzie protestowała w przypadku próby utworzenia już od wieków istniejącego klucza i również w takim przypadku, co może się wydać zaskakujące, zwróci ERROR_SUCCESS. Efekt:

Utworzyliśmy klucz - jeden śmieć więcej w Rejestrze ;-) (Windows 98)
Utworzyliśmy klucz - jeden śmieć więcej w Rejestrze ;-) (Windows 98)

Tworzenie i odczytywanie wartości

Z samych kluczy niewielki mamy pożytek, więc warto dowiedzieć się, jak ustawiamy konkretne wartości. Intuicja podpowiada, że służy do tego funkcja RegSetValue - i faktycznie, istnieje taka. Jednak jest to kolejny relikt z czasów Windows 3.1 i do naszych celów funkcja ta jest praktycznie bezużyteczna, gdyż potrafi tylko ustawiać domyślną wartość klucza. Aby ustawić "zwykłą" wartość, musimy skorzystać z nowszej wersji tej funkcji, czyli oczywiście RegSetValueEx:

C/C++
char buf[ 20 ];
lstrcpy( buf, "Jakiś tam tekst" );
result = RegSetValueEx( hkTest, "MojaWartość", 0, REG_SZ,( LPBYTE ) buf, lstrlen( buf ) + 1 );
if( result == ERROR_SUCCESS )
   
 MessageBox( hwnd, "value is set.", "Test", MB_ICONINFORMATION );

Składnia naszej funkcji przypomina trochę poprzednie dwie, tyle że tym razem zamiast nazwy podklucza podajemy nazwę wartości. Czwarty argument, czyli dwType, to typ wartości. Oto tabelka z najczęściej stosowanymi typami:
 
Stała Znaczenie
REG_BINARY Dowolne dane binarne
REG_DWORD Liczba 32-bitowa bez znaku
REG_SZ String
REG_MULTI_SZ Tablica stringów

W powyższym przykładzie użyliśmy sobie typu REG_SZ, czyli zwykłego stringa (najpowszechniejszy typ danych w Rejstrze). Aby umieścić stringa w Rejestrze, musieliśmy najpierw stworzyć sobie bufor, skopiować do niego tego stringa za pomocą funkcji lstrcpy, a następnie podać adres bufora funkcji RegSetValueEx. Funkcja ta oczekuje wskaźnika tylko do pierwszego bajtu wartości, więc musieliśmy przekonwertować adres bufora do typu LPBYTE (czyli BYTE*). Oczywiście jeśli funkcja oczekuje wskaźnika typu LPBYTE, a wskazywany bufor jest zwykle dłuższy niż 1 bajt, to należy się spodziewać, że będziemy również musieli podać długość tego bufora. A ponieważ funkcja przyjmuje dane różnych typów (nie tylko stringi), więc do tej długości wlicza się również znak zerowy na końcu stringa, dlatego też dodajemy jedynkę do długości otrzymanej przy pomocy lstrlen.
 
Naturalnie, jeśli los się na nas nie uwziął, to po tych wszystkich zabiegach RegSetValueEx powinna nam zwrócić ERROR_SUCCESS.
 
Odczytywanie wartości jest bardzo podobne do ich ustawiania, przynajmniej pod względem składniowym. Tak więc wystarczy nieco zmodyfikować poprzedni przykład. Funkcja, którą tu zaprzęgniemy do pracy, zowie się RegQueryValueEx.

Składnia:
RegQueryValueEx( hKey, lpValueName, lpReserved, lpType, lpData, lpcbData )

Argument Znaczenie
hKey Uchwyt klucza
lpValueName Nazwa wartości
lpReserved Nie dotykać, grozi trwałym kalectwem
lpType Adres bufora na typ wartości
lpData Adres bufora na dane wartości
lpcbData Rozmiar bufora na dane

Poniższy przykład pokazuje, jak pobrać wartość typu tekstowego do bufora i następnie ukazać ją zaskoczonym oczętom użytkownika:
 
C/C++
char buf[ 21 ];
DWORD dwBufSize = 20;
DWORD dwRegsz = REG_SZ;
result = RegQueryValueEx( hkTest, "MojaWartość", NULL, & dwRegsz,( LPBYTE ) buf, & dwBufSize );
if( result == ERROR_SUCCESS ) {
   
buf[ 20 ] = 0;
   
MessageBox( hwnd, buf, "Test", MB_ICONINFORMATION );
}

Jedyna istotna dla nas różnica jest taka, że ostatni parametr funkcji RegQueryValueEx to tym razem wskaźnik. Musimy do niego wpisać adres zadeklarowanej zmiennej, która będzie zawierać rozmiar bufora (i do której później powędruje liczba bajtów skopiowanych do bufora przez funkcję RegQueryValueEx). Powinna to być zmienna typu DWORD. Pewnie zastanawia cię, dlaczego nie wpisaliśmy po prostu REG_SZ jako argument lpType. Jeżeli dokładnie przyjrzysz się tabelce, zobaczysz, że jest to adres bufora na typ danych. W rzeczywistości musi być to adres DWORD'a, którego wartością jest typ wartości klucza, którą odczytujemy.
 
Jeśli RegQueryValueEx nie napotka po drodze żadnych przeszkód, to tradycyjnie zwróci ERROR_SUCCESS, żądana przez nas wartość zostanie wpisana do bufora wskazywanego przez lpData, zaś rozmiar tej wartości w bajtach zostanie wpisany do zmiennej wskazywanej przez lpcbData.
 
Przewidziano jeszcze jedno zastosowanie funkcji RegQueryValueEx, mianowicie samo sprawdzenie, czy dana wartość istnieje, bez jej pobierania do bufora. Wówczas zamiast dwóch ostatnich wskaźników podajemy NULL:

C/C++
result = RegQueryValueEx( hkTest, "MojaWartość", NULL, REG_NONE, NULL, NULL );
if( result == ERROR_SUCCESS )
   
 MessageBox( hwnd, "Wartość istnieje ;-)", "Test", MB_ICONINFORMATION );

W tym przypadku użycie REG_NONE jako typ wartości ma znaczenie czysto kosmetyczne - robimy tak, żeby na pierwszy rzut oka było widać, że de facto nie pobieramy żadnej wartości.

Usuwanie kluczy i wartości

Usuwanie wartości nie jest sprawą skomplikowaną, przynajmniej w porównaniu do wyżej opisanych funkcji. Zajmuje się tym funkcja RegDeleteValue (tym razem bez Ex ;-)). Podajemy jej tylko uchwyt klucza i nazwę wartości do skasowania:

C/C++
result = RegDeleteValue( hkTest, "MojaWartość" );
if( result == ERROR_SUCCESS )
   
 MessageBox( hwnd, "Z wartości pozostało tylko wspomnienie ", "Test", MB_ICONINFORMATION );

Analogicznie usuwamy klucze. Podobnie jak w przypadku otwierania kluczy, także i tutaj nie możemy się odwołać do klucza bezpośrednio, lecz musimy podać uchwyt do klucza nadrzędnego i nazwę podklucza, który chcemy usunąć. Tak więc nie można usunąć kluczy, które są najwyżej w hierarchii, np. HKEY_CLASSES_ROOT (no i bardzo dobrze ;-)). Przykład:

C/C++
result = RegDeleteKey( hkSoftware, "test" ); // usuń klucz "test"
if( result == ERROR_SUCCESS )
   
 MessageBox( hwnd, "Ulotny jest żywot klucza... ;-)", "Test", MB_ICONINFORMATION );

W rodzinie Windows 9x klucze usuwane są kaskadowo (czyli poklucz wraz ze wszystkimi ewentualnymi pod-podkluczami). W rodzinie NT (czyli również w Windows 2000 oraz XP) można usuwać tylko puste klucze (tj. nie mogą one zawierać żadnych podkluczy). Za to w tym drugim przypadku możesz skorzystać z funkcji SHDeleteKey, aby usuwać klucze rekurencyjnie (kaskadowo).

Więcej wartości naraz


Odczytywanie wielu wartości pod rząd potrafi być deczko nużące. Jeśli nie mamy pod ręką jakiegoś frajera, który by to za nas zakodził, możemy sobie ułatwić życie dzięki funkcji RegEnumValue. Nie jest to może najprzyjaźniejsza w użyciu funkcja WinAPI, ale i tak dużo nam pomoże w sytuacjach podobnych do tej:

Nasze zadanie bojowe - 3 wartości do odczytania (Windows 98)
Nasze zadanie bojowe - 3 wartości do odczytania (Windows 98)

Funkcja RegEnumValue służy do pobieranie kolejnych wartości z danego podklucza (np. tych trzech powyżej). Poza tym używamy jej wtedy, gdy nie znamy nazw wartości, do których właśnie się chcemy dobrać ;-). Składnia jest następująca:
 
Składnia:
RegEnumValue( hKey, dwIndex, lpValueName, lpcbValueName, lpReserved, lpType, lpData, lpcbData )

Argument Znaczenie
hKey uchwyt klucza
dwIndex Indeks wartości do odczytania
lpValueName Bufor na nazwę wartości
lpcbValueName Adres zmiennej z rozmiarem powyśzego bufora
lpReserved Nie dotykać
lpType Adres bufora na typ
lpData Adres bufora na dane
lpcbData Adres na rozmiar bufora na dane

Używamy tej funkcji w ten sposób, że wywołujemy ją tak długo, aż zwróci ERROR_NO_MORE_ITEMS.Za każdym wywołaniem powinniśmy też zwiększać wartość dwIndex (zaczynając od 0). Jeśli funkcja znajdzie w danym podkluczu wartość o podanym przez nas indeksie, to umieści jej nazwę w buforze wskazywanym przez lpValueName, typ w buforze wskazywanym przez lpType, natomiast wartość w buforze wskazywanym przez lpData. Bufor na typ wartości musi być typu DWORD, natomiast pozostałe dwa z wymienionych buforów powinne być odpowiednio duże, żeby pomieścić: nazwę i dane naszej wartości. Tylko jak uczynić je "odpowiednio dużymi", jeśli nie wiemy nic o wartości, którą właśnie pobieramy?
 
Na szczęście informacja o maksymalnym rozmiarze tych buforów jest przechowywana przez system dla każdego klucza osobno. Możemy ją pobrać za pomocą RegQueryInfoKey. Liczba argumentów tej funkcji nie jest mała - "jedyne" 12. Większość z nich to wskaźniki do typu DWORD, każdy z nich reprezentuje jakiś atrybut klucza. Na szczęście te, które nas nie interesują (czyli większość), można spokojnie ustawić na NULL. Chcemy tylko dwóch liczb - maksymalny rozmiar bufora na nazwę oraz bufora na wartość. A żeby nas palce nie zabolały od wpisywania tych NULL-ów, to pobierzemy sobie jeszcze całkowitą liczbę wartości w danym kluczu:

C/C++
DWORD dwMaxName, dwMaxData, dwMaxIndex;
result = RegQueryInfoKey( hkTest, NULL, NULL, NULL, NULL, NULL, NULL, & dwMaxIndex,
& dwMaxName, & dwMaxData, NULL, NULL );
if( result == ERROR_SUCCESS )
   
 MessageBox( hwnd, "Niesamowite, ale się udało.", "Test", MB_ICONINFORMATION );

W ten oto sposób otrzymaliśmy interesujące nas dane. Wiemy już, ile jest wszystkich wartości w kluczu hkTest (ich liczba jest w zmiennej dwMaxIndex - znając tę liczbę nie musimy sprawdzać, czy RegEnumValue zwróciła ERROR_NO_MORE_ITEMS), wiemy również, jak duże powinne być bufory, które teraz użyjemy wraz z RegEnumValue... No właśnie, rozmiar buforów. Twórcy funkcji operujących na rejestrze najwyraźniej nie mogli się zdecydować, czy w rozmiar ten wliczać znak zerowy na końcu, czy też nie. W rezultacie nie dość, że każda funkcja interpretuje to inaczej, to jeszcze taka np. RegEnumValue przyjmuje rozmiar wraz z zerem, ale zwraca bez niego. Żeby ten bałagan jako tako opanować, musimy zwiększyć o 1 wartości otrzymane dzięki RegQueryInfoKey:

C/C++
++dwMaxName;
++dwMaxData;

Drugą pułapką czyhającą na amatora rejestru jest fakt, że RegEnumValue, jako się rzekło, coś tam sobie zwraca oprócz kodu błędu. A tak dokładniej - zwraca liczbę znaków, skopiowanych do podanych przez nas buforów, wpisując tę liczbę do zmiennych, których adresy podajemy (czyli u nas dwMaxName i dwMaxData). Ponieważ zaś zmienne te przekazujemy jako argumenty wielokrotnie, więc przy każdym wywołaniu RegEnumValue musimy do nich wpisywać odpowiednią wartość od nowa. Dlatego też będziemy potrzebować dodatkowych dwóch zmiennych, w których zapamiętamy pierwotne wartości dwMaxName i dwMaxData:

C/C++
DWORD dwMaxName_ = dwMaxName, dwMaxData_ = dwMaxData;

Teraz dopiero możemy przystąpić do pobierania w pętli kolejnych wartości z rejestru:

C/C++
char * ValueName =( char * ) GlobalAlloc( GMEM_FIXED, dwMaxName );
char * Data =( char * ) GlobalAlloc( GMEM_FIXED, dwMaxData );

DWORD ValueType;

for( DWORD i = 0; i < dwMaxIndex; ++i )
{
   
result = RegEnumValue( hkTest, i, ValueName, & dwMaxName, NULL, & ValueType,
   
( LPBYTE ) Data, & dwMaxData );
   
if( result == ERROR_SUCCESS )
   
{
       
MessageBox( hwnd, ValueName, "Nazwa", MB_ICONINFORMATION );
       
MessageBox( hwnd, Data, "Wartość", MB_ICONINFORMATION );
   
}
}

GlobalFree( ValueName );
GlobalFree( Data );

Powyższy przykładzik po prostu wyświetla kolejno wszystkie wartości z klucza hkTest. Zakładamy w nim, że wszystkie wartości są typu REG_SZ (dla przyzwoitości powinniśmy to najpierw sprawdzić - typ wartości kopiowany jest tutaj do ValueType - jednakże, jak wiadomo, autor tej strony słynie z lenistwa).
 
Zwróć uwagę, że zgodnie z tym, co w MSDN napisano, kolejność pobierania wartości z rejestru może być dowolna (chociaż najczęściej pobierane są w takiej kolejności, w jakiej zostały utworzone).
Poprzedni dokument Następny dokument
Akceleratory Łamacze komunikatów