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:
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 )
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:
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:
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:
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:
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:
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 )
Poniższy przykład pokazuje, jak pobrać wartość typu tekstowego do bufora i następnie ukazać ją zaskoczonym oczętom użytkownika:
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:
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:
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:
result = RegDeleteKey( hkSoftware, "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:
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 )
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:
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:
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:
DWORD dwMaxName_ = dwMaxName, dwMaxData_ = dwMaxData;
Teraz dopiero możemy przystąpić do pobierania w pętli kolejnych wartości z rejestru:
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).