Masarykova univerzita Fakulta informatiky
BAKALÁŘSKÁ PRÁCE
Technologie COM ve vývojovém nástroji Microsoft Visual Studio
Ondřej Bystrý 2006
Prohlášení
Prohlašuji, že jsem bakalářskou práci zpracoval samostatně a že jsem uvedl všechny prameny, ze kterých jsem čerpal.
V Brně 5.1.2006
......................... podpis
Poděkování Děkuji vedoucí bakalářské práce paní Mgr. Barboře Zimmerové za odborné vedení a panu Ing.Václavu Kadlecovi za ochotu a vstřícnost a za cenné rady, které mi poskytl.
Shrnutí: Práce popisuje obecné principy komponentních technologií a detailněji pak principy, na nichž je založena komponentní technologie COM. Jejím konkrétním zaměřením je tvorba komponent COM ve vývojovém nástroji Microsoft Visual Studio, s využitím programovacího jazyka Visual Basic. Případová studie, jejíž tvorba je v textu podrobně zdokumentována, názorně ilustruje specifika komponentní technologie COM v praxi.
Summary: This thesis describes general principles of component technologies and focuses on principles, that component technology COM is based on. Thesis accords priority treatment to development of component COM using the Microsoft Visual Studio environment, specifically taking advantage of programming language Visual Basic. Thesis contains the description of the sample project implementation, which practically shows the unique aspects of component technology COM.
Klíčová slova: Software, komponentní technologie, technologie COM, Microsoft Visual Studio, Microsoft Visual Basic, tvorba aplikace.
Obsah 1.
Úvod ........................................................................................................ 5
2.
Popis použité technologie ...................................................................... 6
3.
2.1
Vývoj softwarových technologií ................................................................. 6
2.2
Obecné principy komponentní technologie ................................................ 6
2.3
Charakteristika technologie COM .............................................................. 9
2.4
Implementace technologie COM v Microsoft Visual Studio.................... 18
Tvorba aplikace ................................................................................... 24 3.1
Návrh komponent...................................................................................... 24
3.2
Tvorba komponent ActiveX Control ........................................................ 27
3.3
Tvorba komponenty ActiveX DLL ........................................................... 30
3.4
Tvorba komponenty ActiveX EXE ........................................................... 33
3.5
Tvorba klienta ........................................................................................... 36
3.6
Ukázka spolupráce komponent s klientem................................................ 37
4.
Závěr ..................................................................................................... 38
5.
Literatura ............................................................................................. 39
6.
Přílohy................................................................................................... 40 6.1
Přehled komponent....................................................................................... i
6.2
Uživatelská dokumentace............................................................................ v
Technologie COM
1. Úvod Cílem mé bakalářské práce bylo seznámit se s vývojovým prostředím Microsoft Visual Studio a s implementací komponentní technologie COM v tomto prostředí a použít uvedený nástroj pro vytvoření vlastní aplikace. Jádro teoretické části práce je tvořeno kapitolami 2 a 3. V kapitole 2 uvádím základní informace o použité technologii. Jsou popsány výchozí principy objektově orientovaného programování, komponentní technologie obecně a speciálně pak komponentní technologie COM. V kapitole 3 popisuji postup tvorby aplikace v uvedené technologii. Uvádím, jaká základní rozhodnutí je nutno při vývoji aplikace učinit, jak jsem se v případě konkrétní vytvářené aplikace rozhodl a z jakých důvodů. Popisuji, na jaké problémy jsem při vývoji narazil a jak jsem je řešil. Přílohy obsahují seznam komponent s popisem jejich rozhraní a uživatelskou dokumentaci k vytvořené aplikaci. Praktickou část mé bakalářské práce představuje aplikace vytvořená ve vývojovém prostředí Microsoft Visual Studio v jazyce Microsoft Visual Basic. Aplikace demonstruje obecné principy používání komponentních technologií a většinu základních a nejčastějších činností, jako například tvorbu komponent, spolupráci mezi komponentami a využití komponent klientem.
-5-
Technologie COM
2. Popis použité technologie 2.1
Vývoj softwarových technologií Přestože software má v porovnání s jinými produkty lidské činnosti docela krátkou
historii, prošla tvorba softwaru vývojem, na kterém je možno vysledovat několik odlišných fází. V první etapě softwarové tvorby byly aplikace vytvářeny ve formě jednoho programového celku, a to jak na úrovni zdrojového textu, tak na úrovni cílového strojového kódu. Zdrojový text nebyl nijak strukturován, větvení a cykly byly vytvářeny pomocí odskoků na návěští. Kompilací jednoho celku zdrojového textu vznikl jeden spustitelný celek ve strojovém kódu. Je zřejmé, že tvorba složitějších aplikací tímto způsobem byla nesmírně náročná. Čitelnost zdrojového kódu (zejména cizího, ale i vlastního s delším časovým odstupem) byla nízká, z čehož plyne, že modifikovatelnost aplikace byla obtížná a nesla vysoké riziko chyby. Důsledkem byla skutečnost, že při vývoji další aplikace, byť podobné, bylo zpravidla jednodušší naprogramovat vše znovu, než se pokoušet použít něco z dosud vytvořených programů. Metodika strukturovaného programování vycházela z analýzy vytvářeného systému a rozkladu složitého algoritmu do jednoduchých typizovaných struktur. To umožnilo vytváření knihoven funkcí a přineslo možnost opakovaného použití vytvořených softwarových elementů, zatím však pouze na úrovni zdrojového textu, tj. před kompilací. Další důležitou charakteristikou uvedené metodiky je přetrvávající „oddělení“ datových struktur od funkcionality. Knihovny evidovaly funkce, ale nespojovaly je nijak s konkrétními datovými strukturami.
2.2
Obecné principy komponentní technologie Se snižováním ceny hardwaru došlo k výraznému rozšíření sfér použití výpočetní
techniky z původní domény vědecko-technických výpočtů do mnoha dalších oblastí: obchodu, služeb, vzdělávání… Při modelování reálných činností, které měly být automatizovány, se ukázalo, že návrh jednotlivých datových struktur společně s požadovanou funkcionalitou prováděnou nad každou strukturou daleko lépe odráží skutečnost než dosud používaný přístup relativně nezávislého návrhu datových struktur a návrhu funkcí.
-6-
Technologie COM
Odpovědí na tuto potřebu byl vznik metodiky objektově orientovaného programování. Byl definován pojem objekt jako uzavřená struktura programu splňující podmínky. 1. Obsahuje vnitřní paměť (atributy). 2. Obsahuje programový kód pro zajištění funkcionality nad vnitřní pamětí (metody). Důležitá je skutečnost, že metody pracují pouze nad vnitřní pamětí objektu. 3. Objekt je schopen přijmout zprávu zvnějšku a na základě toho spustit určitou metodu ( dle tzv. protokolu zpráv přiřazujícího každé přípustné zprávě určitou metodu objektu). 4. Objekt může obsahovat jiné objekty, kterým je schopen poslat zprávu ( a tímto způsobem může řídit jejich činnost). V praxi se pak jeví jako velmi významné některé další vlastnosti objektů, přestože z pohledu teorie stojí v pozici nikoliv základních, nýbrž „pouze“ odvozených charakteristik. Jsou to především mechanismus tvorby objektů pomocí tříd, možnost dědění a polymorfismus. Objektový přístup představoval ve vývoji softwarových technologií velmi důležitý krok směrem ke standardizaci a opakovanému použití ucelených relativně nezávislých softwarových elementů. Uvedený proces však mohl být uplatňován pouze na úrovni zdrojového kódu, to znamená, že spolu mohly být spojovány pouze celky vytvořené v tomtéž, resp. v blízkém programovacím jazyce, a následně bylo nutno provést kompilaci. Uvedený nedostatek odstraňuje komponentní technologie. Pojem komponenta obecně označuje softwarový element, který může být spojován s dalšími elementy do funkčního celku a umožňuje opakované hierarchické skládání, a to vše na binární úrovni, to znamená na úrovni zkompilovaných prvků systému. Komponentní technologie vychází z objektového přístupu, instanci komponenty je možno považovat za speciální případ objektu. Komponentní technologie umožňuje tvorbu jednotlivých složek systému v různých vývojových prostředích i v odlišném programovacím jazyce, a tím maximální opakované využití prvků dříve vytvořeného softwaru. Dalším důležitým kladem komponentní technologie je snazší modifikovatelnost aplikací. Technologií rozumíme soubor pravidel a dohod, které musí programátor dodržet, aby jeho aplikace dokázala s ostatními komunikovat, jinými slovy, aby opravdu vytvořil použitelnou komponentu. Konkrétních technologií, které tyto standardy nějakým způsobem zavádějí, je několik.
-7-
Technologie COM
Třemi nejrozšířenějšími jsou komponentní technologie CCM (CORBA Component Model), založená na technologii CORBA (Common Object Request Broken Architecture), dále potom EJB (Enterprise JavaBeans) a COM (Component Object Model). COM má oproti EJB výhodu větší konzistence, v důsledku toho, že je vytvořen a poskytován jediným výrobcem – společností Microsoft. „Obrácenou stranou této mince“ je, že efektivní fungování technologie je omezeno na jedinou platformu, tj. Microsoft Windows. Technologie EJB může být realizována na různých platformách, nicméně přenosy z jedné platformy na jinou mohou být problematické. CCM je z uvedených technologií nejmladší a ve své specifikaci se snaží využít řadu prvků, které se osvědčily v ostatních technologiích. Všechny tři technologie mají mnoho společných vlastností a sílí snaha o jejich propojení. Například už existuje software, umožňující propojit komponenty technologií COM a CORBA navzájem.
-8-
Technologie COM
2.3
Charakteristika technologie COM
2.3.1 Rozhraní objektu Jedním z postulátů objektově orientovaného programování je požadavek, aby každý objekt měl tzv. protokol zpráv. Protokol zpráv je v jednotlivých technologiích implementován různě, ale obecně platí, že jde o tabulku obsahující seznam zpráv, na které umí objekt reagovat, a přiřazující každé přípustné zprávě právě jednu metodu, která se při zavolání zprávy spustí. Vzhledem k zapouzdření, které uživateli objektu nedovoluje nahlížet či dokonce zasahovat do vnitřní struktury objektu, je zasílání zpráv jedinou možností, jak s objektem komunikovat. Seznam zpráv, kterým objekt „rozumí“, je tedy pro uživatele klíčovou informací o tom, jak je možno objekt použít. Seznam zpráv sám o sobě samozřejmě neposkytuje informaci o tom, jak se bude objekt chovat. Reakce objektu na zprávu závisí na tom, jaká metoda je k dané zprávě přiřazena. To znamená, že může nastat situace, kdy se dva objekty se stejnou množinou zpráv chovají zcela odlišně (tzv. polymorfismus). Seznam zpráv, které je možné objektu zaslat, označujeme termínem rozhraní, neboli interface. Pomocí rozhraní je objekt schopen komunikovat se svým okolím. Rozhraní může být chápáno jako entita nezávislá na konkrétním objektu, a následně je pak možno uvažovat situaci, kdy rozhraní vzniká nezávisle na objektu. Například je možno definovat rozhraní a teprve potom vytvářet objekt, který bude uvedené rozhraní implementovat. Přirozeným vyústěním těchto úvah je potom otázka, zda by takto vznikající objekt mohl případně implementovat i několik různých rozhraní. Z analytického hlediska se implementace několika rozhraní jeví jako výhodné řešení. Zejména když uvážíme, že v různých již existujících rozhraních se může opakovat volání stejných zpráv, je zřejmé, že tímto způsobem je možno použít jednu metodu (tzn. jednou napsaný kód) vícekrát. Praktická realizace několikanásobného rozhraní však není triviální. Řešení se v jednotlivých komponentních technologiích odlišuje, v kapitolách 2.3.2 a 2.4.5 bude popsáno řešení uvedeného problému v technologii COM. 2.3.2 Realizace rozhraní COM, metoda QueryInterface Realizace rozhraní je především otázkou zavedení určitých dohod a standardů, při jejichž dodržení jsou potom objekty schopné komunikovat pomocí zasílání zpráv.
-9-
Technologie COM
V komponentních technologiích je tento problém o to složitější, že se má jednat o komunikaci již zkompilovaných objektů, tj. na binární úrovni. Rozhraní komponent COM je řešeno jako pole adres, ve kterém jsou uloženy ukazatele na jednotlivé metody, tzn. na první příkazy těchto metod. Zprávy jsou jednoznačně identifikovány svým pořadím v poli, tedy pomocí svého indexu. Zpráva zaslaná binárnímu objektu je tímto zredukována na jediné číslo a je interpretována jako příkaz: „Proveď instrukci nacházející se v paměti na adrese, kterou najdeš na x-tém místě v poli ukazatelů.“ Je zřejmé, že dané řešení zachovává požadované oddělení zpráv od metod a že metody nejsou spouštěny přímo, ale prostřednictvím protokolu zpráv. Výměnou obsahu tabulky ukazatelů je možno dosáhnout toho, že objekt se stejným rozhraním se bude chovat odlišně, neboť zprávám budou přiřazeny jiné metody. Několikanásobné rozhraní v binárních podmínkách je v technologii COM řešeno pomocí
rozhraní
IUnknown.
QueryInterface,
AddRef
Rozhraní a
IUnknown
Release
obsahuje
tři
zprávy:
(jejich význam bude popsán
v následujících odstavcích). Uvedené rozhraní je považováno za standardní a musí být součástí každé komponenty COM. Další standard pak nařizuje, že každé další rozhraní COM komponenty musí obsahovat mimo svých vlastních zpráv také všechny tři zprávy rozhraní IUnknown, a to povinně na „začátku“ rozhraní, tedy na prvních třech místech v poli ukazatelů. Zprávy QueryInterface, AddRef a Release je tedy možno volat pomocí kteréhokoliv rozhraní komponenty COM. Další dohoda potom zajišťuje, že ať budeme tyto zprávy volat jakkoliv (tedy prostřednictvím kteréhokoliv rozhraní), výsledek bude vždy stejný. To znamená, že ve všech rozhraních se budou za těmito zprávami skrývat tytéž metody a jestliže vyšleme zprávu např. QueryInterface kterémukoliv rozhraní komponenty, spustí se vždy tatáž metoda. Dohoda v podstatě říká, že každá komponenta má jedinou metodu QueryInterface, jedinou metodu AddRef a jedinou metodu Release a že v každém rozhraní je k dispozici zpráva pro zavolání každé z těchto metod. Možnost používat několikanásobné rozhraní nám poskytuje konkrétně metoda QueryInterface. Jde o funkci, která na dotaz na určité rozhraní vrátí ukazatel na toto rozhraní, případně informaci o tom, že rozhraní neexistuje. Na rozhraní se dotazujeme pomocí jednoznačného identifikátoru (tzv. IID - Interface Identifier). Standardy technologie COM opět stanovují několik pravidel, podle nichž se musí metoda QueryInterface chovat:
- 10 -
Technologie COM
1. Již zmíněné pravidlo o tom, že v komponentě existuje jen jediná implementace funkce QueryInterface. 2. Po celou dobu trvání života instance komponenty musí QueryInterface vracet na stejné dotazy stejné odpovědi, to znamená, že opakováním dotazu nemůžeme získat dva různé výsledky. 3. Nesmí dojít k chybě ani v situaci, kdy se klient zeptá na rozhraní, které právě v současné době používá. V takovém případě vrací rozhraní odkaz na sebe sama. 4. Pravidlo možné cesty zpět: Jestliže jsme dotazem zaslaným jednomu rozhraní úspěšně získali ukazatel na rozhraní další, pak od tohoto získaného rozhraní jsme schopni dotazem úspěšně získat odkaz na rozhraní původní. 5. Ať se na určité rozhraní ptáme dotazem přes kterékoliv rozhraní komponenty, výsledek dotazu musí být vždy stejný. Tedy odpověď nesmí záviset na tom, kterému rozhraní jsme dotaz zaslali. Takto metoda QueryInterface funguje jako jakýsi přepínač mezi rozhraními a dovoluje nám pracovat s několikanásobným rozhraním i na binární úrovní. 2.3.3 Reference, metody AddRef a Release Komponentní technologie musí také řešit problém, za který v případě OOP zodpovídá samotný programátor. Jde u určení doby vzniku a zániku objektu, který má být klientem používán. Pokud totiž vytváříme komponentu, která má být použitelná v různých programech, nemůžeme předem vědět, kdy ji bude který klient potřebovat. Je tedy potřeba rozhodnout, kdo má být zodpovědný za určení správné chvíle pro zrození a zničení komponenty. Tvůrce komponenty k tomu v době tvorby nemá dostatečné informace. Klient by mohl teoreticky nechat komponentu zrodit ve chvíli, kdy ji potřebuje, ale zůstává problém se zrušením, protože klient nemůže vědět, jestli je v danou chvíli jediným, kdo komponentu používá, a jestli nezničí komponentu, kterou v té době používá ještě někdo další. Svůj vznik a zánik by tedy měla řídit sama komponenta. Ale i tady narážíme na problém, protože na základě principů OOP by se komponenta neměla o své klienty nijak zajímat a měla by jen provádět příkazy, zaslané jí pomocí zpráv. Řešení spočívá ve vhodném rozdělení odpovědnosti mezi klienta a komponentu. Klient je povinen oznámit komponentě, že ji začal, resp. přestal používat, a komponenta si udržuje informaci o počtu referencí na sebe sama, tedy zná počet klientů, kteří ji používají. Svůj vznik a zánik je potom komponenta schopna ovládat sama na základě počtu referencí. Vzniká ve chvíli, kdy ji začne používat první klient (vzniku instance komponenty se
- 11 -
Technologie COM
budeme podrobněji věnovat v následující kapitole), a zanikne, když ji přestane používat poslední klient, to znamená ve chvíli, kdy je počet referencí roven nule. Ke změně počtu referencí slouží klientům v technologii COM právě dvě zbývající metody rozhraní IUnknown, tzn. metody AddRef a Release. Klient zavolá metodu AddRef vždy v okamžiku, kdy získá odkaz na nějaké rozhraní komponenty, a následně metodu Release, když ji používat přestane, tedy když tento odkaz zruší nebo nahradí jiným. Je třeba zdůraznit, že nová reference vzniká i kopírováním odkazu do jiné proměnné, protože v té chvíli máme možnost k objektu přistupovat dvěma různými „cestami“. Pokud bychom při kopírování odkazu nezavolali metodu AddRef, komponenta by se po zániku odkazu v původní proměnné zničila a klientovi by v druhé proměnné zůstal odkaz na neexistující objekt. V okamžiku, kdy zkopírujeme odkaz na nějaké rozhraní komponenty do další proměnné, musíme situaci chápat jako používání komponenty dvěma klienty a v souladu s tím zavolat metodu AddRef. 2.3.4 Vznik instance komponenty, Class Factory V předcházející kapitole je několikrát použita formulace: „komponenta bude sama schopna ovládat svůj vznik…“, v tomto obratu se ukrývá vcelku netriviální problém, a totiž, jak může nějaký objekt sám sebe vytvořit, když zatím neexistuje. V objektových programovacích jazycích je při tvorbě monolitického programu uvedený problém řešen tím, že konstruktor a destruktor jsou metodami třídy a za zrod svých instancí tak odpovídá sama třída. Toto řešení ovšem v komponentní technologii nepřipadá v úvahu, protože konstrukci, ve které považujeme třídu v podstatě za zvláštní druh objektu, můžeme použít ve vyšším programovacím jazyce, ale nikoliv na úrovni zkompilovaného kódu. Jednou z možností, jak lze problém řešit v komponentní technologii, je zavedení systémové funkce přístupné odkudkoliv. V takovém případě pak sám systém vystupuje v roli toho, koho žádáme o vznik objektu. V technologii COM se tato funkce nazývá CoCreateInstance a jejími parametry jsou kromě jiných především CLSID, což je jednoznačný identifikátor komponenty, a IID jako identifikátor rozhraní, které chceme použít jako první. Jako výsledek potom funkce CoCreateInstance vrací ukazatel na námi požadované rozhraní. Nevýhodou uvedeného řešení je ovšem jeho nedostatečná flexibilita. Protože při vytváření instance komponenty můžeme mnohdy vyžadovat, aby byl tento proces pod řízenou kontrolou (například z důvodů řešení přístupových práv ke komponentě, či optimalizace z hlediska dalších alokací paměti v případě, že žádáme víc instancí jedné komponenty). - 12 -
Technologie COM
Odpovědí na tyto požadavky je komponenta Class Factory. Pro každou komponentu existuje zvláštní komponenta Class Factory, která má starosti její zrod. Je vhodné zdůraznit, že Class Factory dané komponenty má na starosti pouze její zrod a nijak nesouvisí s aplikační logikou komponenty samotné, jedná se o čistě služební objekt. Postup při vzniku komponenty s použitím Class Factory vypadá takto: Nejprve je do systému nainstalována právě komponenta Class Factory, poté je možno nastavit podmínky vzniku komponenty samotné pomocí vnitřní paměti Class Factory v závislosti na konkrétních podmínkách, za nichž má být komponenta vytvořena. A teprve potom Class Factory zajistí vytvoření instance požadované komponenty. Na první pohled se může zdát, že popsaným mechanismem jsme problém nevyřešili, ale pouze ho přesunuli z jedné komponenty na jinou, protože u zrodu Class Factory vyvstává tentýž problém. Rozdíl však tkví v tom, že v případě Class Factory, vzhledem k tomu, že jde pouze o služební objekt, odpadají potíže s flexibilitou, na které jsme dříve narazili při použití systémové funkce. Vznik
instance
komponenty
v technologii
COM
tedy
vypadá
takto:
Jestliže klient požaduje vytvoření instance nějaké komponenty, nejprve je pomocí systémové funkce CoGetClassObject vytvořena Class Factory náležející k příslušné komponentě a potom se výše popsaným způsobem Class Factory postará o vznik instance komponenty, kterou chce klient používat. 2.3.5 Identifikace komponent a popis rozhraní Je zřejmé, že každá komponenta, stejně tak jako každé rozhraní, musí být jednoznačně identifikovatelné. Jako jedinečné označení se v technologii COM používá GUID (globally unique identifier). GUID je 128bitová struktura, vygenerovaná na základě času vzniku identifikátoru, relativního času od vzniku nového systému na stroji a jednoznačného identifikátoru stroje (ten vychází z identifikátoru síťové karty, nebo, v případě, že stroj síťovou kartu nemá, z kombinace identifikátorů pevného disku, CPU, příp. dalších součástí stroje) Technologie COM využívá uvedený systém identifikace jak k označování komponent (identifikátor CLSID), tak k označování jejich rozhraní (identifikátor IID). Ke generování GUID jsou využívány různé programy, např. UUIDGEN.EXE. Popsaným způsobem je možno komponentu jednoznačně identifikovat, nicméně tato informace nepostačuje uživateli k tomu, aby se dozvěděl, k čemu je komponenta určena a jak ji může využít. Aby byla komponenta využitelná jinými programátory, než je sám autor, je potřeba vhodným způsobem popsat její rozhraní, tedy jaké zprávy je
- 13 -
Technologie COM
komponenta schopna přijímat, jejich vstupní a výstupní parametry, a především jejich význam. Přestože formát popisu není striktně předepsán, standardně se používá tzv. Interface Definition Language – zkráceně IDL. Jedná se o jazyk se syntaxí podobnou C++, jehož pomocí lze vytvořit popis rozhraní obsahující především jeho jednoznačný identifikátor IID, vytvořený jako GUID, a dále jméno rozhraní, atributy, definice metod… Využití tohoto prostředku výrazně zjednodušuje komunikaci mezi tvůrcem komponenty a jejími uživateli. Je vhodné upozornit na skutečnosti, že zveřejnění popisu komponenty je pro tvůrce určitým závazkem, protože v případě, že se rozhodne nějakým způsobem rozhraní změnit, nemůže ho pouze upravit (protože by pak neodpovídalo zveřejněnému popisu), ale musí zavést rozhraní nové. 2.3.6 Komponenty in-process, out-of-process V propojování komponent na binární úrovni se skrývá ještě další nebezpečí. Komponentu do systému „připojujeme“ díky tomu, že známe ukazatel na její rozhraní. Vyvstává však otázka, zda budeme vždy schopni tento ukazatel použít, tedy zda adresa, na kterou ukazuje, bude pro náš program dostupná. Problém spočívá v izolaci procesů. Dva procesy, byť běží na jednom stroji, nesdílejí stejný paměťový prostor. Každý proces si vyhrazuje vlastní paměťový prostor, ve kterém se pohybuje, a při volání odkazů v této paměti používá logických adres. Logické adresy jsou absolutní v rámci jednoho procesu, ale relativní mezi procesy. Z toho plyne, že shodným logickým adresám ve dvou spuštěných procesech odpovídají rozdílné adresy fyzické. Je tedy zřejmé, že pokud se komponenta nachází jinde než v paměťovém prostoru klienta, nebude přímo dostupná, přestože běží na stejném stroji (v případě, že běží na jiném stroji, je tento problém ještě zjevnější). Na základě tohoto problému tedy rozdělujeme komponenty na servery in-process a out-of-process. Servery in-proces se připojují tak, že se nová instance komponenty instaluje do stejného paměťového prostoru, v němž se nachází klient. Servery out-of-process potom existují mimo paměťový prostor klienta a dělíme je ještě na tzv. lokální a vzdálené servery podle toho, zda běží na stejném nebo na jiném stroji. V případě lokálního serveru out-of-process je potřeba zajistit komunikaci mezi izolovanými procesy. To je možno provést několika způsoby, například tzv. dynamickou výměnou dat (Dynamic Data Exchange, DDE) nebo pomocí sdílené paměti. Technologie COM používá k uvedenému účelu tzv. Local Procedure Call (LPC). Aby bylo
- 14 -
Technologie COM
zabezpečeno odizolování jak klienta, tak komponenty od přímého volání do jiného procesu, probíhá tato komunikace přes „simulanty“. V procesu klienta je ve tvaru in-proces DLL zaveden objekt simulující činnost komponenty (tento simulant se nazývá proxy) a naopak v procesu komponenty je stejným způsobem zaveden objekt simulující činnost klienta (nazývá se stub). Simulanti spolu potom komunikují právě pomocí LPC. V případě vzdáleného serveru je situace obdobná, ale ke komunikaci mezi proxy a stub se používají funkce Remote Procedure Call (RPC) určené ke komunikaci mezi různými stroji pomocí síťových prostředků. Tato technologie se také někdy nazývá DCOM (Distributed COM). Je vhodné podotknout, že LPC je možno považovat za zvláštní případ použití RPC, a to pro volání funkcí na jednom stroji. Současně s použitím RPC je ovšem třeba řešit celou řadu otázek spjatých se síťovou komunikací, především otázku bezpečnosti a přístupových práv. Dalšími termíny, které je vhodné zmínit v souvislosti se servery out-of-process, jsou marshalling a unmarshalling. Jsou to názvy pro činnosti nezbytné při předávání parametrů mezi izolovanými procesy tak, aby tyto byly pro obě strany srozumitelné. Převod parametrů volání do standardního formátu pro přenos mezi procesy se nazývá marshalling. Opačná operace, zpětného dekódování parametrů v procesu, který je přijal, se potom nazývá unmarshalling. Protože každé rozhraní má své vlastní metody a parametry, zavádí se standardně pro každé rozhraní jeho vlastní marshaller. Ten obsahuje specifický algoritmus pro zakódování a dekódování parametrů metod daného rozhraní. 2.3.7 COM a vlákna V souvislosti s multithreadingem, tedy při běhu několika vláken v jednom procesu, může v COM dojít hned ke dvěma problémům. Zaprvé je to situace, kdy je instance komponenty nainstalována v prostředí, kde běží několik vláken najednou. Komponenta neví samozřejmě nic o svých klientech, to znamená, že neví, kdy ji bude kdo volat, a v tomto prostředí potom může docházet ke kolizím, pokud není komponenta sama proti nim nějak zabezpečena. Druhý problém pak nastává v „opačném“ případě: komponenta sama může být naprogramována tak, že v sobě spouští více vláken, a v tom případě může dojít ke kolizím, pokud je takováto komponenta používána klientem, který s více vlákny neumí pracovat. Komponenta totiž může zavolat klienta hned několika vlákny, a pokud tento není zabezpečený proti paralelnímu zpracování, dojde ke kolizi. I v tomto případě však je to komponenta, která jakožto služební objekt (server) nese odpovědnost za bezpečnost své činnosti pro libovolného klienta.
- 15 -
Technologie COM
Uvedené problémy jsou v technologii COM řešeny zavedením dvou základních „povolených“ modelů a to STA (Single-Threaded Apartment Model) a MTA (Multi-Threaded Apartment Model). Rozdíl mezi modely STA a MTA tkví v tom, že v případě modelu STA je synchronizace více vláken realizována samotnou technologií COM, tedy pomocí jejích systémových funkcí, a rozhraní jsou mezi jednotlivými vlákny marshallována. V případě MTA si musí své vnitřní stavy synchronizovat komponenta sama, to znamená, že za správné fungování i v případě volání z více vláken odpovídá programátor, který musí všechny tyto případy nějakým způsobem ošetřit. Dalším rozdílem je pak to, že v MTA nedochází mezi vlákny k marshallingu rozhraní. Nespornou výhodou modelu STA je skutečnost, že se programátor komponenty nemusí zabývat problémy souvisejícími s multithreatingem. Ovšem ruku v ruce s výhodou, že při tvorbě komponenty není potřeba řešit synchronizaci, jde nevýhoda, že tím pádem programátor komponenty nemá synchronizaci nijak pod kontrolou, což také může působit problémy (viz další odstavec). Další nevýhodou je potom to, že v tomto modelu není možné zavést uvnitř komponenty paralelní zpracovávání několika vlákny. Při použití modelu STA stojí před programátorem nesnadný úkol zajistit synchronizaci vláken běhu programu v komponentě, přestože komponenta neví, kdy a kdo ji bude volat. Na druhou stranu je to v některých případech jediná možnost, jak optimalizovat rychlost komponenty, a v některých případech dokonce jediná možnost, jak zajistit její funkcionalitu. 2.3.8 Windows Registry a COM Registry je systémová databáze Windows. Kromě mnoha dalších funkcí tato databáze podporuje evidenci nezbytnou pro technologii COM. COM používá Registry k uschování důležitých informací o komponentách v systému. Primární informací o komponentě je především její jednoznačný identifikátor (viz 2.3.5) označovaný jako CLSID. K tomuto identifikátoru se pak v databázi asociují další podstatné informace týkající se dané komponenty. Registry má stromovou strukturu a k jeho prohlížení se používá Registry Editor, a to buď REGEDIT.EXE (pod Windows 95) nebo REGEDT32.EXE. Prvky používané v technologii COM, které jsou zapsané v Registry, jsou všechny v jediném kořenu s názvem HKEY_CLASSES_ROOT. Zde je možno nalézt podřízený prvek označený CLSID, v němž se nacházejí veškeré informace o všech třídách libovolné komponenty. Kromě CLSID komponenty je důležitou informací především slovní označení
- 16 -
Technologie COM
komponenty, které je samozřejmě srozumitelnější a uživatelsky příjemnější než skupiny hexadecimálních znaků. Registrace (resp. odhlášení) serveru EXE se provádí buď přímo jeho spuštěním nebo pomocí příkazů /regserver a /unregserver. V případě serveru DLL a Control komponent se potom používá registrační program REGSVR32.EXE, který má parametr /u pro odregistrování komponenty.
- 17 -
Technologie COM
2.4
Implementace technologie COM v Microsoft Visual Studio
2.4.1 Microsoft Visual Studio Microsoft Visual Studio je komplexní produkt určený k vývoji softwaru, který v sobě sdružuje několik různých vývojových prostředí. Každý programovací jazyk má své specifické vlastnosti, díky kterým je vhodný k řešení určitých typů problémů. A právě z toho důvodu, aby programátor měl možnost využít pro daný úkol ten nejvhodnější vývojový prostředek, nabízí je firma Microsoft společně v podobě Visual Studia. Konkrétně Visual Studio 6.0 Enterprise Version nabízí: •
Visual Basic,
•
Visual C++,
•
Visual FoxPro,
•
Visual InterDev,
•
Visual J++,
•
Visual SourceSafe,
•
MSDN Library.
Co se týká konkrétně Visual Basicu, jedná se vyšší programovací jazyk, zaměřený především na tvorbu aplikací fungujících na principech OOP. Jak napovídá už sám název „Basic“, jedná se o programovací jazyk, který se snaží být pokud možno uživatelsky „přívětivý“ a maximálně zjednodušit programátorům práci. Jeho předností je jednoduché ovládání, kdy vývojové prostředí samo řeší řadu technických problémů. Ve Visual Basicu je možno velmi snadno a rychle vytvářet „standardní“ aplikace. Jeho nevýhodou na druhou stranu je malá flexibilita. 2.4.2 Vztah jazyka Microsoft Visual Basic a technologie COM Visual Basic 6.0 (stejně jako už i předchozí verze 5.0) je sám vystavěn pomocí technologie COM a v důsledku toho je zavádění komponent v daném jazyce poměrně snadné. Převážnou většinu problémů, o kterých byla řeč v předchozí kapitole, ošetřuje Visual Basic automaticky. Například při tvorbě komponenty ve Visual Basicu se programátor nikde nesetká s takovými pojmy jako rozhraní IUnknown či metody QueryInterface, AddRef a Release. Práci s těmito metodami uschovává Visual Basic za běžné příkazy vyššího programovacího jazyka. Poznámka: Existence uvedených metod je samozřejmě patrná. Jako příklad můžeme uvést porovnávací operátor is. Ten zjišťuje zda dva ukazatele odkazují na tentýž objekt. - 18 -
Technologie COM
Tedy výraz A is B je pravdivý, pokud A i B jsou ukazatele na rozhraní téhož objektu, a to i v případě, že každý ukazuje na jiné rozhraní a tím pádem obsahují různé hodnoty. Operátor is ve skutečnosti pracuje tak, že se pomocí metody QueryInterface zeptá každého z rozhraní, na něž A a B ukazují, na rozhraní IUnknown daného objektu a v případě, že mu obě rozhraní vrátí stejnou adresu, jedná se o jeden a týž objekt. Při tvorbě komponenty ve Visual Basicu není potřeba využívat žádnou speciální syntaxi a je možno vypracovávat projekt stejně jako při tvorbě běžné objektově orientované aplikace. Převedení aplikace na komponentu COM je potom otázkou několika změn v nastavení ve vlastnostech projektu. (Samozřejmě však je potřeba znát principy komponentní technologie a zohlednit je při tvorbě analytického návrhu.) Vzhledem k tomu, že Visual Basic není programovacím jazykem určeným primárně k vytváření komponent COM, je potřeba vyjasnit vztahy mezi termínem komponenta a termíny, které používá Microsoft Visual Basic. Z jednoho projektu vždy vznikne jedna komponenta, ve smyslu jednoho EXE souboru. Na druhou stranu, z každé třídy (s výjimkou tříd nastavených jako Private, touto problematikou se bude zabývat kapitola 2.4.4) také vznikne jedna samostatná komponenta, ve smyslu komponenty využitelné při vytváření klienta. Může tak teoreticky dojít k zajímavé situaci, kdy jeden EXE souboru obsahuje několik komponent (nikoliv různých rozhraní jedné komponenty, ale skutečně různých komponent). Platí tedy pravidlo, že při tvorbě komponenty COM ve Visual Basicu by projekt měl mít pouze jednu zvnějšku viditelnou třídu. Nicméně to, jaký typ programu Visual Basic při kompilaci nakonec vytvoří, záleží především na nastavení položky Project Type ve vlastnostech projektu. Přípustná nastavení budou popsána v následující kapitole. 2.4.3 Typy komponent v Microsoft Visual Basic Visual Basic umožňuje vytvářet několik typů komponent COM: •
ActiveX EXE,
•
ActiveX DLL,
•
ActiveX Control,
•
ActiveX Dokument DLL a ActiveX Dokument EXE.
ActiveX EXE jsou komponenty out-of-process, určené primárně pro běh logiky aplikace. Někdy bývají také nazývány „out-of-process code components“. V souladu s dříve popsanými vlastnostmi out-of-process serveru se tyto komponenty nevyskytují v paměťovém prostoru klienta, ale alokují si vlastní paměťový prostor. Problematice tvorby komponent bude věnována pozornost ještě později, prozatím snad ještě stojí za zmínku skutečnost, že v souvislosti s vlákny používá Visual Basic vždy pouze model - 19 -
Technologie COM
STA (Single-Threaded Apartment Model), a to jak u serveru EXE, tak DLL. To znamená, že programátor komponenty se nemusí (ale ani nemůže) zabývat synchronizací v případném prostředí s více vlákny, neboť tato problematika je ošetřena systémovými funkcemi technologie COM. Komponenty ActiveX DLL jsou in-process servery, které mají na starosti taktéž aplikační logiku programu. Analogicky s ActiveX EXE jsou někdy označovány termínem „in-proces code components“. Uvedené označení zdůrazňuje jejich aplikační použití v kódu programu, to znamená, že by neměly být používány jako prvky uživatelského rozhraní. Z charakteristiky „in-process server“ plyne, že komponenta ActiveX DLL se nachází vždy v paměťovém prostoru klienta. Přístup z klienta do komponenty je tedy velmi rychlý, protože není zdržován žádnými simulanty ani marshallingem, jako je tomu v případě out-of-process komponent. Stejně jako běžná DLL knihovna se komponenta ActiveX DLL připojuje do systému až v době běhu programu, nicméně protože jde o komponentu COM, tzn. jedná se už o zkompilovaný kus kódu, je samozřejmě jazykově nezávislá a může být použita i v jiném vývojovém prostředí. ActiveX Control je taktéž komponentou in-process, ale slouží naopak v naprosté většině případů ke komunikaci s uživatelem. Aplikační logikou by neměla být pokud možno zatěžována, především proto, že by to značně snižovalo jejich znuvupoužitelnost. Komponent AcitveX Control již samozřejmě existuje celá řada a vzhledem k jejich jazykové nezávislosti jsou využívány širokou škálou vývojových nástrojů jako Microsoft Visual C++, Microsoft Visual J++, Microsoft FoxPro, Borland Delphi a samozřejmě i Microsoft Visual Basic. Při tvorbě takovéto „vizuální“ komponenty můžeme postupovat několika způsoby. Jednak je možno samozřejmě vytvořit kompletně novou komponentu, ale je nutno počítat s tím, že tvorba vizuální stránky (kreslení, překreslování při různých událostech atd.) je velmi pracná a časově náročná. Mnohem častěji se setkáme s postupem, kdy je pouze upravována a vylepšována existující komponenta tím, že jí jsou přidávány nové atributy, metody či události. Přitom libovolné atributy, metody i události původní komponenty mohou být buď ponechány uschované v nové komponentě, nebo jednoduchým přeposláním být zveřejněny navenek. Poslední možnost je v podstatě jen zobecněním předchozí, kdy místo upravování jedné existující komponenty sestavíme náš nový ActiveX Control z několika již existujících komponent. Posledním uvedeným druhem jsou komponenty ActiveX Document. Reprezentují využití technologie COM pro speciální případ, a sice pro přístup k dokumentům
- 20 -
Technologie COM
(otevírání dokumentu, editace, řízení atd.) Jejich využití umožňuje přistupovat ke všem typům dokumentů jednotným způsobem. Poznámka k terminologii: Na první pohled může být poněkud matoucí, že všechny druhy komponent mají v názvu označení ActiveX a naopak označení COM se nikde neobjevuje. Důvodem je skutečnost, že Microsoft využívá technologii COM jako základ pro několik různých technologií, z nichž jedna je právě ActiveX. Znamená to tedy, že jakákoliv komponenta ActiveX je zároveň COM-komponentou.
2.4.4 Nastavení projektu Uvedu nejprve několik základních parametrů, které mají vliv na chování komponenty, a význam jednotlivých hodnot, jichž mohou parametry nabývat. Samozřejmě nejpodstatnějším z parametrů je již dříve zmíněný Project Type. Typ projektu je ve Visual Basicu zadáván již na začátku vytváření projektu. V již existujícím projektu je pak možno hodnotu uvedeného parametru změnit ve vlastnostech projektu (tj. v nabídce Project, pod odkazem „Název projektu“ Properties…) v záložce General. Dále stojí za zmínku položka Project Description. Pod názvem uvedeným v Project Description je pak možno komponentu vyhledat, jestliže má být použita jako server klientské aplikace. Taktéž ve vlastnostech projektu, ale v záložce Component, jsou evidovány další důležité vlastnosti ovlivňující chování komponenty. Nejvýznamnější z nich je zřejmě Version Compatibility. Tato vlastnost ovlivňuje spolupráci Visual Basicu s Windows Registry a generování identifikátoru komponenty v průběhu jejího vývoje. Může nabývat tří hodnot: •
No Compatibility – v tomto případě s každým nově vytvořeným EXE či DLL souborem vzniká nová registrace ve Windows Registry a je generován nový identifikátor CLSID. To může samozřejmě při nestřídmém používání vést k velmi nepřehlednému stavu ve Windows Registry
•
Project Compatibility – je vytvářen pouze jeden CLSID a další se už v rámci kompilací tohoto projektu nevytvářejí. Komponentu je tedy možo vyvíjet bez obav, že by se někde hromadily její staré verze. Ovšem k tomu, aby nová verze komponenty byla pro klienta použitelná, musí být po změně serveru zkompilován i klient.
•
Binary Compatibility – při tomto nastavení není nutno klienta znovu kompilovat, protože dohoda, pomocí níž se bude s komponentou napojovat, je uvedeným nastavením stanovena jako neměnná. V praxi to znamená, že rozhraní komponenty nesmí být modifikováno, protože jinak komponenta přestane být pro klienta použitelná. - 21 -
Technologie COM
(Samozřejmě je možno provádět úpravy ve vnitřních mechanismech komponenty, ale rozhraní už musí zůstat beze změny.) Charakteristiku Binary Compatibility je tedy vhodné nastavit až ve chvíli, kdy komponenta už je prakticky hotová. Další podstatnou vlastností komponent ActiveX DLL a ActiveX EXE je nastavení atributu Instancing, který vyjadřuje vztah třídy k vytváření instancí. Atribut Instancing může být nastaven na následující hodnoty: •
Private – Třída, která je nastavená jako Private, je zvnějšku komponenty neviditelná, uživatel komponenty nemůže ani vytvořit instanci této třídy, ani ji jakkoliv jinak použít, dokonce ani jako typ proměnné.
•
PublicNotCreatable – v tomto případě uživatel bude třídu znát, ale nebude moci z této třídy vytvářet vlastní instance. Pracovat s instancí třídy je možno pouze v případě, že je tato vytvořena samotnou komponentou.
•
SingleUse - při volbě SingleUse je s každou novou instancí volána také tvorba nové komponenty.
•
MultiUse – s novou instancí nevzniká nová komponenta, to znamená, že několik instancí sdílí stejnou komponentu. Jedná se o nejčastěji používaný případ.
•
GlobalSingleUse a GlobalMultiUse – od předchozích možností se liší tím, že metody a vlastnosti deklarované v takové třídě se chovají jako deklarované globálně. Můžeme je tedy používat, aniž bychom předtím vytvořili instanci třídy. Ta samozřejmě ve skutečnosti existuje, ale Visual Basic ji vytvoří skrytou, pro nás neviditelnou, takže se vlastnosti a metody tváří jako globální.
Třídy komponenty ActiveX DLL mohou mít nastaveny jen čtyři z těchto variant a sice: Private, PublicNotCreatable, MultiUse a GlobalMultiUse. U komponent ActiveX EXE jsou povoleny všechny možnosti. Vlastnost Instancing je nastavitelná pouze během návrhu komponenty a za běhu aplikace již není možné ji změnit. 2.4.5 Rozhraní a několikanásobná rozhraní ve Visual Basic Rozhraní není ve Visual Basicu potřeba nikde speciálně definovat (na rozdíl např. od C++), jako rozhraní komponenty jsou automaticky nabízeny všechny metody třídy, které jsou označeny klíčovým slovem public. V této chvíli se samozřejmě nabízí otázka: Pokud je rozhraní generováno automaticky, jak je možné vytvořit ve Visual Basicu několikanásobné rozhraní? Tato možnost samozřejmě existuje, nicméně je poznamenána právě částečným splýváním pojmů rozhraní a třída, ke kterému dochází v důsledku toho, že rozhraní je od třídy odvozováno automaticky. - 22 -
Technologie COM
Odpovědí je příkaz Implements, za kterým jako parametr následuje název třídy. V tomto případě ovšem název třídy reprezentuje právě rozhraní, které tato třída nabízí. Díky příkazu Implements si tedy můžeme vybrat, která z již existujících rozhraní má naše komponenta implementovat. Metody vybraných rozhraní se v kódu označují názvem příslušného rozhraní (třídy) a názvem implementované zprávy (názvy jsou odděleny podtržítkem). Visual Basic usnadňuje programátorovi práci tím, že tyto složené názvy metod sám nabízí, stačí vybrat příslušný název v rozbalovacím seznamu v horní části okna s kódem dané třídy. Pokud je potřeba, aby komponenta nabízela několik vlastních, dosud neexistujících rozhraní, je potřeba přidat do projektu požadovaný počet tříd, pojmenovat je názvy rozhraní, která chceme prostřednictvím naší komponenty nabízet, a nastavit jejich atribut Instancing na Private. Do každé z nich potom vytvoříme metody odpovídajícího rozhraní. Vytvořením je v tomto případě myšlena pouhá deklarace metody, např: Public Sub Priklad () End Sub
Skutečnost, zda je v metodě uveden nějaký kód, je zcela nepodstatná, protože její implementace, kterou bude používat klient, bude vytvářena až ve zvnějšku viditelné třídě (označené např. Class1). V Class1 bude pak pomocí příkazu Implements nastaveno, že třída má implementovat „vytvořená“ rozhraní. (Příkazy Implements jsou uváděny v bloku deklarací.) 2.4.6 Tvorba komponent out-of-process ve Visual Basicu Jak je patrné už z předchozích kapitol, nese s sebou tvorba komponent out-of-process velké množství komplikací, které je třeba nějak ošetřit. Nicméně je na místě říct, že ve Visual Basicu bere na sebe technická řešení většiny těchto problémů programovací jazyk sám, takže z uživatelského hlediska je podstatné především správné nastavení, zajišťující, že Visual Basic vyřeší tyto problémy podle našich požadavků. Při vývoji v podstatě nepoznáme rozdíl mezi vývojem komponenty ActiveX EXE a ActiveX DLL, přestože každá používá zcela odlišný způsob komunikace s klientem. Snad jediný rozdíl spočívá v tom, že v případě out-of-process serveru Visual Basic nedovoluje otevřít projekt ve stejné skupině projektů (project group) jako klienta. Pokud však vyvíjíme komponentu samostatně, pak „přepracování“ komponenty ActiveX DLL na ActiveX EXE či naopak je pouze otázkou změny v nastavení Project Type.
- 23 -
Technologie COM
3. Tvorba aplikace 3.1
Návrh komponent
3.1.1 Obecné principy Při vytvoření aplikace komponentní technologií je možno kvalitní návrh komponent považovat za nutnou (byť nikoliv postačující) podmínku úspěchu. Předpokladem dobrého návrhu je pak správná analýza cílového systému. Systém je nutno rozložit do relativně nezávislých funkčních celků vhodné velikosti, případně hierarchicky strukturovaných. Přitom je potřeba dbát na to, aby každý funkční celek pracoval pouze nad svými daty a s ostatními komunikoval pouze na základě definované množiny zpráv. Návrh by také měl zohlednit případné opakující se datové struktury a s nimi spojené funkcionality. Často diskutovaná je granulita rozkladu. Příliš velké specializované komponenty mají menší naději na znovupoužití. Příliš malé obecné komponenty naopak snižují přehlednost programu. Vždy je ale dobré, pokud už máme rozvržení hotové, podívat se na jednotlivé komponenty a zamyslet se, zda by bylo možné rozsah jejich použitelnosti zvětšit a jakou cenu bychom za větší univerzálnost zaplatili. A v případě, že je poměr „cena/výkon“ příznivý, raději zvolit univerzálnější komponentu, i přesto, že budeme část práce dělat, z pohledu naší aplikace, zbytečně. Na rozdíl od předchozích rozhodnutí je otázka pořadí tvorby komponent zpravidla jednoduchá. Nejprve jsou vytvářeny komponenty ActiveX Control, aby je následně bylo možno využít na „vizualizaci“ aplikační logiky při testování a ladění ActiveX DLL a ActiveX EXE. V rámci komponent jednoho typu jsou pak zpravidla vytvářeny nejprve nižší využívané komponenty, a teprve následně komponenty vyšší úrovně, aby každá komponenta mohla být co nejdříve otestována. 3.1.2 Rozvržení komponent v aplikaci Karty Komponenta ObrazekKarty: Představuje univerzální komponentu, umožňující zobrazit libovolnou ze standardní sady 52 karet, otočenou buď lícem nebo rubem, která je současně schopna oznámit akce uživatele prováděné nad ní. Komponenta je tedy použitelná v libovolné karetní hře používající tuto sadu karet, nebo její libovolnou podmnožinu.
- 24 -
Technologie COM
Komponenta SeznamObrazkuKaret: Zásadním omezením, v jehož důsledku je komponenta využitelná jen pro některé typy her, je skutečnost, že uživatel není schopen jednotlivé karty v seznamu otáčet. To je způsobeno tím, že všechny tři základní úkony myši (poklepnutí levým tlačítkem, poklepnutí pravým tlačítkem a dvojitého poklepnutí) jsou využity na proces označení (resp. odznačení) a odhození karty. Komponenta umožňuje pouze nastavit charakteristiku líc / rub pro seznam jako celek a regulovat pak zobrazení karet modifikací tohoto atributu. Primárně je tedy komponenta určena ke znázornění karet hráčů ve všech karetních hrách fungujících na principu postupného odhazování karet. Nicméně může být použita i k zobrazení seznamu karet odhozených (jako v našem případě) či vyložených na stůl. Další komponentou je SeznamKaret: Komponenta slouží pro simulaci karet, které má hráč v ruce, to znamená, že musí plnit z toho vyplývající funkcionality. Mimo běžných úkonů jako přijmout a odhodit danou kartu, vypsat svůj obsah či setřídit karty požadujeme po komponentě především, aby na základě informací o první vynesené kartě a o trumfech byla schopna rozhodnout, které z karet může hráč hrát, a vrátit klientovi jejich seznam. Aby využití komponenty bylo co nejširší a komponenta mohla být použita nejen pro jednu konkrétní hru, jsou některé podmínky vyhodnocování předávány metodě ve formě parametrů. Nejkomplikovanější komponentou je komponenta Hra: Jejím úkolem je simulovat průběh karetní hry, a to na základě klientem provedeného nastavení atributů komponenty a zasílání zpráv v závislosti na chování uživatele. Zde opět vyvstává otázka míry univerzálnosti komponenty, která přímo souvisí s rozdělením funkcionality mezi komponentu a jejího klienta. Existuje zde přímá úměra: čím bude komponenta univerzálnější, tím více problémů za ni bude muset ošetřovat klient. Při rozhodování o charakteru komponenty Hra jsem nakonec upřednostnil univerzální pojetí. Komponenta je schopna simulovat nejen mou konkrétní hru, ale obecně všechny karetní hry fungující na stejném základním principu, tj. na odhazování karet a braní zdvihů. Důsledkem pak je, že některé funkcionality, především ty, ve kterých se hry od sebe nejčastěji liší, musí obstarat klient. Jde například o správu počtu sehrávek, vyhodnocení sehrávek apod.
- 25 -
Technologie COM
3.1.3 Schéma spolupráce komponent
- 26 -
Technologie COM
3.2
Tvorba komponent ActiveX Control Komponenty ActiveX Control jsou tzv. uživatelské komponenty, tj. komponenty,
jejichž primárním cílem je umožnit uživateli ovládat program. Neměly by být zatěžovány aplikační logikou programu. Jedná se o komponenty in-process, tedy jsou součástí paměťového prostoru svého klienta a slouží jen jemu. Klient by měl mít možnost komponenty ovládat, což se děje především pomocí nastavování atributů komponent. Komponenta by potom měla umět reagovat na zásah uživatele a uvědomit o něm svého klienta, zpravidla vyvoláním příslušné události. 3.2.1 Tvorba komponenty ObrazekKarty Prvním krokem tvorby komponent bylo vytvoření komponenty pro zobrazení jedné karty. Atributy komponenty evidují zejména •
identifikační číslo karty,
•
příznak, zda karta je zobrazena lícem nebo rubem,
•
příznak, zda kartu je v daném okamžiku povoleno otočit (z rubu na líc, resp. obráceně).
Pro uložení obrázku jsem použil komponentu PictureBox, do které je v závislosti na obsahu atributu načítán obrázek karty ze zdrojového souboru karty.RES. (Soubor karty.RES jsem vytvořil v Microsoft Visual C++) Komponenta obsahuje metodu pro otočení karty z rubu na líc, resp. naopak. Při klepnutí na zobrazenou kartu vyvolává komponenta příslušnou z událostí: •
lClick (poklep levým tlačítkem myši),
•
rClick (poklep pravým tlačítkem myši),
•
dblClick (poklepání levým tlačítkem myši).
Podnětem pro vyvolání uvedené události komponentou ObrazekKarty je odpovídající událost v podřízené komponentě PictureBox. 3.2.2 Tvorba komponenty SeznamObrazkuKaret Komponenta zobrazuje karty, které drží hráč v ruce a které postupně odhazuje na stůl. Jde tedy o komponentu se specifičtějším účelem, než byla předchozí komponenta. Především bylo třeba vyřešit problém odhazování karty, tj. jakým způsobem má uživatel sdělit požadavek na odhození určité karty. Z možných řešení jsem nakonec zvolil variantu, kdy hráč může odhodit pouze kartu, kterou předem označí (přestože se - 27 -
Technologie COM
tento způsob ovládání může v některých okamžicích jevit hráči jako „zbytečné zdržování“). Rozhodl jsem se takto ze dvou důvodů: Za prvé ve hře často dochází k situaci, kdy hráč může odhodit jen některé z karet, které má v ruce. Jestliže kartu je možno označit (tj. předběžně vybrat), znamená to, že je možno ji odhodit. Uživatel má takto možnost zjistit, zda kartu může odhodit, a to předtím, než tak skutečně učiní. Druhým důvodem je skutečnost, že předběžné označení zabrání neúmyslnému shození karty nebo shození jiné karty v případě nepřesného umístění kurzoru nebo neúmyslnému pohybu myši při klepnutí. (Je zřejmé, že vrácení karty, byť neúmyslně odhozené, nesmí být dovoleno.) Mezi atributy komponenty patří zejména: •
počet karet v seznamu,
•
příznak, zda je povoleno s kartami manipulovat (tedy označovat a odhazovat),
•
příznak, zda jsou karty v seznamu otočeny lícem nebo rubem,
•
příznak, zda seznam má být zobrazen horizontálně nebo vertikálně.
Komponenta obsahuje především kolekci ovládacích prvků typu ObrazekKarty. Obsahuje samozřejmě metodu k přidání nové karty (kartu implicitně nastaví jako neotočitelnou) a její příznak Lic (resp. Oznacitelna) je nastaven v souladu s atributem Lic (resp. Aktivni) komponenty SeznamObrazuKaret. Mezi nejdůležitější metody patří bezpochyby metody reagující na události vyvolané některou z karet v kolekci (tzn. lClick, rClick a dblClick). Zde je zejména potřeba mít na paměti, že společně s událostí dblClick jsou vyvolány dvě události lClick. V souvislosti s tuto skutečností bylo potřeba ošetřit dva problémy: 1. Při dvojitém poklepnutí, které mělo sloužit k odhození již dříve označené karty, došlo nejdříve k vyvolání události lClick, která způsobila označení karty a následně vyvolaná událost dblClick potom tuto kartu odhodila, přestože před započetím tohoto dvojitého poklepnutí byla označena jiná karta. 2. Vzhledem k tomu, že při dvojitém poklepnutí je událost dblClick vyvolána dříve než událost lClick druhého poklepu, docházelo k tomu, že po odhození karty, které dblClick způsobil, byla ihned označena karta, která se ocitla na místě právě odhozené. Oba tyto případy jsem vyřešil zavedením zvláštních vnitřních atributů mDvojklikPovolen a mDvojklik, oba typu boolean, jejichž pomocí jsem zabezpečil korektní chování programu. Uvádím kód obou metod: Private Sub mKarty_dvojClick(Index As Integer, ByVal aID As Byte) mDvojklik = True If mDvojklikPovolen = False Then Exit Sub
- 28 -
Technologie COM
If Index = mIndex Then Odhod End Sub Private Sub mKarty_lClick(Index As Integer, ByVal aID As Byte) If Not mDvojklik Then If (Index = mIndex) Or (mKarty(Index).Oznacitelna = False) Then mDvojklikPovolen = True Exit Sub End If If mIndex > -1 Then Odznac mIndex = Index mKarty(Index).Top = mKarty(mIndex).Top - 150 mDvojklikPovolen = False Else mDvojklik = False End If End Sub
Další důležitou metodou je metoda AktivujMnozinu, jejímž vstupním parametrem je pole karet (přesněji řečeno pole identifikačních čísel reprezentujících karty) a která se snaží aktivovat karty předané jí jako vstupní parametry. Ty karty ze vstupního pole, které se v kolekci nacházejí, jsou samozřejmě aktivovány. Pokud však metoda kartu v kolekci nenajde, vyvolá událost AktivovanaKartaNenalezena s číslem této karty jako parametrem. Mimoto
nabízí
komponenta
svému
klientovi
možnost
nechat
aktivovat
(resp. deaktivovat) všechny karty, zbavit se karty s daným identifikátorem, zbavit se všech karet, či nechat systémem odhodit právě označenou kartu. Kromě události AktivovanaKartaNenalezena může komponenta vyvolat událost ZrusenaKartaNenalezena, a to v situaci, kdy se, analogicky s předchozím případem, nepodaří v kolekci karet najít kartu, jejíž zrušení klient požaduje. Dále potom komponenta vyvolává „standardní“ události při změnách některých svých atributů, např. atributů •
PridanaKarta,
•
OdhozenaKarta,
•
ZmenaSvisle,
•
Vyprázdněni.
Všechny zmíněné události jsou samozřejmě vybaveny odpovídajícími parametry (nejčastěji identifikační číslo karty), které jsou relevantní pro jejich správnou identifikaci klientem.
- 29 -
Technologie COM
3.3
Tvorba komponenty ActiveX DLL Komponentu Seznam karet jsem se rozhodl vytvořit jako ActiveX DLL, protože
v jejím případě nebyl důvod, proč by měla existovat mimo paměťový prostor svého klienta, tedy komponenty Hra. Naopak její umístění mimo paměťový prostor klienta by si vyžádalo další volání pomocí LPC, což by běh programu zbytečně zdržovalo, a navíc by řešení bylo neefektivní z hlediska nároků na paměť. 3.3.1 Tvorba komponenty SeznamKaret V původním návrhu jsem zamýšlel použít komponentu SeznamKaret pouze pro reprezentaci karet, které má hráč v ruce, V průběhu tvorby komponenty jsem došel k názoru, že bude výhodné rozšířit její interface o metody Zamichej a IndexNejvyssiKarty a použít metodu současně k evidenci celého balíčku karet a karet odhozených na stůl. Komponenta obsahuje především pole proměnných typu Byte, které reprezentuje karty a přirozeně eviduje také informaci o počtu karet, které uchovává. Otázkou bylo, jakým způsobem bude komponenta interpretovat identifikační čísla karet, aby si přitom zachovala co největší flexibilitu. Problém jsem se rozhodl vyřešit přidáním nastavitelného atributu mPocetHodnot, udávajícího počet karet jedné barvy. Komponenta je tak schopna jednak porovnat, zda jsou dvě karty stejné barvy, a jednak v rámci jedné barvy rozhodnout, která karta má vyšší hodnotu, což z hlediska požadované funkcionality naprosto postačuje. Přitom tento způsob řešení dovoluje případnému uživateli komponenty velice široké spektrum možností, například libovolné množství barev (omezené pouze rozsahem proměnné). Dokonce je možné realizovat i různé počty karet jednotlivých barev, stačí při číslování karet dodržet pravidlo, že karty každé další barvy budou mít čísla větší než příslušný násobek počtu karet „nejpočetnější“ barvy, a atribut mPocetHodnot nastavit právě na toto číslo. Implicitně je atribut mPocetHodnot nastaven na číslo 13, protože to odpovídá standardnímu balíčku karet. Správná interpretace karet je nezbytná zejména kvůli dvěma funkcím: VyberHratelne a IndexNejvyssiKarty. Úkolem první z nich je na základě předaných parametrů rozhodnout, které karty může hráč v dané sehrávce odhodit. Mezi předávané parametry přitom patří: vynesená karta, číslo barvy trumfů a informace o tom, zda je hráč povinen ctít barvu a přebíjet. Druhá funkce potom slouží k vyhodnocení zdvihu. V jejím případě je parametrem jen informace o trumfech. Pro lepší představu přikládám kód zmiňovaných funkcí. - 30 -
Technologie COM
Public Function VyberHratelne(ByVal aKarta As Byte, ByVal aTrumf As Byte, ByVal aCtitBarvu As Boolean, ByVal aPrebijet As Boolean) As Byte() Dim MamBarvu As Boolean, MamTrumf As Boolean, i As Byte, j As Byte, Hratelne() As Byte MamBarvu = False MamTrumf = False j = 0 If aCtitBarvu Then For i = 1 To mPocet If Int((mKarty(i) - 1) / mPocetHodnot) = Int((aKarta - 1) / mPocetHodnot) Then MamBarvu = True Next i For i = 1 To mPocet If Int((mKarty(i) - 1) / mPocetHodnot) = aTrumf – 1 Then MamTrumf = True Next i End If For i = 1 To mPocet If (MamBarvu = False) Or (Int((mKarty(i) - 1) / mPocetHodnot) = Int((aKarta - 1) / mPocetHodnot)) Then If (MamTrumf = False) Or (Int((mKarty(i) - 1) / mPocetHodnot) = aTrumf - 1) Or (MamBarvu = True) Then If mKarty(i) Mod mPocetHodnot > aKarta Mod mPocetHodnot Then ReDim Preserve Hratelne(j) Hratelne(j) = mKarty(i) j = j + 1 Else If (Not aPrebijet) Or ((Not MamBarvu) And (Not MamTrumf)) Then ReDim Preserve Hratelne(j) Hratelne(j) = mKarty(i) j = j + 1 End If End If End If End If Next i VyberHratelne = Hratelne End Function Public Function IndexNejvyssiKarty(ByVal aTrumf As Byte) As Byte Dim i As Byte, max As Byte, indexMax As Byte If mPocet = 0 Then IndexNejvyssiKarty = -1 If mPocet = 1 Then IndexNejvyssiKarty = 0 indexMax = 1 max = mKarty(1) For i = 2 To mPocet If (Int((mKarty(i) - 1) / mPocetHodnot) = Int((max - 1) / mPocetHodnot) And (mKarty(i) > max)) Then max = mKarty(i) indexMax = i End If If (Int((mKarty(i) - 1) / mPocetHodnot) = aTrumf - 1) And (Int((mKarty(i) - 1) / mPocetHodnot) <> Int((max - 1) / mPocetHodnot)) Then max = mKarty(i) indexMax = i
- 31 -
Technologie COM
End If Next i IndexNejvyssiKarty = indexMax - 1 End Function
Další metody potom komponentě dovolují přidat příslušnou kartu, odhodit příslušnou kartu, odhodit všechny karty, uspořádat se podle velikosti, zamíchat se a vypsat svůj obsah. Komponenta vyvolává následující události: •
Pridana (při přidání karty),
•
Odhozena (po úspěšném odhození požadované karty),
•
Nenalezena (pokud komponenta nevlastní kartu, jejíž odhození klient požaduje).
Poznámka: V předchozím textu byl několikrát uveden pojem "trumf". Termín je samozřejmě ve všech hrách „mariášového“ typu dobře známý a způsob jeho implementace v komponentě je zřejmý. Je však dobré uvědomit si, že zavedení klasického pojetí "trumfové barvy" je v určitém smyslu omezením, protože takto vytvořená komponenta podporuje maximálně jednu „zvýhodněnou“ barvou. Komponentu SeznamKaret tedy není možno použít u her, ve kterých by upřednostněných barev bylo víc, případně kdyby navíc bylo na preferovaných barvách zavedeno nějaké uspořádání.
- 32 -
Technologie COM
3.4
Tvorba komponenty ActiveX EXE Skutečnost, že komponenta nesdílí společný paměťový prostor s klientem, vyžaduje
u těchto komponent především jiný způsob testování ve srovnání s ostatními komponentami. Při testování jedné komponenty ActiveX EXE je potřeba mít spuštěná současně dvě vývojová prostředí Visual Basicu. V jednom z nich je spuštěna testovaná komponenta, nejlépe s plnou kompilací, ve druhém testovací formulář. Analogicky pak testování více komponent ActiveX EXE vyžaduje spuštění vlastního okna vývojového prostředí pro každou ze spouštěných testovaných komponent a jednoho okna vývojového prostředí pro formulář. 3.4.1 Tvorba komponenty Hra Vzhledem
k tomu,
že
tato
komponenta
je
zároveň
klientem
vzhledem
ke komponentě SeznamKaret, je vhodné uvést, jakým způsobem klient rozhoduje, které komponenty (DLL či EXE) chce používat. K tomu slouží položka References z nabídky Project, kde je možno vyznačit ty komponenty, které bude aplikace vyžadovat. (Komponenty jsou zpravidla vybírány podle označení zadaného ve vlastnostech projektu v položce Project Description.) Klíčovým problémem při tvorbě komponenty Hra bylo vyhovět požadavku na sdílení dat společných pro všechny hráče a dále pak možnost komunikace jednotlivých instancí komponenty Hra tak, aby všichni přihlášení opravdu hráli stejnou hru. První problém jsem vyřešil přidáním nového modulu typu BAS (tzv. standardní modul), který jsem pojmenoval „global“ a který obsahuje informace společné pro všechny hráče (počet hráčů, informaci o tom, kdo je na tahu, apod.) Tento způsob řešení je umožněn nastavením vlastnosti Instancing na MultiUse. Komponenta obsahující modul „global“ vznikne při vyžádání první instance komponenty Hra. Při dalších požadavcích na vznik instance komponenty pak, právě díky MultiUse, používají všechny instance stejnou komponentu, tudíž i stejný „global“ modul a tudíž sdílejí stejná data. Problém vzájemné komunikace je pak vyřešen tím, že modul „global“ obsahuje kolekci gSeznamHracu, která jako své prvky uchovává právě všechny instance komponenty
Hra
sdílející
stejnou
komponentu.
Prostřednictvím
kolekce
gSeznamHracu potom může každá z instancí komponenty Hra (každá reprezentuje jednoho hráče) pomocí zasílaní zpráv informovat ostatní o změnách ve hře. Jako příklad uvádím kód metody PridejHrace, která přihlašuje ke hře nového hráče. Nejprve Hra sama sebe přidá do kolekce hráčů gSeznamHracu a nastaví své - 33 -
Technologie COM
vnitřní atributy a poté zašle svému klientovi informace o již přihlášených hráčích a zašle informaci o svém přihlášení ostatním komponentám v kolekci (ty ji pak předají dál svým klientům). Public Sub PridejHrace(ByVal aJmeno As String, ByVal aPrebijet Boolean, ByVal aCtitBarvu As Boolean) Dim i As Byte If (gPocetHracu >= gMaxPocetHracu) Or (gHraBezi) Then Exit Sub gSeznamHracu.Add Me gPocetHracu = gPocetHracu + 1 ID = gPocetHracu Jmeno = aJmeno mPrebijet = aPrebijet mCtitBarvu = aCtitBarvu If gPocetHracu > 1 Then For i = 1 To gPocetHracu - 1 RaiseEvent PridanyHrac(gSeznamHracu(i).Jmeno, i) gSeznamHracu(i).PridanyHrac (aJmeno) Next i End If RaiseEvent PridanyHrac(aJmeno, mID) End Sub
As
Public Sub PridanyHrac(ByVal aJmeno As String) RaiseEvent PridanyHrac(aJmeno, gPocetHracu) End Sub
Metody jako PridanyHrac představují z analytického hlediska určitý problém. Jsou to sice metody využívané čistě pro potřeby komunikace jednotlivých instancí komponenty Hra, ale z pochopitelného důvodu musí být označeny jako public (jinak by je ostatní instance nebyly schopné volat) a tím pádem jsou Visual Basicem zveřejněné v rozhraní komponenty, přestože z pohledu klienta nemají žádný smysl (pouze vrací klientovi obratem stejnou informaci, kterou on sám serveru předal). Proto jsem se snažil metody používané pouze ke komunikaci jednotlivých instancí třídy co nejvíce omezit. Často jsem za tímto účelem využíval možnosti deklarovat metodě
nepovinný
parametr
(klíčovým slovem Optional).
Pomocí
operátoru
IsMissing, který vrací hodnotu true, právě když je metoda volána bez tohoto nepovinného parametru, jsem potom rozlišoval případy, kdy je metoda volaná klientem, a případy, kdy se jedná pouze o komunikaci mezi jednotlivými instancemi komponenty. Jako příklad uvádím právě takto navrženou metodu Vynasejici, která v závislostí na nepovinném parametru aIDHrace buď informuje příslušnou instanci, že má vynášet nebo dává vědět všem klientům (včetně svého) o tom, kdo vynáší. Public Sub Vynasejici(Optional ByVal aIDHrace As Variant) Dim i As Byte If IsMissing(aIDHrace) Then mNaRade = True For i = 1 To gPocetHracu gSeznamHracu(i).Vynasejici mID
- 34 -
Technologie COM
Next i Else RaiseEvent KdoVynasi(aIDHrace, gSeznamHracu(aIDHrace).Jmeno) End If End Sub
Samozřejmě jsem i při tvorbě této komponenty zachovával principy OOP, takže komponenta Hra je, jakožto server, schopna volat svého klienta pouze pomocí vyvolaných událostí.
- 35 -
Technologie COM
3.5
Tvorba klienta Jak je patrné ze schématu, aplikace Whist (klient) přímo používá tyto komponenty:
•
ActiveX Control: SeznamObrazkuKaret – komponenta je použita dvěma způsoby, jednak kolekce těchto komponent reprezentuje karty, které hráči drží v ruce, a dále jako reprezentant karet odhozených na stůl.
•
ActiveX EXE: Hra – komponenta jednak částečně napomáhá (z hlediska aplikační logiky) klientovi v odehrání sehrávky, především však slouží ke komunikaci s ostatními hráči (reprezentovanými taktéž komponentami Hra), které by jinak klient nebyl schopen.
Tvorba klienta probíhala už víceméně současně s tvorbou komponenty Hra, postupným upravováním testovacího formuláře až do výsledné podoby. V první fázi byl formulář využíván k testování správné činnosti uživatelských komponent ActiveX Control. Poté, co byly odladěny tyto komponenty, jsem s jejich využitím začal testovat server Hra. Testování probíhalo (z důvodu co nejsnazšího dohledání chyb v programu) od nejzákladnějších metod, jako jsou rozdání karet či reakce na odhozenou kartu, postupně k metodám z hlediska hry pokročilejším, jako např. vyhodnocení zdvihu a určení dalšího vynášejícího. Jednoduchost testování a snadné odhalování chyb se staly rozhodujícími faktory, které mě mnohdy přiměly k vytváření náročnější klientské aplikace, než jsem původně zamýšlel. Velmi výhodná byla například možnost simulace hry všech hráčů na jediné spuštěné aplikaci, kterou jsem použil na začátku pro odhalení chyb ve funkcionalitě serveru a spolupráci komponenty Hra s jí podřízenou komponentou SeznamKaret. Přitom jsem se nemusel zabývat problémy, které vzniknou při komunikaci několika instancí komponenty mezi sebou. Přepsaní klienta z jednouživatelské verze na víceuživatelskou bylo sice náročnější než přímé vytvoření klienta pro víceuživatelské prostředí, ale z celkového hlediska zanedbatelné v porovnání s časem, který jsem ušetřil efektivním odlaďováním prvotních chyb v komponentě Hra, jednak díky snazšímu hledání chyb a především díky jednoduššímu způsobu testování (simulace hry v jediném okně, je nesrovnatelně rychlejší než přepínání mezi minimálně čtyřmi běžícími aplikacemi). Jako poslední potom přišla na řadu úprava klienta, aby poskytoval přehledné zobrazení a pohodlné uživatelské ovládání.
- 36 -
Technologie COM
3.6
Ukázka spolupráce komponent s klientem
Schéma znázorňuje reakci programu na dvojité poklepnutí na označenou kartu (hráčem, který je na tahu).
- 37 -
Technologie COM
4. Závěr Během tvorby bakalářské práce jsem se seznámil se základními principy komponentních
technologií,
s problémy,
které
s sebou
zavedení
komponentní
technologie přináší, a s mechanismy, kterými jsou tyto problémy řešeny v technologii COM. Pochopil jsem výhody použití komponentní technologie i její nedostatky a související technická omezení. Zejména
jsem
se
seznámil
s používáním
komponentní
technologie
v programovacím jazyce Microsoft Visual Basic a s možnostmi, které uvedený programovací jazyk tvůrcům komponent COM nabízí. Získané poznatky jsem následně ověřil v praxi při tvorbě aplikace založené na komponentní technologii COM. Přitom jsem se snažil navrhnout aplikaci tak, aby demonstrovala různé možnosti využití technologie, to znamená tvorbu různých typů komponent, jejich vzájemné skládání a komunikaci jak s aplikací klienta, tak komponent navzájem. V teoretické části práce jsem v kapitole 2 popsal výchozí teoretické principy technologie a v kapitole 3 jsem zdokumentoval postup při tvorbě aplikace. Věřím, že má práce může být použita při tvorbě dalších aplikací pracujících se zobrazováním karet nebo zabývajících se simulací karetních her.
- 38 -
Technologie COM
5. Literatura 1.
Balena, Francesco: Programming Microsoft Visual Basic 6.0, Microsoft Press, 1999
2.
Heineman, George T., Councill, William T.: Component-based software engineering: putting the pieces together, Boston, Addison-Wesley, 2001
3.
Kraval, Ilja, Ivachiv, Pavel: Základy komponentní technologie COM, Praha, Computer Press, 1998
4.
Zelený, Jindřich: COM+, COBRA, EJB : komponentní architektury, Praha, BEN - technická literatura, 2002
- 39 -
Technologie COM
6. Přílohy
- 40 -
Technologie COM
6.1
Přehled komponent
6.1.1 ObrazekKarty Komponenta ObrazekKarty je komponentou ActiveX Control, kromě rozhraní IUnknown poskytuje rozhraní CObrazekKarty, které dokáže reagovat na zprávy. Metody a jejich parametry: ZmenOtocitelnost
Mění hodnotu příznaku, zda karta může být otočena.
ZmenOznacitelnost
Mění hodnotu příznaku, zda karta může být označena, tj. vybrána.
Otoc
Otočí kartu, pokud je to povoleno.
6.1.2 SeznamObrazkuKaret Komponenta SeznamObrazkuKaret je komponentou ActiveX Control, kromě rozhraní IUnknown poskytuje rozhraní CSeznamObrazkuKaret, které dokáže reagovat na zprávy. Metody a jejich parametry: Aktivuj
Nastaví všechny karty jako označitelné.
AktivujMnozinu
Nastaví množinu karet jako označitelné.
aHratelne
Pole obsahující identifikační čísla karet, které chceme aktivovat.
Deaktivuj
Nastaví všechny karty jako neoznačitelné.
Pridej
Přidá kartu do seznamu.
aID ZbavSeKarty aID
Identifikační číslo karty, kterou chceme přidat. Odebere kartu ze seznamu. Identifikační číslo karty, kterou chceme odhodit.
Odhod
Odebere ze seznamu kartu, která je právě označena.
VyprázdniSe
Odebere ze seznamu všechny karty.
ZakazOdznacit
Zakáže označení karty na dané pozici v seznamu.
aIndex PovolOznacit aIndex
Číslo udávající pozici karty v seznamu. Povolí označení karty na dané pozici v seznamu. Číslo udávající pozici karty v seznamu.
Odznac
Odznačí označenou karty, pokud taková existuje.
Otoc
Mění hodnotu příznaku, zda seznam je zobrazován svisle nebo vodorovně.
-i-
Technologie COM
6.1.3 SeznamKaret Komponenta SeznamKaret je komponentou ActiveX DLL. Kromě rozhraní IUnknown poskytuje komponenta rozhraní CSeznamKaret, které dokáže reagovat na zprávy. Metody a jejich parametry: Pridej aID Setrid
Přidá kartu do seznamu. Identifikační číslo karty, kterou chceme přidat. Uspořádá karty v seznamu vzestupně podle velikosti identifikačního čísla.
Zamichej
Náhodně zamíchá karty v seznamu.
VyberHratelne
Funkce na základě vstupních parametrů vrací pole karet, které hráč může v tomto zdvihu odhodit.
aKarta
Identifikační číslo karty, která byla pro výběr hratelných karet v daném zdvihu klíčová. Zpravidla je to první vynesená karta.
aTrumf
Číslo barvy trumfů.
aCtítBarvu
Informace, zda je hráč povinen hrát kartou stejné barvy jako aKarta, respektive trumfem, pokud stejnou barvu nemá.
aPrebijet
Informace, zda je hráč povinen hrát kartou vyšší hodnoty než aKarta.
IndexNejvyssiKarty aTrumf
Funkce vrací index karty, která bere zdvih. Číslo barvy trumfu.
Vypis
Funkce vrací pole se seznamem karet.
Odhod
Odstraní kartu ze seznamu.
aID OdhodVsechno
Identifikační číslo karty, kterou chceme odstranit. Odstraní všechny karty.
6.1.4 Hra Komponenta Hra je komponentou ActiveX EXE, kromě rozhraní IUnknown poskytuje rozhraní CHra, které dokáže reagovat na zprávy. Metody a jejich parametry: Poznámka: U metod používaných výhradně pro komunikaci mezi jednotlivými instancemi komponenty nebudou parametry uváděny, neboť se nepředpokládá jejich využití klientem.
VezmiKartu aID SeznamKaret
Přidá kartu do seznamu karet, které má hráč v ruce. Identifikační číslo karty, kterou chceme přidat. Funkce vrací seznam karet, které má hráč v ruce.
- ii -
Technologie COM
ZapisVysledek aVysledek NovyVysledek
Přidá výsledek do pole výsledků. Hodnota výsledku. Metoda používaná při komunikaci jednotlivých instancí komponenty mezi sebou, vyvolá událost NovyVysledek a informuje o ní svého klienta.
Vysledky
Vrací seznam výsledků
VynulujSkore
Vyprázdní pole výsledků
RozdejKartu
Metoda používaná při komunikaci jednotlivých instancí komponenty mezi sebou, vyvolá událost RozdanaKarta a informuje o ní svého klienta.
ZadejTrumfy
Metoda používaná při komunikaci jednotlivých instancí komponenty mezi sebou, vyvolá událost Trumfy a informuje o ní svého klienta
ZacatekHry
Metoda používaná při komunikaci jednotlivých instancí komponenty mezi sebou, vyvolá událost ZacatekHry a informuje o ní svého klienta
CoChcesUhrat
Metoda používaná při komunikaci jednotlivých instancí komponenty mezi sebou, vyvolá událost CoChcesUhrat a informuje o ní svého klienta
Rozdej
Zamíchá balík karet a rozdá na základě nastavení atributů PocetHracu a RozdatKaret.
aIDHrace
Nepovinný parametr, pokud jej použijeme, metoda pouze zavolá Rozdej() příslušného hráče.
PridejHrace
Připojí hráče ke hře, pokud je to povoleno (jestliže hra neběží a počet hráčů je menší než povolené maximum).
aJmeno
Jméno přidávaného hráče.
aPrebijet
Nastavení parametrů hry.
aCtitBarvu
Nastavení parametrů hry.
PridanyHrac
Metoda používaná při komunikaci jednotlivých instancí komponenty mezi sebou, vyvolá událost PridanyHrac a informuje o ní svého klienta.
CoMuzuHrat
Funkce zavolá funkci VyberHratelne u karet, které hráč drží v ruce a výsledek přepošle klientovi.
ZacniHru
Nastaví atributy o počtu her na výchozí hodnoty, informuje ostatní hráče o začátku hry a rozdá.
- iii -
Technologie COM
Zadano
Metoda používaná při komunikaci jednotlivých instancí komponenty mezi sebou, vyvolá událost Zadano a informuje o ní svého klienta.
KolikChciUhrat aChciUhrat Nahlášeno
Nastaví atribut ChciUhrat a informuje ostatní hráče. Informace, čeho chce hráč v sehrávce dosáhnout. Funkce vrací pole obsahující informace, co chce který hráč uhrát.
PridejNaStul
Metoda používaná při komunikaci jednotlivých instancí komponenty mezi sebou, vyvolá událost ZahranaKarta a informuje o ní svého klienta.
ZahranaKarta
Odhodí kartu z rukou klienta a přidá ji do karet zdvihu. V případě, že jde o poslední kartu zdvihu, určí, kdo zdvih bere, a případně předá klientům výsledek celé sehrávky.
aIDKarty KonecZdvihu
Identifikační číslo karty. Metoda používaná při komunikaci jednotlivých instancí komponenty mezi sebou, vyvolá událost KonecZdvihu a informuje o ní svého klienta.
VzalJsemZdvih
Metoda používaná při komunikaci jednotlivých instancí komponenty mezi sebou, vyvolá událost KdoVzalZdvih a informuje o ní svého klienta.
Vynasejici
Informuje ostatní hráče, kdo vynáší.
NaTahu
Informuje ostatní hráče, kdo je na tahu.
PredejVysledkySehravky
Metoda používaná při komunikaci jednotlivých instancí komponenty mezi sebou, vyvolá událost KonecSehravky a informuje o ní svého klienta.
OdeberHrace
Odhlásí hráče ze hry a informuje o tomto kroku ostatní hráče.
NactiBalik aBalik
Načte danou množinu karet jako balík. Pole čísel.
OdhodBalik
Vyprázdní balík.
OdhodKartu
Odhodí danou kartu.
aID OdhodVsechnyKarty
Identifikační číslo karty. Odhodí všechny karty v ruce hráče.
Poznámka: V přehledech nejsou uvedeny standardní metody Property Get a Property Let.
- iv -
Technologie COM
6.2
Uživatelská dokumentace
6.2.1 Pravidla hry Karetní hra „brněnský whist“ je oblíbenou hrou brněnských středoškolských a vysokoškolských studentů, zejména těch, jejichž obor souvisí s matematikou. Úspěch hráče je totiž dán především jeho logickou úvahou a kombinačními schopnostmi, nikoliv štěstím na dobře rozdané karty. (Pojmenováni je poněkud zavádějící, protože pravidla hry jsou odlišná od klasického whistu, nicméně hra je v našem městě mezi studenty skutečně nazývaná „whist“.)
♦ Základní údaje: Počet hráčů:
4-6
Druh karet:
pikety (32 listů)
Pořadí hodnot:
vzestupně: 7, 8, 9, 10, J, Q, K, A
♦ Cíl hry: V každé jednotlivé sehrávce je cílem hráče uhrání ohlášeného počtu zdvihů
♦ Průběh hry Hra se skládá z jednotlivých kol. V každém kole je jeden hráč rozdávajícím (pro všechny sehrávky kola), hraje se tedy tolik kol, kolik je hráčů. V prvním kole je rozdávající vylosován, v každém následujícím kole se role rozdávajícího posunuje na dalšího hráče po směru hodinových ručiček. Pozn. Je potřeba odehrát vždy plný počet kol, aby hráči se spravedlivě vystřídali v pořadí pro hlášky. (Nejvýhodnějším pořadím pro hlášky je pozice vpravo od rozdávajícího, tzn. předposlední ohlašující, protože hráč při rozhodování o své hlášce má již informace o hláškách protihráčů a přitom na rozdíl od posledního ohlašujícího není v hlášení nijak omezen.)
♦ Průběh kola Počet sehrávek v kole je závislý na počtu hráčů: při 4 hráčích se hraje 8 sehrávek, při 5 hráčích 6 sehrávek a při 6 hráčích 5 sehrávek v kole (obecně: při n hráčích se jedno kolo hraje na 32 div n sehrávek). V první sehrávce kola se rozdává maximální počet karet (samozřejmě všem hráčům stejný počet, např. při 5 hráčích se rozdává po 6 kartách a zbývající 2 karty zůstanou -v-
Technologie COM
nerozdané). V každé další sehrávce se počet karet každého hráče o jednu kartu snižuje, až v poslední sehrávce se každému hráči rozdává pouze jedna karta. Nerozdané karty zůstávají na stole v balíčku rubem vzhůru a hry se nijak nezúčastní.
♦ Průběh sehrávky Pro každou sehrávku je před rozdáváním vylosována trumfová barva. Po rozdání karet nahlásí každý hráč počet zdvihů, které chce uhrát. Hráči oznamují svou hlášku v pevně stanoveném pořadí: začíná hráč vlevo od rozdávajícího, jako poslední oznamuje hlášku rozdávající hráč. Všichni hráči kromě rozdávajícího mohou ohlásit libovolný počet zdvihů (včetně nuly). Rozdávající, tzn. poslední ohlašující, nesmí ohlásit takový počet zdvihů, při kterém by se celková hodnota hlášek rovnala počtu zdvihů v sehrávce (tím je vyloučeno, aby všichni hráči splnili stanovený cíl). V prvním zdvihu sehrávky vynáší hráč, který oznámil nejvyšší hlášku, při shodných maximálních hláškách pak ten hráč, který ohlásil maximální hodnotu jako první. V následných zdvizích vynáší vždy ten, kdo bral předchozí zdvih. ♦ Pravidlo sehrávky
Shazování karet je omezeno pouze barvou, nikoliv hodnotou, to znamená, že není nutno přebíjet. Shazovaná karta musí ctít barvu vynesené karty. Jestliže hráč nemá kartu v barvě vynesené karty, musí shodit trumfovou kartu. V případě, že hráč nemá v ruce ani vynesenou barvu ani trumf, může shodit libovolnou kartu.
♦ Hodnocení sehrávky Jestliže hráč uhrál nahlášený počet zdvihů P, pak za sehrávku získává P+10 bodů. Jestliže uhrál méně nebo více zdvihů, než nahlásil, nezíská žádný bod.
♦ Závěrečné hodnocení Celkové pořadí hráčů za hru je určeno součtem bodů za všechny sehrávky všech kol.
- vi -
Technologie COM
6.2.2 Ovládání programu
♦ Hrací pole Základní obrazovka odpovídá standardnímu rozložení hracího pole při stolní hře. To znamená, že každý hráč má své karty před sebou nejblíže k sobě (na obrazovce v dolní části obrazovky). Hráč vidí své karty lícem, zatímco karty protihráčů, otočené rubem, jsou umístěny jedna sada vlevo, jedna sada vpravo a zbývající jeden až tři hráči „naproti“, tj. v horní části obrazovky. Vynesená karta a odhazované karty jsou zobrazovány ve středu obrazovky. V levé části obrazovky je umístěn informační panel, kde jsou uvedeny základní údaje aktuální sehrávky: jména hráčů, jejich hláška v aktuální sehrávce, dosud získaný počet zdvihů, trumfová barva a jméno hráče, který je na tahu. Obrázek č. 1: Hrací pole pro 4 hráče
- vii -
Technologie COM
Obrázky č. 2 a 3: Hrací pole pro 5 hráčů a pro 6 hráčů
♦ Počátek hry Pořadí, v jakém se hráči přihlásili, je považováno za pořadí, v jakém by při stolní hře seděli u stolu, a to po směru hodinových ručiček. Jakmile se do hry přihlásil dostatečný počet hráčů, systém zpřístupňuje prvnímu přihlášenému tlačítko „Začni hru“. Na pokyn „Začni hru“ systém spustí zahájení první sehrávky prvního kola.
♦ Sehrávka Systém rozdá karty, vylosuje trumfovou barvu a zobrazí ji ve spodní části informačního panelu. Potom systém vyzve jednotlivé hráče k nahlášení zamýšleného počtu vítězných zdvihů (v pořadí stanoveném pravidly). Uskutečněné hlášky jsou zobrazovány v informačním panelu.
- viii -
Technologie COM
Obrázek č. 4: Dialogové okno pro hlášku na začátku sehrávky
Hráč, který je na tahu, může vynést libovolnou kartu. Ostatní hráči pak odhazují ve stanoveném pořadí každý jednu kartu v souladu s pravidly. (Systém umožní hru pouze tomu hráči, který je na tahu.) Vynesení i odhození karty jsou prováděna stejným způsobem: nejprve hráč klepnutím vybere kartu a teprve následně ji poklepáním vynese, resp. odhodí. Výběr karty je systémem zobrazován vysunutím karty nad úroveň ostatních. Přitom při odhazování systém dovolí vybrat pouze takovou kartu, jejíž odhození je v souladu s pravidly. Hráč může vybrat postupně několik karet, aniž by došlo k odhození. Pouze poklepání na aktuálně vybranou je systémem interpretováno jako požadavek na vynesení, resp. odhození karty. (Tento způsob ovládání zabraňuje neúmyslnému vynesení karty nepřesným poklepáním.) Provedený úkon samozřejmě není možno odvolat.
- ix -
Technologie COM
Obrázek č. 5: Zobrazení vybrané karty
-x-
Technologie COM
♦ Vyhodnocení výsledků Po skončení sehrávky systém zobrazuje výsledek sehrávky. Obrázek č. 6: Zobrazení výsledku sehrávky
Po skončení každého kola systém zobrazuje průběžný výsledek celé hry. Obrázek č. 7: Zobrazení průběžného výsledku
- xi -