Umíme to s Delphi: 70. díl – práce se systémovým registrem, dokončení

Jubilejní, 70. díl seriálu bude věnován dokončení informací o využití systémového registru. Nejprve si povíme, k čemu bychom registr měli používat a k čemu nikoliv, a pak si ukážeme, co všechno umožňuje třída TRegistry. To však nebude všechno...
Před týdnem jsme skončili jednoduchým příkladem, který demonstroval, jak ukládat do registru údaje naší aplikace. Než se budeme věnovat tématu dnešního článku, tedy detailnímu popisu třídy TRegistry, uvedeme si poznámku týkající se programátorské cti a kultury.

Nač používat systémový registr?

Přestože v příkladu z minulého týdne ukládáme do registru obsah editačního pole, obecné doporučení zní – neukládejte do registru příliš rozsáhlé a dlouhé údaje (především řetězce), ani to zbytečně nepřehánějte s jejich množstvím. Důvody? Registr slouží (měl by sloužit) jako úschovna konfiguračních, instalačních a dalších informací, ale neměl by fungovat jako náhrada databází, textových souborů a jiných datových „skladů“. Jakékoliv údaje, které bezprostředně nesouvisí s chodem aplikace by měly být uloženy jinde. Je samozřejmě možné (a hojně se to dělá) ukládat do registru nejrůznější pracovní cesty, složky, přípony a další údaje, ale asi by se tam neměly objevit básně, dopisy nebo romány, a to ani v případě, že je uživatel zadal a přeje si je uchovat. Pokud vám nestačí zdůvodnění spočívající v apelu na programátorskou čest, nabídnu i racionální důvod: příliš „nakynutý“ a obsáhlý registr je příčinou pomalého nabíhání a chodu systému.

Důležitá poznámka spočívá také v tom, že údaje v registru jsou čitelné pro kohokoliv (kdo to jen trochu umí), není tedy možné uložit do registru citlivé údaje a očekávat, že jejich „skrytím“ z adresářové struktury na disku jsou ochráněny. Zdůrazňuji to proto, že někteří uživatelé mají tendenci vzhlížet k registru jako k něčemu zázračnému; přitom je nutné vidět, že registr (ač se jedná o jednu z klíčových součástí systému) není vlastně nic jiného než trochu vylepšený, centralizovaný, avšak veřejně přístupný a nijak nechráněný soubor *.ini.

Poslední mravokárný výlev se bude týkat odinstalace aplikací. Jsem přesvědčen, že neexistují-li pádné důvody pro opak, měl by scénář být následující:

1. V aplikaci, kterou vytváříme, se rozhodneme uchovávat údaje v systémových registrech.

2. K aplikaci vytvoříme instalační program (instalátor), který (kromě jiných operací) v registru vytvoří potřebné klíče a naplní je default hodnotami.

4. K aplikaci vytvoříme ODinstalační program, který (kromě jiných operací) z registru odstraní všechny klíče vytvořené touto aplikací.

5. Pak teprve aplikaci šíříme.

Realita je bohužel obvykle odlišná. Aplikace má instalátor, který něco „nacpe“ do registru, stejně tak do něj aplikace „cpe“ další údaje v průběhu svého používání. Odinstalační program však nakonec údaje v registru ponechá, případně odstraní jen minimum z nich. Je samozřejmé, že někdy k takovému chování existují dobré důvody (snaha o zajištění uživatelského komfortu při případné další instalaci, snaha o vynucení licenčního ujednání apod.), ale jsem si jist, že v celé řadě případů se jedná jen o zbytečné gesto, možná lajdáctví či naopak frajerství tvůrců, kteří mají pocit, že po nich musí něco zůstat. To neplatí samozřejmě jen o registrech, ale o celé řadě dalších elementů, např. o dynamických knihovnách. Smutným důsledkem, kromě principiálně nesmyslného „zanášení“ počítače, je pak postupné bobtnání a zpomalování systému.

