Umíme to s Delphi: 106. díl – zdroje (resources) systému Windows

Jedním z důležitých aspektů systému Windows jsou tzv. zdroje (resources). Hledáte-li odpovědi na některé z otázek co jsou zdroje, k čemu slouží, jaké jsou jejich výhody a nedostatky, nebo jak je využívat v Delphi, pak je právě pro vás určen dnešní článek, který obsahuje podrobný úvod do problematiky zdrojů.
V dnešním článku otevřeme další zajímavé téma, kterému jsme se v seriálu dosud nevěnovali, na což mě před nedávnem upozornil jeden ze čtenářů emailem. Jedná se o zdroje systému Windows, nebo – chcete-li – tzv. resources. K čemu takové zdroje slouží, jak vznikají, a jakým způsobem je můžeme využít při programování v Delphi – to jsou všechno otázky, na něž se pokusí přinést odpovědi dnešní článek.

Soubor se zdroji – co to je?

Protože celý tento seriál je primárně zaměřen na práci s vývojovým nástrojem Delphi, budeme se i v oblasti zdrojů zabývat především tím, jaký mají vztah právě k Delphi. A jak je naším zvykem, začneme od úplného počátku. Podíváte-li se do kteréhokoliv adresáře, v němž je uložen nějaký projekt v Delphi, najdete tam celou řadu souborů, které většinou již dobře známe. Kromě zdrojových souborů <.pas), souboru projektu <.dpr), spustitelného souboru <.exe), zkompilovaných jednotek <.dcu), nejrůznějších dalších podpůrných a konfiguračních souborů <.dof, *.cfg) a záloh <.~??) spatříte ještě dva zajímavé druhy souborů:

  • soubor s popisem formuláře <.dfm)
  • a soubor se zdroji - *.res.

Proč tyto dvě skupiny souborů, které spolu zdánlivě nijak nesouvisí, uvádím oddělně a kladu na ně zvýšený důraz? Odpověď je prostá: jedná se právě o soubory související s náplní dnešního článku – o soubory zdrojů.

Obecně platí, že zdroje (za okamžik si konečně také prozradíme, co to vlastně zdroje jsou a co vše se v nich může skrývat) systému Windows jsou uchovávány a distribuovány v souborech s tradiční příponou *.res (vzniklé pochopitelně jako zkratka z anglického označení „resource“). V případě Delphi však vstupuje do hry ještě jeden formát souborů se zdroji, a tím je právě soubor popisující formulář - *.dfm. Přestože se v tomto bodě můžete setkat s různými interpretacemi, skutečnost je taková, že soubor *.dfm je jakýmsi speciálním případem souboru se zdroji.

Na tomto místě mohou pozorní čtenáři namítnout jednoduchý argument: když si v nějakém textovém editoru prohlédnu kterýkoliv soubor *.dfm, nic moudrého se z něho nedozvím, protože je uložen v binárním formátu. Zatímco prohlédnu-li si v tomtéž editoru soubor *.dfm, vidím dobře čitelný textový popis s informacemi a údaji o formuláři.

Tato námitka je správná: od Delphi verze 5 jsou soubory *.dfm ukládány v textovém formátu. To však neznamená, že bychom již nemohli hovořit o zdrojích nebo že bychom v nichsnad nemohli uchovávat např. nějaké obrázky (ikony apod.). Zkuste si schválně jednoduchou operaci: vytvořte v Delphi novou aplikaci a v Object Inspectoru změňte ikonu formuláře. Najděte na disku nějakou libovolnou ikonu (např. v systémovém adresáři jich jistě budete mít k dispozici dostatek), vložte ji do vlastnosti Icon, celý projekt uložte, a pak si prohlédnete obsah souboru Uni1.dfm (příp. jiný.dfm v závislosti na tom, jak jste jednotku pojmenovali). Spatříte zhruba následující text:

