V minulém dílu povídání o prostředí .NET Framework jsme se zabývali tím, čemu se anglicky říká assembly; v české literatuře uvidíte vedle špatně skloňovatelného „assembly“ termíny „komplet“, „seskupení“ nebo „sestavení“; já zde budu používat seskupení.
Seskupení existují ve dvou variantách – soukromá a sdílená. Nejprve se budeme zabývat soukromými seskupeními. Ta jsou součástí jediné aplikace a typicky se instalují do domovského adresáře aplikace. (O dalších možných umístěních si povíme později.)
Poznamenejme, že všechny příklady v tomto článku budou v jazyce C# a budeme je překládat pomocí samostatného překladače csc.exe , který je součástí instalace SDK pro platformu .NET. Tento překladač totiž poskytuje více možností než různá vývojová prostředí.
Čtenářům, které iritují háčky a čárky v identifikátorech, se omlouvám, ale jazyk C# a mezijazyk IL je podporují, a proto je používám. Když už nic jiného, výklad je pro mne pak podstatně snazší.
Seskupení vznikne – jaké překvapení – překladem zdrojového kódu. Pochopitelně tomu slouží překladač csc.exe , za jistých okolností budeme ale využívat i linker al.exe a některé z dalších nástrojů, jejichž přehled najdete v minulém dílu tohoto článku.
Seskupení je, jak už víme, buď spustitelný soubor (. exe ) nebo dynamická knihovna (. dll ). Typicky je seskupení tvořeno jediným souborem, který obsahuje několik datových typů (tedy tříd – nezapomínejme, že seskupení obsahuje kód v mezijazyku IL, který je čistě objektový). Později uvidíme, že se může skládat i z několika souborů.
Začneme tím, že si ukážeme jednoduchý příklad. Máme dva zdrojové soubory v jazyce C#:
// Soubor Počty.cs
using System;public class Počty
{
/// <summary>
/// Počítá faktoriál daného celého čísla
/// </summary>
public static int f(int n)
{if (n<0)
throw new ArgumentException("Záporný parametr");
int s = 1;
while(n > 1)s *= n--;
return s;}
}
// Soubor Program.cs
using System;class Program
{public static void Main()
{Console.WriteLine("Zadej číslo: ");
string s = Console.ReadLine();
int n = System.Int32.Parse(s);
System.Console.WriteLine("Faktoriál je " + Počty.f(n));}
}
Jestliže tento program přeložíme příkazem
csc Program.cs Počty.cs
vytvoří se jediné seskupení obsahující třídy z obou zdrojových souborů a tzv. manifest. Toto seskupení se bude jmenovat Program.exe , jméno výsledného seskupení lze ale změnit přepínačem /out: v příkazové řádce překladače. (Připomeňme si, že manifest je soubor obsahující metadata o celém seskupení, tj. o tom, jaké typy obsahuje, s jakými jinými seskupeními spolupracuje atd.)
Podívejme se nyní na vytvořené seskupení podrobněji. Použijeme k tomu program ildasm.exe , což je disasembler s grafickým rozhraním. Po spuštění uvidíte okno jako na obr. 1.
Obr. 1 Program ILDASM ukazuje, že naše seskupení obsahuje manifest a dvě třídy
Dvojklik na položku Manifest otevře další okno, v němž můžeme zjistit některé údaje o tomto seskupení.
Obr. 2 Základní informace o seskupení
První položka říká, že naše seskupení obsahuje odkaz na externí seskupení mscorlib – to je základní knihovna prostředí .NET. Dále si zde můžeme přečíst číslo požadované verze a token veřejného klíče – o tom budeme hovořit později. (Všimněte si, že závislé seskupení obsahujue údaje o seskupení, na němž závisí – jméno, číslo verze a jakýsi další údaj. Pokud se tyto údaje nebudou shodovat, prostředí .NET požadované seskupení nenajde.)
Následuje popis aktuálního seskupení jménem Program . V něm najdeme číslo verze 0.0.0.0 (jde o soukromé seskupení, které vlastně nemá číslo verze) a odkaz na hešovací algoritmus; o tom se také zmíníme později.
Vraťme se do základního okna programu ILDASM. Kliknutím na křížek u jména třídy příslišný uzel stromu rozvineme a dostaneme přehled metod. Budou tam i konstruktory, které si překladač vytvořil sám. Dvojklikem na ikonu u jména metody otevřeme další okno, v němž uvidíme překlad metody do IL (obr. 3).
Obr. 3 Překlad metody do IL
Vraťme se opět k problému, jak vytvořit soukromé seskupení. Příkazem
csc /t:library Počty.cs
vytvoříme seskupení Počty.dll (dynamickou knihovnu pro .NET) obsahující pouze třídu Počty a manifest.
Příkazem
csc /r:Počty.dll Program.cs
vytvoříme seskupení obsahující třídu Program a odkazující na danou dynamickou knihovnu. (Přepínač /r: uvádí odkaz – referenci – na jiné seskupení.) Pokud umístíme dynamickou knihovnu do téhož adresáře jako spustitelný program, bude vše v pořádku.
Nyní tedy máme program, který dělá totéž co předchozí verze, ale skládá se ze dvou seskupení. Tím jsme získali cosi navíc – dynamickou knihovnu lze použít i v jiných programech. Cena je také zřejmá: Dynamickou knihovnu bude třeba zavést do paměti za běhu programu, a to může znamenat zpomalení programu.
Vraťme se ale k seskupením. Příkazem
csc /t:module Počty.cs
vytvoříme modul (obdobu souboru . obj pro platformu .NET). Modul nepředstavuje seskupení, obsahuje pouze IL, nikoli však manifest. Modul lze připojit k seskupení příkazem:
csc /addmodule:Počty.netmodule Program.cs
POZOR: Soubor Počty.netmodule se sice stane součástí seskupení Program.exe , zůstane však samostatný, takže seskupení se bude skládat ze souborů Program.exe a Počty.netmodule . Soubor Program.exe bude obsahovat třídu Program , manifest a odkaz na soubor Počty.netmodule .
Poznamenejme, že poněkud dlouhá přípona .netmodule není povinná; pomocí parametru /out: v příkazové řádce lze předepsat jinou příponu. Používá se např. .net . My však v tomto článku zůstaneme u přípony .netmodule .
Příkazem
csc /addmodule:počty.netmodule /t:module Program.cs
vytvoříme modul i ze souboru Program.cs; tento modul bude obsahovat třídu Program a manifest. Seskupení z těchto dvou modulů vytvoříme pomocí linkeru al.exe příkazem
al /t:exe /out:prog.exe /main:Program.Main Program.netmodule Počty.netmodule
Výsledkem bude seskupení složené ze tří souborů: Počty.netmodule , Program.netmodule a prog.exe . Soubor .exe bude obsahovat pouze manifest. Poznamenejme, že volbu /out , specifikující jméno spustitelného souboru, stejně jako volbu /main , udávající jméno vstupního bodu programu, musíme uvést.
Už víme, že pokud jedno seskupení (např. spustitelný soubor) odkazuje na jiné seskupení (např. dynamickou knihovnu), hledá ho prostředí .NET nejprve v domovském adresáři aplikace. Pokud ho tam nenajde, bude ho hledat v podadresáři domovského adresáře aplikace, který se bude jmenovat stejně jako hledané seskupení.
Vrátíme se k programu z předchozího oddílu a přeložíme ho jako dvojici seskupení, Program.exe a Počty.dll . To už umíme.
Umístíme-li oba soubory do téhož adresáře, např. do C:\Pokus , bude vše v pořádku, program spustíme příkazem
Pokus
a poběží bez problémů (samozřejmě pokud k němu operační systém najde cestu).
Stejně dobře ale můžeme soubor Počty . dll umístit do adresáře C:\Pokus\Počty . Ani tentokrát nevzniknou problémy.
Pokud nám takovéto umístění nevyhovuje, můžeme použít konfigurační soubor aplikace. To je XML soubor se jménem shodným se jménem „spouštěcího“ seskupení (tedy souboru, se třídou obsahující vstupní bod aplikace) a s příponou .config . V něm lze specifikovat mimo jiné i jméno podadresáře závislého seskupení, a to v prvku <probing> v parametru privatePath . Prvek <probing> musí být vnořen do prvku <assemblyBinding> a ten je vnořen do prvku <runtime> v prvku <configuration> , který je na nejvyšší úrovni.
Parametr privatePath může obsahovat i několik adresářů oddělených středníkem. Vždy to budou jména podadresářů domovského adresáře aplikace.
Vezměme opět seskupení Program.exe a Počty.dll . Seskupení Počty.dll chceme umístit do podadresáře C:\Pokus\Hokus . Vytvoříme proto konfigurační soubor Program.exe.config s následujícím obsahem:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="Hokus" />
</assemblyBinding>
</runtime>
</configuration>
Pozor: Na konci prvku <assemblyBinding> v řetězci asm.v1 je číslice 1 (jedna), nikoli písmeno l („el“).
Soubor Program.exe.config uložíme do domovského adresáře aplikace, spolu se souborem Program.exe (v našem případě tedy do C:\Pokus ). Soubor Počty.dll umístíme do podadresáře C:\Pokus\Hokus . Program bude opět fungovat.
Jméno konfiguračního souboru musí obsahovat celé jméno spustitelného souboru, tj. včetně přípony exe .
Soubor Počty.dll můžeme také umístit do podadresáře C:\Pokus\Hokus\Počty .
Jak se tedy hledá? Předchozí informace lze shrnout do následujících pravidel. Přitom je třeba vědět, že v metadatech seskupení je uvedeno pouze samotné jméno seskupení, na něž odkazuje. Toto jméno neobsahuje cestu ani příponu.
Obsahuje-li tedy spouštěný program odkaz na jiné seskupení jménem Počty , budou se za běhu prohledávat adresáře v následujícím pořadí a bude se v nich hledat soubor Počty.dll :
• Domovský adresář aplikace, např. C:\Pokus .
• Podadresář se jménem shodným se jménem seskupení, např. C:\Pokus\Počty .
• Podadresář odpovídající první z cest uvedených v privatePath , tedy např. C:\Pokus\Hokus .
• Jeho podadresář se jménem shodným se jménem seskupení, např. C:\Pokus\Hokus\Počty .
• Podadresář odpovídající druhé z cest uvedených v privatePath , pokud je zadána,
• atd.
Pokud se ani v jednom z těchto adresářů nenajde soubor Počty.dll , prohledá je .NET ještě jednou ve stejném pořadí, bude ovšem hledat soubor Počty.exe .
Je jasné, že pokud není k dispozici konfigurační soubor, připadají v úvahu pouze první dvě možnosti; opět se bude nejprve hledat dynamická knihovna a pak soubor .exe .
Poznamenejme, že při překladu se odkazované seskupení hledá
• v pracovním adresáři,
• v adresáři, který obsahuje CLR a který používá překladač. V tomto adresáři je uložena knihovna MSCorLib.dll . Jde např. o C:\WinNT\Microsoft.NET\Framework\v1.1.4322 ,
• v jakémkoli adresáři určeném volbou /lib překladače,
• v jakémkoli adresáři určeném proměnnou LIB prostředí.
Pravidla se aplikují v uvedeném pořadí. (Knihovní seskupení dodávané Microsoftem jsou instalována dvakrát, a to v adresáři uvedeném v bodě (2), kde je využívá překladač, a v tzv. GAC, o níž budeme hovořit dále.)
Sdílené seskupení využívá několik aplikací. Instaluje se typicky do globální mezipaměti seskupeni (Global Assembly Cache, GAC).
Sdílené seskupení musí mít tzv. silné (sdílené) jméno. Jeho základem je dvojice klíčů, veřejný a soukromý. Ty vytvoříme pomocným programem sn.exe , který je součástí instalace SDK pro .NET.
Dále musíme specifikovat číslo verze a případě „kulturu“ (jazykovou mutaci). Soubor s klíči a další údaje zadáváme pomocí atributů pro seskupení. Podstatné pro nás budou především atributy System.Reflection.AssemblyVersion a System.Reflection.AssemblyKeyFile .
Vytvoříme sdílené seskupení ze souboru Počty.cs .
Nejprve vytvoříme nový pár klíčů příkazem
sn -k klíč.snk
Zde přepínač –k říká, že chceme vytvořit novou dvojici klíčů, a klíč.snk je jméno souboru, do kterého bude tato dvojice uložena. (Opět je nezbytné, aby operační systém našel cestu k souboru sn.exe .)
Dále upravíme zdrojový text souboru Počty.cs takto:
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: AssemblyDelaySign(false)]
[assembly: AssemblyKeyFile("klíč.snk")]
[assembly: AssemblyVersion("1.0.O.0")]
public class Počty
{
public static int f(int n)
{
if (n<0) throw new ArgumentException("Záporný parametr");
int s = 1;
while(n > 1)s *= n--;
return s;
}
}
Pak tento soubor přeložíme obvyklým způsobem. Vzniklé seskupení lze používat i nadále jako soukromé, lze ho ale také instalovat do GAC. Nejjednodušším způsobem, jak to udělat, je přetáhnout ho myší pomocí programu Windows Explorer do podadresáře assembly v adresáři s instalací Windows (tedy např. do C:\Winnt\assembly ). Nejjednodušší způsob odinstalování je prostě ho (opět pomocí Windows Exploreru) smazat.
Pozor, nejde o prosté kopírování, takže jiný program – např. Windows Commander nebo příkaz operačního systému COPY – zde nemůže Explorer nahradit . Adresář assembly má totiž poměrně složitou vnitřní strukturu, kterou Explorer nezobrazuje. Obsahuje podadresáře pro různé verze, různé jazykové mutace atd. téhož seskupení. Kromě toho, pokud se seskupení skládá z několika různých souborů (např. ze souboru s manifestem a ze souborů s jednotlivými moduly), postará se explorer o překopírování všech částí seskupení, i když přetáhneme pouze část obsahující manifest.
Lze ovšem využít také utilitu gacutil.exe . Ta se hodí zejména při automatické instalaci.
Sdílené seskupení je digitálně podepsáno algoritmem podobným šifrování s veřejným klíčem. Veřejný klíč je součástí tohoto seskupení a mohl by i sloužit jako součást jeho identifikace. Protože je ale velice dlouhý, používá se místo něj tzv. token veřejného klíče (public key token). To je 128bitová hodnota, která z veřejného klíče vznikne použitím jistého hešovacího algoritmu a je součástí identifikace seskupení.
Jestliže nechceme zadávat atributy, tj. jestliže chceme použít původní zdrojový text programu,
using System;
public class Počty
{
public static int f(int n)
{
if (n<0) throw new ArgumentException("Záporný parametr");
int s = 1;
while(n > 1)s *= n--;
return s;
}
}
postupujeme takto:
Nejprve vytvoříme modul příkazem
csc /t:module Počty.cs
Pak sestavíme dynamickou knihovnu příkazem
al /out:Počty.dll /t:library /v:1.0.0.0 /keyf:klíč.snk Počty.netmodule
I když se výsledné seskupení skládá ze dvou souborů ( Počty.dll a Počty.netmodule ), při instalaci přetáhneme myší do adresáře assembly pouze soubor Počty.dll . Explorer se postará o ostatní sám.
Čísla verzí sdílených seskupení mají v .NET strukturu
Hlavní.vedlejší.sestavení.revize
Jestliže se liší hlavní nebo vedlejší číslo revize, pokládá se sestavení vždy za nekompatibilní s požadovaným. To ale lze obejít pomocí konfiguračních souborů. (Tak praví dokumentace. Podle mých zkušeností pokládá prostředí .NET za nekompatibilní verze, které se liší v kterémkoli c čísel.)
Přeložíme knihovnu Počty znovu, ovšem tentokrát s číslem verze 1.1.0.0, a nahradíme původní soubor novým. Jiné změny nebudou pro účely tohoto příkladu potřeba. (Pokud máme soubor v GAC, musíme ho odinstalovat, neboť jinak bychom měli dvě verze vedle sebe. Můžeme ale knihovnu ponechat v adresáři aplikace, pro naše účely to stačí. Nesmíme ale zapomenout původní verzi odstranit z GAC.)
Jestliže nyní aplikaci spustíme, dostaneme poměrně obsáhlé chybové hlášení, které bude končit sdělením
Comparing the assembly name resulted in the mismatch: Minor Version
což znamená, že nalezené seskupení se liší od požadovaného ve vedlejším čísle verze. K tomu, aby se program dal spustit, stačí vytvořit konfigurační soubor Program.exe.config s následujícím obsahem:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Počty" publicKeyToken="bf97be80f07ba3bd"
/>
< oldVersion="1.0.0.0" newVersion="1.1.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
Poznamenejme, že hodnota v atributu publicKeyToken se bude lišit. Zjistíme ji např. pomocí programu ildasm.exe z odkazu na sestavení Počty v sestavení Program .
Element bindingRedirect udává starou a novou požadovanou verzi. Atribut oldVersion smí udávat rozmezí, např. oldVersion="1.0.0.0-1.0.99.99" .
Konfigurační soubor aplikace lze vytvořit pomocí nástrojů platformy .NET. Ve Windows 2000 zvolíme z nabídky Start položku Control Panel a v něm položku Administrative Tools . (V jiných verzích Windows se přístup k administrativním nástrojům může lišit.) Zde zvolíme Microsoft .NET Framework configuration . V nabídce Tasks vybereme Manage Individual Applications, Add an Application to Configure a v dialogovém okně vyhledáme naši aplikaci.
V podřízených položkách pak zvolíme Configured Assemblies , Configure an Assembly , Choose an assembly from the list a na kartě Binding Policy nastavíme požadovanou novou verzi. Příslušný průvodce automaticky vytvoří nebo upraví konfigurační soubor aplikace.
Pod označením reflexe (reflection) se skrývají nástroje pro pokročilejší práci s datovými typy. Jde např. o možnost zjistit podrobné informace o datovém typu, který v době psaní programu neznáme, vytvořit jeho instanci, volat jeho metody atd.
Většina nástrojů pro reflexi je v prostoru jmen System.Reflection . Nástroje pro vytváření nových datových typů za běhu programu najdeme v prostoru jmen System.Reflection.Emit .
Reflexe je v C# (a ve všech jazycích pro platformu .NET, neboť všechny se opírají o stejné knihovny) založena především na třídě System.Type a na několika dalších pomocných třídách, jež obsahují informace o složkách datových typů ( MemberInfo , FieldInfo , MethodInfo , ConstructorInfo atd.).
Mechanizmus reflexe umožňuje umět pracovat nejen s typy, ale i se seskupeními, moduly a složkami. Hierarchicky nejvyšší je aplikační doména, představovaná třídou System.AppDomain . Za ní následuje seskupení, představované třídou System.Reflection.Assembly , a modul, představovaný třídou System.Reflection.Module . O třídách, reprezentujících datový typ ( System.Type ) a složky ( System.Reflection.MemberInfo , System.Reflection . PropertyInfo a dalších) jsme se již zmiňovali.
Poznamenejme, že v předchozím odstavci slovo „hierarchie“ neoznačuje hierarchii ve smyslu dědění, ale hierarchii ve smyslu vztahu celek – část.
Vztahy (tentokrát z hlediska dědění) mezi nejčastěji používanými třídami pro reflexi ukazuje obr. 4.
Obr. 4 Některé z tříd používaných při reflexi. Kde není vyznačen prostor jmen,
rozumí se System.Rexlection
Máme k dispozici dynamickou knihovnu, jež obsahuje pouze následující třídu:
// Přeložíme příkazem csc /t:library testik.cs
public class Testík
{
static Testík() // Statický konstruktor
{
System.Console.WriteLine("Testík se hlásí");
}
int i;
public Testík(int y){ i = y;} // Konstruktor
public void Vypiš() // Volaná metoda
{
System.Console.WriteLine("i = {0}", i);
}
}
Chceme napsat program, který jako parametr příkazové řádky dostane znakový řetězec obsahující jméno této dynamické knihovny. Jediné další informace, které bude mít náš program k dispozici, jsou, že
• seskupení obsahuje deklaraci jediného datového typu, a to třídy,
• tato třída má konstruktor s jedním parametrem typu int a
• obsahuje veřejně přístupnou metodu bez parametrů void Vypiš() .
V programu vypí?eme informace o slo?kách této neznámé toídy, vytvooíme instanci této toídy a zavoláme její metodu Vypiš() .
Postup bude následující: Nejprve musíme v programu vytvooit instanci toídy Assembly , jež bude reprezentovat seskupení obsahující neznámou třídu. K tomu použijeme statickou metodu Assembly.LoadFrom() , jejímž parametrem je znakový řetězec představující jméno souboru s tímto seskupením.
Program bude pochopitelně začínat direktivami
using System;
using System.Reflection;
Je-li název souboru obsažen v řetězci s , získáme odkaz na odpovídající seskupení příkazem
Assembly a = Assembly.LoadFrom(s);
Pokud se tato operace nepodaří, může vzniknout výjimka typu ArgumentNullException , FileNotFoundException , SecurityException a ještě některé další, proto bude třeba tuto operaci – spolu s následujícími – uzavřít do bloku try .
Seznam typů, definovaných v tomto seskupení, získáme pomocí metody GetTypes() třídy Assembly , jež vrací odkaz na pole typu Type :
Type[] t = a.GetTypes();
Protože víme, že toto seskupení obsahuje jediný typ, nemusíme se zabývat analýzou vráceného pole a může dále použít přímo jeho nultý prvek.
Dále vypíšeme informace o složkách této třídy. Tyto informace získáme pomocí metody GetMembers() , jež vrátí odkaz na pole s informacemi o složkách:
MemberInfo[] mip = t[0].GetMembers();
foreach(MemberInfo m in mip) Console.WriteLine(m);
Naším dalším úkolem je vytvořit instanci tohoto typu. K tomu použijeme statickou metodu System.Reflection.Activator.CreateInstance() . Jejím prvním parametrem je instance třídy Type , reprezentující datový typ, jehož instanci chceme vytvořit, a druhým je pole typu object[] , obsahující parametry konstruktoru. Tato metoda vrátí odkaz na object :
object obj = Activator.CreateInstance(t[0], new object[]{1});
Nakonec chceme zavolat metodu Vypiš() . Instanci obj nelze přetypovat, protože v době překladu neznáme jméno typu, o který jde. Proto musíme pomocí třídy Type získat instanci třídy MethodInfo popisující tuto metodu,
MethodInfo mti = t[0].GetMethod("Vypiš");
a zavolat ji prostřednictvím metody Invoke() :
mti.Invoke(obj, null);
Druhý parametr představuje odkaz na pole typu object obsahující parametry metody. Protože naše metoda nemá žádné parametry, použijeme hodnotu null , jež představuje „ukazatel nikam“. Program p řeložíme ho příkazem
csc Program.cs
Všimněte si, že zde neuvádíme odkaz na dynamickou knihovnu, s níž pracujeme. Není to nutné, neboť náš program neobsahuje žádný odkaz na třídu Testík .
Tento úvod do práce se seskupeními samozřejmě není vyčerpávající, alespoň pokud jde o obsah. Nabízí ale alespoň základní informace o tom, jak seskupení vytvořit a jak je používat. Nehovořili jsme např. o problematice jazykových mutací („kultury“), o možnosti odložit podpis seskupení (kdy certifikovaná autorita potvrdí platnost seskupení po dokončení vývoje) atd. Další informace můžete najít například v knihách z následujícího seznamu.
• Jeffrey Richter: Applied Microsoft .NET Framework Programmig . Microsoft Press 2002.
ISBN 0-7356-1422-9. (Česky: .NET Framework – programování aplikací, Grada Publishing 2002, ISBN 80-247-0450-1.)
Andrew Troelsen: C# and the .NET Framework Platform. Apress, 2002. ISBN 1-893115-59-3.
• International standard ISO/IEC 23270:2003 . Programming languages – C#.
Microsoft C# Language Specifications . Microsoft Press, Redmond 2001. ISBN 0-7356-1448-2.
J. Gough: Compiling for the . NET Common Language Runtime . Prentice Hall, 2002,
ISBN 0-13-062296-6.
• Simon Robinson, K. Scott Allen, Ollie Cornes, Jay Glynn, Zach Greenvoss, Burton Harvey, Christian Nagel, Morgan Skinner, Karli Watson: C# Programujeme profesionálně. Computer Press, Praha 2003. ISBN: 80-251-0085-5
D. Kačmář: Programujeme .NET aplikace. Computer Press, Praha 2001. ISBN 80-7226-569-5
Miroslav Virius