Doufám, že vás mé staropanenské kázání neznechutilo :-) a slibuji, že pro dnešek je s ním již konec. Pojďme raději dále – k popisu třídy TRegistry.

Třída TRegistry

Třída TRegistry je zapouzdřením systémového registru společně se všemi operacemi, které s ním provádíme. Všechny tyto operace pracují s klíči (namátkou vytváření, otvírání, čtení, zápis), které jsou podklíčem tzv. kořenového klíče. Kořenový klíč je defaultně určen jako HKEY_CURRENT_USER, nicméně pomocí vlastnosti RootKey je možné zvolit jiný kořenový klíč. Abychom mohli pracovat s jakýmkoliv klíčem v registru, je nutné jej otevřít, např. pomocí metody OpenKey.

Tolik ke stručnému popisu používání třídy. Pojďme se nyní podívat na vlastnosti třídy TRegistry:

Vlastnost Význam
Access Pomocí vlastnosti Access můžeme stanovit přístupová práva, která budou použita pro otvírané klíče. Metoda OpenKey pro otevření klíče používá hodnotu vlastnosti Access. Tato vlastnost je inicializována při vytváření objektu konstruktorem Create, nicméně jje možné ji měnit. Nejčastěji používané hodnoty: KEY_ALL_ACCESS (plný přístup), KEY_READ (klíč bude možné pouze číst), KEY_WRITE (klíč bude možné pouze zapisovat). Existuje ještě řada dalších přístupových práv (např. KEY_CREATE_LINK pro vytváření symbolických odkazů apod., nicméně je asi zbytečné zde všechny jmenovat).
CurrentKey Vlastnost pouze pro čtení; udává aktuálně otevřený klíč registru. Veškeré operace, které objekt třídy TRegistr bude provádět, se vždy týkají pouze aktuálně otevřeného klíče.
CurrentPath Vlastnost pouze pro čtení, udává cestu v registrech k aktuálně otevřenému klíči.
LazyWrite Udává, jakým způsobem jsou klíče zapsány do registru v okamžiku zavolání CloseKey. Pokud LazyWrite = True, klíče jsou zapsány do registru v okamžiku zavolání CloseKEy, nicméně návrat z procedury CloseKey je proveden ihned, bez čekání na dokončení zápisu. Je-li naopak LazyWrite = False, vrátí CloseKey řízení až v okamžiku, kdy jsou všechny klíče zapsány do registru. Nastavení na false tak zajistí, že klíče budou určitě (fyzicky) aktualizovány předtím, než aplikace pokračuje v činnosti, na druhou stranu však může zpomalit běh aplikace. V okamžiku vytvoření objektu TRegistry je tato vlastnost inicializována na True.
RootKey Udává (a nastavuje) kořenový klíč. Standardně je nastaveno HKEY_CURRENT_USER.

Nyní nás čeká popis metod třídy TRegistry:

Metoda Popis
procedure CloseKey Zapíše aktuálně otevřený klíč do registry (všechny hodnoty) a uzavře klíč.
constructor Create(AAccess:LongWord) Vytvoří objekt třídy TRegistry. V parametru AAccess slouží k inicializaci vlastnosti Access. Tento parametr nemusí být uveden, v takovém případě je Access inicializován na KEY_ALL_ACCESS.
function CreateKey(const Key: String): Boolean Vytvoří nový klíč. Parametrem Key zadáme název a cestu, a to buď absolutně (začíná se zpětným lomítkem \ a uvedený klíč bude podklíčem kořenového klíče) nebo relativně (klíč bude podklíčem aktuálního klíče). Při úspěchu vrací funkce true, při chybě vznikne výjimka. CreateKey používá výhradně přístupová prává KEY_ALL_ACCESS (bez ohledu na hodnotu vlastnosti Access).
function DeleteKey(const Key: String): Boolean Vymaže zadaný klíč a všechna jeho data. Vrací true nebo false.
function DeleteValue(const Name: String): Boolean Vymaže zadaný údaj z aktuálního klíče.
function GetDataInfo(const ValueName: String; var Value: TRegDataInfo): Boolean Vrátí informace (především datový typ) zadaného údaje z aktuálního klíče. Předání probíhá přes strukturu typu TRegDataInfo, funkce sama vrací true/false podle výsledku (úspěchu) operace.
function GetDataSize(const ValueName: String): Integer Vrací velikost zadaného údaje v bytech. V případě chyby vrací -1, v případě řetězcových dat vrací počet znaků + 1 (ukončovací nula).
function GetKeyInfo(var Value: TRegKeyInfo): Boolean Vrací informace o aktuálním klíči a uloží je do struktury typu TRegKeyInfo.
procedure GetKeyNames(Strings: Tstrings) Vrátí názvy všech podklíčů aktuálního klíče a uloží je do následníka typu TStrings (což může být např. vlastnost Items komponenty ListBox nebo třeba vlastní objekt třídy TStringList apod).
procedure GetValueNames(Strings: TStrings) Vrátí názvy všech údajů uložených v aktuálním klíči. Uložení výsledku je stejné jako u metody GetKeyNames.
function HasSubKeys: Boolean Logická vlastnost; vrací True, pokud aktuální klíč má nějaké podklíče, jinak False.
function KeyExists(const Key: String): Boolean Zjistí, zda se v registru nalézá klíč zadaný parametrem.
function LoadKey(const Key, FileName: String): Boolean Vytvoří podklíč kořenového klíče a načte do něj údaje ze zadaného souboru. Tato metoda je určena především ke zjednodušení procesu vytváření a naplňování nových klíčů. Načítaný soubor musí být vytvořen předchozím voláním metody SaveKey (případně voláním funkce WinAPI RegSaveKey).
procedure MoveKey(const OldName, NewName: String; Delete: Boolean) Přesune existující klíč (OldName), jeho podklíče a údaje do nového umístění (NewName). Parametrem Delete určíme, má-li se po provedení operace starý klíč odstranit.
function OpenKey(const Key: String; CanCreate: Boolean): Boolean Nám již dobře známá metoda pro otevření klíče. Otevřený klíč se stane aktuálním klíčem. Pomocí parametru CanCreate říkáme, má-li se klíč vytvořit v případě, že neexistuje. Klíč je otevřen s přístupovými právy specifikovanými vlastností Access.
function OpenKeyReadOnly(const Key: String): Boolean Otevře zadaný klíč pouze pro čtení.
function RegistryConnect(const UNCName: String): Boolean Připojí se k registrům na jiném počítači. Parametr UNCName musí být ve formátu //computername.
procedure RenameValue(const OldName, NewName: String) Přejmenuje zadanou položku (OldName) v aktuálním klíči.
function SaveKey(const Key, FileName: String): Boolean Otevře zadaný klíč s přístupovým právem KEY_ALL_ACCESS a uloží jej (i se všemi podklíči a hodnotami) do zadaného souboru. Ten je pak např. možné použít metodou LoadKey..
function ValueExists(const Name: String): Boolean Zjistí, zda se v aktuálním klíči vyskytuje údaj zadaného jména.

Kromě metod uvedených v tabulce existuje ještě několik dalších. Dvě velké skupiny metod slouží ke čtení a k zápisu položek. Metody určené ke čtení:

  • ReadBinaryData
  • ReadBool
  • ReadCurrency
  • ReadDate
  • ReadDateTime
  • ReadFloat
  • ReadInteger
  • ReadString
  • ReadTime
Všechny mají prakticky tutéž syntaxi a způsob použití:

function ReadXXX(const Name: String): XXX;

Fungují tak, že z údaje zadaného parametrem Name (který musí ležet v aktuálním klíči) přečtou hodnotu datového typu XXX. Pokud má údaj jiný datový typ než XXX, je generována výjimka.

Metody pro zápis údajů do registru vypadají a fungují analogicky:

  • WriteBinaryData
  • WriteBool
  • WriteCurrency
  • WriteDate
  • WriteDateTime
  • WriteExpandString
  • WriteFloat
  • WriteInteger
  • WriteString
  • WriteTime