object Form1: TForm1
  Left = 192
  Top = 110
  Width = 544
  Height = 375
  Caption = `Form1`
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = `MS Sans Serif`
  Font.Style = []
  Icon.Data = {
    0000010006002020100000000000E80200006600000010101000000000002801
    00004E030000303010000000000068060000760400002020000000000000A808
    0000DE0A00003030000000000000A80E00008613000010100000000000006805
    00002E2200002800000020000000400000000100040000000000000200000000
    0000000000000000000000000000000000000000800000800000008080008000

... vypuštěno přes 20kB dalšího textu :-)

    0000000222204444444444440000000000000022222200C4C444444400000000
    0000002A2A22204C4C44440020000000000002A2A2A220C4C4C4C02202000000
    0000022A2A2A222C4C4002222000000000002AAAA2A22224C422A2A222000000
    00002AFAAA2222CCCC2A2A2A2220000000002FAFAA2CCCCCCC27A2A222000000
    0A00000000000A0A00000A0AF90A020A0A000000000000000000000A0A0A0A0A
    00000000000000000000000000000000000000000000FF8FCC00FE030000FC01
    3300FC006600F8009900F800CC00C000FF0030000000C001330030016600C003
    9900300FCC00C01FFF00301F0000F83F3300FFFF6600}
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
end

Jinak řečeno – v souboru *.dfm je možno (i přes jeho textový formát) uchovávat i binární data třeba o ikonách.

Jinak ovšem platí, že zdroje jsou obvykle uchovávány v binárních souborech, typicky s příponami *.res. A těmito soubory se také budeme v dalším textu článku obvykle zabývat.

Tolik na samotný úvod, který byl uveden především proto, abychom se sjednotili v chápání toho, co lze v kontextu Delphi chápat pod pojmem „soubor s resources“. Nyní se můžeme směle vrhnout do popisu a definice samotných zdrojů.

Zdroje Windows

Co vlastně rozumíme pod tolikrát opakovaným pojmem zdroje? Rozeberme si nejprve reálnou situaci: jak vlastně vypadá běžný program pod Windows?

Když celý problém zjednodušíme až na samotnou hranici únosnosti a budeme se zaměřovat ryze na problematiku zdrojů, můžeme prohlásit, že programy ve Windows se skládají ze dvou (relativně nezávislých) částí:

  • spustitelný program, který obsahuje programový kód a nejrůznější data (proměnné, jejich hodnoty, konstanty apod.)
  • zdroje.

Pod zdroji prozatím budeme rozumět nejrůznější grafické „maličkost“, jako jsou zmíněné ikony, dále třeba bitmapy apod. Za okamžik tuto definici rozšíříme a uvedeme si kompletní výčet toho, co ve zdrojích může být.

Nicméně pokračujme dále v naší úvaze: jelikož jsme řekli, že program a zdroje jsou relativně nezávislé (přičemž to slovo relativně bych si dovolil silně zdůraznit, protože určitá závislost je samozřejmě přítomna a konec konců lze souhlasit i s názorem, že je poměrně silná), je možné využívat zdroje například pro realizaci různých grafických vzhledů aplikace, případně třeba různých jazykových verzí.

Jak je to možné? Odpověď bych si opět dovolil podložit praktickou ukázkou situace v Delphi. Prohlédnete-li si obsah kteréhokoliv zdrojového souboru (souvisejícího s nějakým formuláře), například tradiční Unit1.pas, spatříte v jeho úvodu pravděpodobně zhruba toto:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    RadioButton1: TRadioButton;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

... pokračování zdrojového souboru

Proč na to upozorňuji? Zejména z toho důvodu, že bezprostředně za klíčovým slovem implementation najdete podivný příkaz, který je ve výše uvedeném výpisu vyznačen tučně:

{$R *.DFM}