Opět si je můžeme popsat najednou, neboť jsou v maximální míře podobné:

procedure WriteXXX(const Name: String; Value: XXX);

Údaj opět musí ležet v aktuálním klíči. Pokud údaj zadaný parametrem Name existuje, je jeho hodnota přepsána. V opačném případě je založen nový údaj. Dojde-li k chybě, je generována výjimka.

Bezbolestný přechod z *.ini souborů na registr – třída TRegistryIniFile

Dejme tomu, že máme starší aplikaci, která používá soubory *.ini a hodláme přejít na používání systémového registru. Co si počít? Přepisovat celý kód? Dalším problémem souvisejícím s registry, jak jsme si uvedli, je přenositelnost aplikace.

Na oba problémy existuje jednoduchý lék: třída TRegistryIniFile. Pokud pouze chceme „překlopit“ aplikaci používající soubory *.ini na aplikaci používající registry, není nic snazšího než pouze nahradit ve zdrojovém kódu třídu TIniFile třídou TRegistryIniFile. Tato třída pracuje se systémovým registrem, ale umožňuje používat naprosto stejné metody jako při práci s inicializačními soubory. V registru pak vytvoří klíč, jehož název odpovídá názvu inicializačního souboru (včetně cesty). Jediné, co je nutné „ručně“ dodělat, je eventuální zkopírování údajů ze souboru *.ini do registru, protože to za nás Delphi samozřejmě neudělá.

Dalším problémem, který TRegistryIniFile řeší, je přenositelnost aplikací. Chceme-li vytvořit aplikaci, která bude používat registry, ale zároveň ji bude možné přeložit třeba v Linuxu, můžeme si opět pomocí s třídou TRegistryIniFile. Vzhledem k tomu, že TRegistryIniFile je potomkem třídy TCustomIniFile, stačí napsat aplikaci tak, aby používala datový typ TCustomIniFile a pomocí nějaké podmínky zajistit, aby ve Windows vytvořila objekt třídy TRegistryIniFile a v Linuxu objekt třídy TMemIniFile. Tento vytvořený objekt pak přiřadíme do objektu TCustomIniFile a nadále s ním pracujeme zcela shodně bez ohledu na systém, v němž hodláme provozovat své nechutné čáry.

Zjednodušená ukázka (neřešící třeba sekci Uses):

procedure test;
var IniSoub: TCustomIniFile;
begin
  if Windows=1 then
    IniSoub := TRegistryIniFile.Create(ChangeFileExt(Application.ExeName, `.INI`))
  else
    IniSoub := TMemIniFile.Create(ChangeFileExt(Application.ExeName, `.INI`));
... atd.

Nicméně i pro vývojáře, kterým nejde o přenositelnost, může být TRegistryIniFile vhodnou volbou. Důvodem je především fakt, že zvolíme-li tuto třídu, budeme moci pracovat se stejnými metodami jako u souborů *.ini, což může být jednodušší (odpadá nutnost pracovat s přístupovými právy, s klíči a s jejich otvíráním, atd.), ale údaje budou fyzicky uloženy v systémovém registru, což působí profesionálně a moderně.

Na závěr

Dnes jsme úspěšně uzavřeli téma ukládání aplikačních údajů v registrech. Myslím, že nyní si již každý dokáže zvolit cestu, která je mu nejbližší: třídu TIniFile nebo TMemIniFile pro práci s inicializačními soubory, třídu TRegistry pro ukládání v systémových registrech a třídu TRegistryIniFile pro ukládání v registrech „tvářící se“ jako ukládání do souborů *.ini.

Závěrem ještě jednou zdůrazním, že ukládání aplikačních informací je v současné době považováno za standard kultivovaných programů a že pokud nějaká aplikace zapomene do příštího spuštění uživatelská nastavení, může se s úspěšností bohužel rozloučit.

Diskuze (2) Další článek: Český Telecom akceptoval paušály na Internet

Témata článku: , , , , , , , , , , , , , , , , , , , , , , , , ,