Tuto řádku není přesné označovat jako „příkaz“: jedná se totiž o tzv. direktivu překladače, tedy o jakýsi „pseudopříkaz“, který ovšem neovlivňuje běh programu (naní překládán do nějaké instrukce), ale slouží k ovládání překladače s říká cosi ve smyslu: „můj milý, drahý překladači, v aktuálním adresáři najdi soubor, který bude mít stejné jméno jako aktuální soubor a bude mít příponu *.dfm. V tomto souboru totiž nalezneš zdroje týkající se tohoto souboru (modulu, jednotky).

Analogicky si zkuste prohlédnout zdrojový soubor celého projektu (tedy soubor *.dprú. Také v něm najdete podobnou direktivu $R, ale tentokráte mírně modifikovaného tvaru:

program Project1;

uses
  Forms,
  Unit1 in `Unit1.pas` {Form1};

{$R *.RES}

Jinak řečeno – také zde direktivou překladači sdělujeme, že v aktuálním adresáři by měl najít soubor jmenující se stejně jako daný projekt a mající příponu *.res – a že v tomto souboru jsou umístěny všechny zdroje pro daný projekt (samozřejmě s výjimkou zdrojů týkajících se formulářů, neboť ty jsou umístěny – jak jsme si uvedli v úvodu, v souborech *.dfm).

Na tomto místě bych upozornil ještě na jednu lehkou „zradu“: zatímco v souborových systémech obvykle chápeme znak hvězdičky <) jako náhradu za „cokoliv“, ve výše uvedených direktivách $R znamená hvězdička „cokoli, co se jmenuje stejně jako aktuální soubor“. Pokud tedy uvnitř souboru Unit1.pas uvedeme direktivu {$R *.dfm}, bude vzat v úvahu pouze soubor Unit1.dfm.

Nyní jsme poznali direktivu $R a řekli jsme si, že tato direktiva zřejmě nějak přiměje překladač, aby „našel“, případně „vzal v potaz“ soubor s uvedených názvem <.dfm, *.res). Co toto „najití“, případně „vzetí v potaz“ prakticky znamená? Nic jiného, než že překladač příslušný soubor vezme a přilinkuje jej k výslednému souboru *.exe. Takže máme-li soubor se zdrojovým kódem a soubor se zdroji, pomocí direktivy $R tyto dva soubory „spojíme“ tak, že překladač z nich vytvoří jeden výsledný spustitelný soubor *.exe.

Na tomto místě už asi začíná být zřejmé, jak celé manévry se zdroji vlastně prakticky vypadají, jaké mají výhody a nevýhody. Situace je taková, že vytvoříme zdrojový kód a vytvoříme soubor se zdroji (později si ukážeme, jak). Pomocí direktivy $R kterou samozřejmě můžeme uvést v jednom modulu opakovaně) nařídíme překladači, aby tyto soubory spojil do výsledného spustitelného souboru *.exe.

Rozhodneme-li se, že se nám nelíbí ikona aplikace (kterou máme samozřejmě umístěnu v souboru se zdroji), není nic jednoduššího, než vytvořit nový soubor se zdroji, v němž místo staré a ošklivé ikony použijeme novou a krásnou. Tento nový soubor se zdroji pak připojíme k aplikaci a bez nutnosti měnit zdrojový kód tak teoreticky dát aplikaci zbrusu nový kabát (nebo zmíněnou jazykovou mutaci).

Většina čtenářů už na tomto místě určitě pochopila, proč jsem v úvodu článku nejprve hovořil o „relativní nezávislosti“ zdrojů a kódu a vzápětí vše popřel tvrzením, že tato relativní nezávislosti může být z určitého úhlu pohledu chápána jako poměrně silná.

Důvodem totiž je, že abychom mohli použít nový soubor se zdroji, musíme aplikaci znovu přeložit. Nestačí nahrát do adresáře nový, lepší soubor s novými, lepšími zdroji (byť by měl stejný název jako starý, hnusný): je třeba si uvědomit, že „svázání“ aplikace s jejími zdroji se neprovádí za běhu programu, ale již v době překladu.

Jinak řečeno: za běhu aplikace je už úplně jedno, kolik souborů s příponou *.res se v aplikačním (aktuálním) adresáři vyskytuje, jaké mají názvy a jak krásné obsahují zdroje. Když soubor *.res s aplikací vůbec nedodáte, nic se nestane (ostatně jste si již určitě sami vyzkoušeli, že po vytvoření aplikace v Delphi stačí přenášet jen jediný, spustitelný soubor *.exe, a to i přesto, že každá aplikace v Delphi obsahuje nějaký soubor se zdroji). Zdroje ze souborů *.res a *.dfm jsou přilinkovány do spustitelného souboru *.exe už při překladu, takže pokud chceme nahradit jeden soubor se zdroji druhým, je nutná rekompilace.

Tím se tedy – doufám – vysvětluje schizofrenní chápání nezávislosti:

  • ano, protože lze změnit aplikaci bez zásahu do programového kódu a protože lze vyvíjet zdrojový kód a zdroje nezávisle na sobě;
  • ne, protože ke spojení aplikace s jejími zdroji je nutný opětovný překlad.

Ponechám již na vážených čtenářích, aby posoudili, zda jim tento způsob závislosti/nezávislosti vyhovuje nebo nikoliv. Pokud se ptáte, existuje-li plně nezávislá alternativa, která by například umožňovala změnit nějakou ikonu za běhu programu bez nutnosti rekompilace projektu, odpovídám, že je možné ukládat ikony například do dynamicky linkovaných knihoven <.dll) a z nich načítat za běhu aplikace přesně to, co zrovna potřebujeme. To je však zase docela jiná problematika, která se zdroji souvisí asi jako matematická analýza se včelařstvím.

Několik dalších informací

Ještě předtím, než se pro dnešek rozloučíme, si dovolím poznamenat několik dalších informací souvisejících se zdroji, které by pro vás mohly být užitečné.

  • Při práci v Delphi nejsou zdroje Windows tak potřebné jako v dávných dobách před Delphi. V Delphi se bez zdrojů lze docela dobře obejít, proto také nejsou nijak extrémně dokumentovány (a proto tak dlouho chyběly v našem seriálu:-)). Před Delphi se obvykle ve zdrojích uchovávaly všechny grafické elementy aplikací – tlačítka, menu apod.). V Delphi jsou tyto grafické prvky již přímou součástí integrovaného vývojového prostředí a pracujeme s nimi zcela běžně, takže je nemusíme uchovávat v resources. Avšak i v Delphi můžeme samozřejmě zdroje využít.
  • Zdroje můžeme modifikovat nejen uvnitř souboru *.res, ale dokonce i v případě, že jsou již součástí výsledného spustitelného *.exe souboru. Slouží k tomu nejrůznější editory zdrojů, jeden z těch nejjednodušších je dodáván i přímo s Delphi a my se jím budeme zabývat v následujícím článku. Jedná se o známý Image Editor, který jsme již několikráte použili i v předchozích částech seriálu. Jeho nevýhodou je, že nemá úplně všechny možnosti, které bychom občas potřebovali, proto je někdy nutné poohlédnout se po některém dalším nástroji (např. Resource Workshop).

V dnešním článku již nezbyl prostor pro slíbené shrnutí a podrobnější popis všech fragmentů, které ve zdrojích můžeme uchovávat. V příštím článku tedy tímto souhrnem začneme a následně se budeme zabývat tím, jak se zdroji prakticky pracovat v Delphi a jak využívat jejich největší výhody – nezávislé editace zdrojů a programu.

Na závěr

Dnešní článek se pokusil přinést komplexní úvod do problematiky zdrojů a vysvětlit jejich princip a filozofii i těm, kteří se s nimi dosud nesetkali. Věřím, že jste v článku nalezli odpovědi na většinu teoretických otázek, které vás o zdrojích napadaly. Praktické otázky ovšem zůstaly prozatím nezodpovězeny: jejich rozboru bude věnován následující díl našeho – a vašeho – seriálu.

Diskuze (3) Další článek: Samolepka přetaktuje Athlon XP

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