VŠB - Technická univerzita Ostrava Fakulta elektrotechniky a informatiky Katedra aplikované matematiky
Sdílení dokumentů ve stávajícím informačním systému
2006
Martin Čermák
Prohlášení diplomanta
Prohlašuji, že jsem celou bakalářskou práci včetně příloh vypracoval samostatně a uvedl jsem všechny použité podklady a literaturu. Chtěl bych poděkovat ing. Petru Foltýnkovi za odbornou pomoc při tvorbě této bakalářské práce. Souhlasím s tím, že s výsledky mé bakalářské práce může být naloženo dle uvážení vedoucího bakalářské práce jako jejího spoluautora a doporučení vedoucího katedry. V případě publikace výsledků nebo její významné části budu uveden jako spoluautor.
V Ostravě ……………….
………………………………… podpis diplomanta
Abstrakt Úlohou této práce bylo vyvinout modul popřípadě moduly, které firmě CID International, ® a.s. lépe pomohou pracovat s dokumenty Microsoft Office , které jsou uloženy v databázi aplikace Market. Ke splnění této úlohy je využita technologie COM, vyvinutá firmou Microsoft. První část práce popisuje potřeby Microsoft Office® dokumentů v aplikaci Market a schémata použití těchto dokumentů v aplikaci Market. Druhá část práce shrnuje základní znalosti COM technologie. Od úvodního seznámení přes popis jednotlivých funkcí k popisu důležitých tříd, které se využívají při tvorbě aplikace. Součástí je komentovaný zdrojový kód pro práci s dokumenty typu Microsoft Office®. Vyvinuté hotové moduly jsou funkční a jsou vyžívány v produktech firmy CID International, a.s..
Klíčová slova Microsoft Office®, COM, dokument,
Seznam zkratek BI CRM COM IDL MDI MSMQ MTS OLE RPC UI vptr WWW
– Business Inteligence – Customer Relationship Management – Compoment Object Model – Interface Definition Language – Multiple dokument interface – Microsoft Message Queue Server – Microsoft Transaction Server – Object Linking and Embedding – Remote Procedure Call – User interface – virtual table pointer – World Wide Web
Sdílení dokumentů ve stávajícím informačním systému
6
Obsah Úvod................................................................................................................................................ 7 1 Práce s dokumenty v produktech společnosti CID International, a. s. .................................. 8 1.1 Principy použití dokumentu v aplikaci Market................................................................. 8 1.1.1 Přístup pomocí kontaktů ........................................................................................ 11 1.1.2 Přístup pomocí Požadavku ..................................................................................... 14 1.2 Práce s Office dokumenty............................................................................................... 18 2 Technologie COM............................................................................................................... 19 2.1 Úvodní seznámení s touto technologií............................................................................ 19 2.1.1 Základní myšlenka ................................................................................................. 19 2.1.2 Úvodní deklarace rozhraní ..................................................................................... 20 2.1.3 Skutečné rozhraní COM......................................................................................... 20 2.1.4 Krátké seznámení s jazykem IDL .......................................................................... 21 2.1.5 Rozhraní IUnknown ............................................................................................... 21 2.1.6 Vytvoření objektu a jeho používání ....................................................................... 23 2.1.7 Dědičnost a polymorfizmus ................................................................................... 25 2.2 Automation ..................................................................................................................... 26 2.2.1 Rozhraní IDispatch................................................................................................. 27 2.2.2 Návrh rozhraní IDispatch v komponentě ............................................................... 27 2.2.3 Implementace rozhraní IDispatch .......................................................................... 28 2.2.4 Detekce chyb.......................................................................................................... 30 3 Základní COM objekty pro práci s Microsoft Office.......................................................... 32 3.1 Třída COleDispatchDriver.............................................................................................. 32 3.1.1 Automation Clients ................................................................................................ 32 3.1.2 Automation Server ................................................................................................. 33 3.1.3 Členské atributy ..................................................................................................... 33 3.1.4 Konstruktor ............................................................................................................ 33 3.1.5 Metody třídy COleDispatchDriver......................................................................... 34 3.2 Třída _Application.......................................................................................................... 34 3.3 Třída _Document ............................................................................................................ 35 3.4 Třída Documents............................................................................................................. 35 3.5 Třída COleVariant .......................................................................................................... 36 3.6 Třída Workbooks ............................................................................................................ 36 4 Implementace jednotlivých části programu ........................................................................ 38 4.1 Vytvoření Automation Client pro MFC.......................................................................... 38 4.2 Důležitý kód pro MS Word ............................................................................................ 38 4.3 Důležitý kód pro MS Excel ............................................................................................ 40 4.4 Hotový kód ..................................................................................................................... 42 5 Vyhodnocení výsledků........................................................................................................ 43 Použitá literatura ........................................................................................................................... 44
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
7
Úvod Problematika sdílení dokumentů je ve firmě CID International, a.s. velmi naléhavá a důležitá pro její správnou funkčnost a prosperitu. Tuto problematiku řeší zejména aplikace Market. Práce je zaměřena na komunikaci Microsoft Office® dokumentů s aplikací Market. Jsou zde popsány způsoby práce s těmito dokumenty v aplikaci Market, je uveden důvod, proč se firma CID rozhodla ® vyvinout moduly, které slouží k interakci Microsoft Office dokumentů do aplikace Market. Výsledkem této bakalářské práce je popis a realizace interakce Microsoft Office® dokumentů s informačním systémem Market. Práce seznamuje s obecnými charakteristikami COM a OLE Automation technologiemi a s objekty vyvinutými firmou Microsoft pro práci s programy Microsoft Office®. Poslední část práce se věnuje popisu kódu, který zabezpečuje otevření ®
®
dokumentu typu Microsoft Word a Microsoft Excel a následně reaguje na uzavření dokumentu uživatelem.
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
1 Práce s dokumenty International, a. s.
v produktech
8
společnosti
CID
Společnost CID International, a.s. se je nezávislá společnost (ISV), zabývající se vývojem software pro oblast dopravy, spedice a logistiky zejména v segmentu malých a středních firem. Nosným produktem společnosti je podnikový informační systém pro provozní řízení práce firem v uvedených oblastech podnikání. Podle převažující oblasti činnosti zákazníka je tento informační systém realizován produktem COLLI pro přepravy kusových zásilek, LORI pro firmy s vlastními dopravními prostředky a přepravujícími spíše ucelené zásilky, LOGI pro skladování zásilek a CARGI pro přepravu zásilek po železnici. Pro všechny formy podnikového systému je typická modulární struktura, která umožňuje kombinace všech produktů podle konkrétních provozních potřeb zákazníka. Během doby vývoje systému a jeho provozním používaní se ukázala potřeba doplnit informační systém i moduly, které umožní evidovat a vyhodnocovat obchodní a marketingové aktivity před i po realizaci obchodních případů (segment CRM) a to včetně vazeb na data z provozních systémů. Všechny tyto informace se dále využívají v modulech pro manažerské rozhodování (segment BI). Pro zachovaní celistvosti údajové základny informačního systému bylo k němu přidáno datové a modulární rozšíření soustředěné do nástavby pojmenované Market. Tuto nástavbu firma využívá i pro vlastní potřebu výměny informací a dokumentů jak pro obchodní aktivity, tak pro vnitropodnikové plánování a řízení výroby. V rámci této nástavby je podchyceno vytváření, evidence, oběh a archivování provozních, účetních, rozhodovacích a jiných dokumentů a dokladů včetně vazeb do provozního informačního systému. Práce s dokumenty v elektronickém tvaru je zajišťována programy podle nastavených asociací v prostředí Windows. U dokumentů typu Microsoft Office® (.doc, .xls) se jednoduchá vazba přes asociaci v systému, ukázala nedostačující a bylo proto požadováno vyvinout nový modul (případně moduly) pro práci s těmito typy souborů. Modul využívá objektové rozhraní pro těsnější kontrolu nad zpracovávaným dokumentem. Navrhnout a programově realizovat takové rozhraní s vazbou na existující knihovní moduly aplikace Market pro práci s dokumenty je cílem této bakalářské práce.
1.1
Principy použití dokumentu v aplikaci Market
Čím dál tím víc informací ukládáme do dokumentů různých typů. Můžeme říct, že v dnešní době máme takto uloženy většinu dat, které potřebujeme a neobejdeme se bez nich. Ne jinak je tomu i ve firmě CID International .a.s.. Důležitá data si zaměstnanci ukládají do dokumentů typu Microsoft ® Office . Pro lepší přehlednost a bezpečnost se tyto dokumenty ukládají do informačního systému Market. Informační systém Market je postaven na architektuře Klient-Server. Všechny dokumenty (např. *.doc, *.xls), které se používají v informačním systému Market, se ukládají do databáze. Pro uložení dokumentů do databáze Market byly vytvořeny dvě tabulky „Dokumenty“ a „VazDokumenty“, jejichž schéma je zobrazeno na obr. 1. V tabulce „Dokumenty“ do položky „binData“ se ukládají dokumenty (např. *.doc, *.xls), které se před uložením do databáze zkomprimují pomocí komprimačního algoritmu. Aktivity, které se provádí při ukládaní a načítání dokumentů z databáze a do databáze jsou zobrazeny na obr. 2. Když je dokument v databázi uložen jako zkomprimovaný, nelze použít fulltextové vyhledávání v dokumentu.
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
9
Dokumenty
VazDokumenty
PK,FK1
lIDDokumenty
FK1
szDokJmeno IDDokFormat szDokTyp binData lTyp IDUserAdd IDUserMod dtDatumAdd dtDatumMod szPopis lStav szStav lSmer lPristup IDKategorie rowguid szLocal szCislo dtDatum szZnacka szPopisDok szPopisZmeny lVerze IDParent lIDVazDokumenty
PK
lIDVazDokumenty IDMarketKontakty IDDokumenty IDFirma IDMarkEmail rowguid ID lTyp szLocal
Obr. 1. – Datový model databáze
Soubor je uložen jako binární zazipovaná data
Načte soubor z databáze a odzipuje ho
Vložení souboru do systémového adresáře
Asociace na otevření dokumentu
Obr. 2a) – Práce s dokumentem - načítání
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
10
Reakce na uzavření dokumentu
Uložení souboru
Zazipování souboru
Vložení souboru zpět do databaze
Obr. 2b) – Práce s dokumentem - ukládání
Při spuštění aplikace Market se objeví obrazovka, která je základem veškeré práce s Marketem a je zobrazena na obrázku obr. 3.
Obr. 3. – Okno aplikace Market
Pro práci s dokumenty uloženými v databázi Market můžeme použít tyto následující způsoby.
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
11
1.1.1 Přístup pomocí kontaktů V Marketu se můžeme dostat k dokumentům pomocí kontaktů. Obrazovka kontaktů je ukázaná na obrázku obr. 4.
Obr. 4. – Okno Kontaktů
Okno dokumentů Pro vyvolání okna „Kontaktů“ viz. obr.4 se použijí knihovny, které jsou zobrazeny v komponentovém diagramu Kontaktů viz. obr. 5. «komunikace s DB» «volá»
«spustitelný soubor» Market
SQL server na kterém běží databáze market
«komunikace s DB» «knihovna» _aMkKontakty
ADO
«DB Server» Databáze Market
«volá» «komunikace s DB» «knihovna» _aMkDokuTyp «volá»
«knihovna» _aMkDokuWord
Obr. 5. – Komponentový model Kontaktů
VŠB – TU Ostrava
Martin Čermák
VŠB – TU Ostrava
Uvolnění knihovny
Uvolnění knihovny
IsUkoncenWord()
Probíha dokud není word uzavřen. Jakmile je uzavřen, ptáme se na změny.
příjímá zprávu, zda se mají změny uložit
Uložit změny
CreateDispatch()
Instance Wordu
MS Office
Zda se mají uložit změny
_aMkDokuWord
volání knihovny()
_aMkDokuTyp
volání knihovny()
_aMkKontakty
volání knihovny()
Market
Market
Sdílení dokumentů ve stávajícím informačním systému 12
Popis událostí, vyskytující se při načítání a ukládání dokumentu, je zobrazen na sekvenčním diagramu a diagramu stavů. Sekvenční diagram je zobrazen na obr. 6. a diagram stavů je zobrazen na obr. 7.
Obr. 6. – Sekvenční diagram pro kontakty
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
Market
Volání knihovny _aMkKontakty
Kontakty
Dokument
13
Word
Volání knihovny _aMkDokuTyp
Načtení dat z DB
Uloženi souboru do temp adresáře
Volání knihovny _aMkDokuWord
Otevření dokumentu
Překreslení obrazovky
Testování, zda je dokument ještě otevřen
Je již uzavřen
Uložení dat do DB
Ukládání změn
Zda se mají uložit změny
Uvolnění souboru z temp adresáře
Uvolnění Dll z paměti
Uvolnění Dll z paměti
Obr. 7. – Diagram stavů pro kontakty
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
14
1.1.2 Přístup pomocí Požadavku Druhým způsobem přístupu k dokumentů uložených v databázi je pomocí požadavku. Na obrázku obr. 8. je zobrazena obrazovka pro přístup k dokumentu pomocí požadavku.
Obr. 8 – Obrazovka Marketu s požadavkem
Pro vyvolání okna „Požadavku“ viz. obr. 8 se použijí knihovny, které jsou zobrazeny v komponentovém diagramu Požadavků viz. obr. 9. «komunikace s DB»
«volá»
SQL server na kterém běží databáze market
«spustitelný soubor» Market
«komunikace s DB» «knihovna» _aMkPozadavek
ADO
«DB Server» Databáze Market
«komunikace s DB» «volá» «knihovna» _aEDokument «volá»
«knihovna» _aMkDokuWord
Obr. 9. – Komponentový model Požadavku
VŠB – TU Ostrava
Martin Čermák
M arket
VŠB – TU Ostrava
U volnění knihovny
volání kn ih ovn y()
U lo žit zm ěny
IsU končenW o rd
P robíhá dokud není w o rd uzavřen . Ja km ile je u zavřen , ptám e se na zm ěny .
příjím á zprávu, zd a se m ají zm ěny uložit
U ložit zm ěny
C rea teD ispa tch()
M S O ffic e Instance W ordu
Zda se m ají u lo žit zm ěny
_a M kD o kuW ord
volá ní knihovny ()
_ aE D okum ent
volání knih ovn y()
_a M kP ozadavky
M arke t
Sdílení dokumentů ve stávajícím informačním systému 15
Popis událostí, vyskytující se při načítání a ukládání dokumentu, je zobrazen na sekvenčním diagramu a diagramu stavů. Sekvenční diagram je zobrazen na obr. 10. a diagram stavů je zobrazen na obr. 11.
Obr. 10. – Sekvenční diagram požadavku
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
Market
Volání knihovny _aMkPožadavky
Požadavky
Dokument
16
Word
Volání knihovny _aEDokument
Načtení dat z DB
Uloženi souboru do temp adresáře
Volání knihovny _aMkDokuWord
Otevření dokumentu
Překreslení obrazovky
Testování, zda je Dokument ještě otevřen
Je již uzavřen
Uložení dat do DB
Ukládání změn
Zda se mají uložit změny
Uvolnění souboru z temp adresáře
Uvolnění Dll z paměti
Uvolnění Dll z paměti
Obr. 11. – Diagram stavů pro požadavek
Většina dokumentů, ať se již jedná o soubor typu Word nebo Excel, vzniká na základě požadavku. Vznik dokumentu na základě požadavku je ukázán na obrázku obr. 12.
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
17
Na začatku stojí požadavek
Vytvoří se dokument
Probíhá analýza
Připomínky k analýze
Schválení analýzy
Předání prográmatorovi
Konzultace analyzy s analytikem
Jsou připominky
Nejsou připomínky
Programovaní požadavku
V průběhu programování se vyskytly další připomínky
Nejsou připomínky
Vytvoření modulu
Nové požadavky k modulu
Zákazník si požádal o úpravy modulu
Obr. 12 – Diagram vzniku dokumentu
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
1.2
18
Práce s Office dokumenty
Firma CID měla vytvořen modul pro otevírání dokumentu a reakci na jeho uzavření. Tento modul pracoval na principu vyvolávání procesů a následně čekal na ukončení tohoto procesu. Tento modul byl funkční pouze pro situaci: otevření jednoho dokumentu. Jakmile uživatel otevřel více Office dokumentů, dokumenty se otevřely v jednom okně a modul nebyl schopen rozpoznat uzavření jednotlivých Office dokumentů. Aplikace Microsoft Office® (např. Word) pracují na základech MDI okna. To znamená jedna aplikace otevře více oken, v kterých je otevřeno více dokumentů. Původní modul neměl přístup k instancím jednotlivých dokumentů, a proto nemohl rozpoznat uzavření jednotlivých dokumentů otevřených v MDI okně. Vyvstal tedy požadavek vyvinout modul, který každý nově otevřený dokument otevře v novém okně a dokáže rozpoznat uzavření jednotlivých Office dokumentů uživatelem. Po složitém hledání možností, jak vyřešit tento problém, se firma rozhodla řešit tento problém s pomocí COM technologie.
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
19
2 Technologie COM 2.1
Úvodní seznámení s touto technologií
Technologie COM je dílem světoznámé firmy Microsoft. Má za sebou dlouhou dobu vývoje. Její začátky se datují v technologii OLE, která dokázala inteligentně vkládat dokumenty jednotlivých aplikací do jiných aplikací. To bylo sice možné již dříve, ale pomocí OLE jsme mohli kromě dat vkládaných do dokumentů uchovávat i informace o jeho zdroji. Tato technologie se samostatně objevila poprvé až v roce 1995. Od té doby sledujeme její mohutný rozvoj a uplatnění. Ukázalo se, že za její pomoci se rozsáhlé projekty stávají jednoduššími a snáze se dají řešit mnohé problémy. Tímto ale rozvoj komponentové tvorby softwaru neskončil. Objevila se snaha jednotlivé komponenty distribuovat pomoci počítačových sítí. Toto již bylo na jiných platformách vyřešeno. Proto se firma Microsoft rozhodla implementovat RPC, neboli Vzdálené volání procedury do svých systémů. RPC bylo použito pro základní volání vzdálených funkcí distribuovaných komponent. Diky tomu v roce 1996 vznikla distribuovaná verze COM nazvaná jako DCOM. Komponenty COM a DCOM jsou převážně používány ve velkých systémech, kde velká část kódu je věnovaná otázce spolehlivosti, ověřování bezpečnosti a řízení selektivního přístupu. Velmi často se současně řeší obsluha několika klientů a z toho vyplývají problémy synchronizace a přístupu ke sdíleným datovým zdrojům. Na základě COM, DCOM, MTS (řeší problémy současné obsluhy více klientů, řízení bezpečnosti, ošetřování chybových stavů) a MSMQ (umožňuje zapisovat požadavky na straně klienta do fronty a přenášet na server) vznikla technologie COM+. COM+ není pouhou integrací těchto technologií,ale současně tyto technologie vylepšuje.
2.1.1 Základní myšlenka Komponentová technologie je postavena na binárně nezávislém komunikačním nástroji nazvaném Rozhraní neboli Interface. Rozhraní nám představuje datový typ, v němž deklarujeme seznam funkcí, které bude klient smět využívat. Je třeba zdůraznit, že rozhraní je pouze seznamem funkcí, není zde jejich definice a rozhraní neobsahuje ani žádná členská data, která by způsobila nekompatibilitu. Využíváme také datovou strukturu Třída, která realizuje vlastní komponentu. Tato třída implementuje již dříve definované rozhraní. Třída také implementuje svá členská data a vlastní funkce, pokud je potřebuje, a hlavně obsahuje implementaci všech funkcí, které jsou definovány v rozhraní. Obecně můžeme říci, že rozhraní stanovuje způsob komunikace, mezi libovolnou komponentou a klientem. Konkrétní komponenta dává jednotlivým funkcím rozhraní určitou podobu. Základem je pravidlo, kterým stanovujeme, že klientské aplikaci poskytujeme pouze informace o rozhraní a ne o konkrétní implementaci. Klient tedy nemá možnost zjistit jak vypadá implementace a je schopen vyvolávat pouze funkce rozhraní, které jsme mu poskytli. Tímto popsaným postupem se vyřešily mnohé problémy, které se vyskytovaly v minulosti.
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
20
2.1.2 Úvodní deklarace rozhraní Rozhraní se v každém programovacím jazyce tvoří různě. Společné pro všechny jazyky je výsledný produkt – binárně kompatibilní rozhraní. Binární kompatibilitou rozumíme fakt, že vytvořené komponenty mohou být po svém přeložení používány jakýmikoliv klienty nezávisle na jazyce, v kterém byli napsány. V programovacím jazyce C++ říkáme, že rozhraní je abstraktní třída, která obsahuje pouze čisté virtuální funkce. Třídám, které nemají žádná členská data a ani žádnou implementaci deklarovaných funkcí, říkáme Čistá abstraktní třída, Protokolová třída, nebo jednoduše Interface. Na ukázku si vytvoříme jedno rozhraní IAuto a třídu komponenty Auto implementující vytvořené rozhraní. class IAuto { public: virtual int pocetKol() };
const = 0;
class Auto : public IAuto { int m_pocK; public: … int pocetKol() const { return m_pocK;} };
Každá třída obsahující virtuální funkce, má definovanou tabulku virtuálních funkcí. Existuje vždy pouze jedna pro daný typ třídy a je sdílena všemi instancemi třídy. Každá instance třídy obsahuje ukazatel na tabulku a ten často označujeme zkratkou vptr. Tabulka obsahuje ukazatele na implementace všech virtuálních funkcí v dané třídě.
2.1.3 Skutečné rozhraní COM U komponenty musíme zařídit, aby si sama řídila svoji životnost na základě počtu připojených klientů. Pro tento účel bylo v technologii COM vytvořeno rozhraní IUnknown. Díky jeho dvou funkcí AddRef a Release můžeme velmi jednoduše řídit životní cyklus instance komponenty. Třetí funkcí rozhraní IUnknown je QueryInterface, která nám dává ukazatel na rozhraní v případě, že komponenta implementuje více rozhraní. Skutečná deklarace rozhraní IAuto bude vypadat takto: class IAuto : public IUnknown { public virtual HRESULT __stdcall pocetKol(int *vysledek) = 0; };
Rozhraní IAuto nazýváme Uživatelské rozhraní, protože výčet metod je pouze na programátorovi. Vedle uživatelských rozhraní máme v COM, DCOM i v COM+ mnoho připravených rozhraní, jako je třeba IUnknown. Většina uživatelských rozhraní je přímo či nepřímo odvozena od IUnknown. Když porovnáme původní návrh rozhraní IAuto se skutečným, vidíme že funkce pocetKol doznala malých změn. Prvním rozdílem je návratová hodnota HRESULT. Tato hodnota je 32 bitové číslo, které definuje, zda funkce uspěla či selhala. Používá se u většiny funkcí rozhraní.
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
21
Druhým rozdílem je použití volací konvence, kterou určujeme klíčovým slovem __stdcall. Volací konvence nám určuje, jak budou funkci (pocetKol) předávány parametry a kdo bude parametry ze zásobníku odstraňovat. V jazyce C a C++ musíme vždy použít klíčové slovo __stdcall, protože implicitní volací konvence je __cdecl, která ovšem není kompatibilní s ostatními programovacími jazyky. Tím bychom porušili binární kompatibilitu rozhraní a nemohla by být použita široká množina jazykově odlišných klientů, o kterou se snažíme.
2.1.4 Krátké seznámení s jazykem IDL Jazyk IDL byl používán pro popis datových typů a funkcí, jež byli spouštěny mimo počítač klienta. Poté ho firma Microsoft nejenom přejala, ale i rozšířila jeho schopnosti o možnosti popisu objektových rozhraní COM komponent. Jazyk IDL se používá pouze pro popis konstant, datových struktur, funkcí a rozhraní, které budou použity pro tvorbu COM komponent. Popis rozhraní v IDL jazyce je tedy jen pojítkem mezi aplikací klienta a aplikací komponenty. Pro malou ukázku si ukážeme, jak bychom napsali naše rozhraní IAuto v IDL jazyce: import “unknnw.idl”; [object, uuid (123445678-1234-0000-abcd-00000000001)] interface IAuto:IUnknown { HRESULT pocetKol([out, retval] int *pcVysledek); }
Vidíme, že definice rozhraní se skládá: a) z atributů rozhraní uvedených v hranatých závorkách, kde definice vždy začíná klíčovým slovem object; poté následuje další klíčové slovo uuid, pomocí něj je každému rozhraní přiděleno jedinečné 128 bitové číslo, které je zapsáno pomocí hexadecimální konstanty uvedené v kulatých závorkách. Toto číslo je označované zkratkou GUID. b) z klíčového slova interface, názvu rozhraní, dvojtečky a jména rodičovského rozhraní, které je v našem případě IUnknown. Jelikož rozhraní IUnknown existuje, je třeba na začátek IDL souboru vložit jeho definici tedy příkaz import “unknnw.idl” c) ze složených závorek které obsahují deklarace funkcí rozhraní, doplněnými mnohými atributy, uvedených v hranatých závorkách. Úplný výčet všech atributů můžeme najít v oficiální dokumentaci firmy Microsoft.
2.1.5 Rozhraní IUnknown IUnknown je rozhraní, které musí každá COM komponenta bezpodmínečně implementovat, nezávisle na tom kolik dalších rozhraní daná komponenta implementuje. IUnknown je rozhraní, na které se může každý klient spolehnout. Je základním rozhraním pro tvorbu většiny dalších rozhraní každého COM objektu. Toto rozhraní obsahuje sice jenom tři metody, ale přítomnost rozhraní je pro každý objekt životně důležitá a nepostradatelná. Jak jsme si už ukázali, životnost objektu sledujeme na základě počtu referencí. Tedy základní princip spočívá v myšlence u každé instance objektu počítat kolik má klientů, nebo kolikrát se
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
22
tentýž objekt odkazuje na danou instanci. To znamená, že klient neprovádí zrušení objektu v paměti počítače, nýbrž mu pouze oznámí, že instanci nehodlá dále používat. Poté co to provedou všichni klienti, objekt u poslední instance zjistí, že není dále potřebný a sám provede svou destrukci. K tomu, aby toho byl klient schopen obsahuje, rozhraní IUnknown funkce AddRef a Release. Všechny objekty musí ještě obsahovat celočíselné počítadlo, které bude obsahovat počet aktuálních odkazů všech připojených klientů. Funkce AddRef inkrementuje počítadlo referencí všech připojených klientů a vrací aktuální počet klientů. Funkce Release dekrementuje počítadlo referencí. Pokud počítadlo dosáhne hodnoty nula, objekt se sám odstraní z paměti. Tato funkce opět vrací aktuální stav počítadla referencí. Poslední funkcí rozhraní IUnknown, jak jsme se už zmínili, je QueryInterface. Tato funkce slouží k dotazu na existující rozhraní, které je identifikováno prvním parametrem. Pokud objekt toto rozhraní implementuje, vrátí na něj odkaz ve druhém parametru. Pokud ovšem objekt neimplementuje toto rozhraní, návratová hodnota funkce má hodnotu E_NOINTERFACE a druhý parametr má neplatnou hodnotu. Tato funkce dává klientovi možnost získat novou referenci, ale také zjistit, zda komponenta zvolené rozhraní podporuje. Po popsání funkce jednotlivých funkcí rozhraní IUnknown je třeba si uvědomit dvě věci. První, že po vytvoření nového odkazu na libovolné rozhraní je třeba bezprostředně volat funkci AddRef. A druhá, že pokud chceme aby odkaz na rozhraní zaniknul, musí se před jeho zánikem volat funkce Release. Teď si ukážeme jak by měli jednotlivé funkce rozhraní vypadat na třídě Auto. class Auto : public IAuto { public: //Funkce rozhraní IUnknown ULONG __stdcall AddRef(); ULONG __stdcall Release(); HRESULT __stdcall QueryInterface(REFIID riid, void** ppv); //Funkce rozhraní IAuto HRESULT __stdcall pocetKol(int *vysledek); private: ULONG m_cRef; }; //funkce AddRef která zvyšuje počítadlo referencí ULONG Auto :: AddRef() { return ++m_cRef; } //funkce Release, která snižuje počítadlo referencí a pokud //proměnná m_cRef je rovna nule odstraní objekt z paměti ULONG Auto :: Release() { if(--m_cRef != 0) //test jestli to není poslední reference return m_cRef; else { //je to poslední reference, samodestrukce delete this; return 0; } }
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
23
//Funkce QueryInterface, která na základě IID rozhraní přiřazuje //ukazateli ppv korektně přetypovaný ukazatel this HRESULT Auto :: QueryInterface(REFIID riid, void** ppv) { if(riid == IID_IUnknown) { //implementuje jen IUnknown *ppv = (IUnknown*)this; } else if(riid == IID_IAuto) { //implementuje IAuto *ppv = (IAuto*)this; } else { *ppv = NULL; return E_NOINTERFACE; //konstanta signalizující } //neexistující rozhraní } //Funkce z rozhraní IAuto, pouze pro ilustraci HRESULT Auto :: pocetKol(int *vysledek) { *vysledek = 4; //na ukazku uložíme hodnotu čtyři return S_OK; //konstanta signalizující vše je OK }
Máme tedy představu jak může deklarace komponenty vypadat.
2.1.6 Vytvoření objektu a jeho používání Prvním krokem je inicializace COM knihovny a to provedeme voláním jedné ze dvou funkcí. První funkce HRESULT CoInitializeEx( void pvRes, DWORD dwCoInit) inicializuje COM knihovnu, abychom byli potom schopni využít její služeb. První parametr je rezervován pro budoucí použití, ale teď ho musíme vždy nastavit na hodnotu NULL. A druhý parametr nastavíme na jednu z konstant, buď COINIT_APARTMENTTHREADED, nebo COINIT_MULTITHREADED, které určují použitý threadový model. Druhá funkce HRESULT CoInitialize ( void * ) je jednoduší varianta funkce CoInitializeEx a její použití je ekvivalentní s voláním CoInitializeEx(NULL,
COINIT_APARTMENTTHREADED); Tak jak musíme na začátku každého programu volat funkci CoInitialize nebo
CoInitializeEx, musíme na jeho konci volat funkci CoUninitialize. Její použití je jednoduché, protože tato funkce nemá žádné parametry. Jejím úkolem je provést určitý úklid po používané COM knihovně, jako je například uvolnění systémových, zdrojů nebo ukončení všech RPC spojení. Jedna z možností, jak vytvořit objekt, je použít funkci CoCreateInstance. Je to jedna z nejjednodušších a nejpoužívanějších funkcí. Její deklaraci máme tady: HRESULT CoCreateInstance( REFCLSID rclsid, LPUNKNOWN pUnkOuter, DWORD dwClsContext, REFIID riid, LPVOID *ppv);
Význam parametru je uveden v tabulce Tab.1. Parametr Význam rclsid pUnkOuter
Identifikátor COM třídy, jejíž instanci chceme vytvořit. Často označovaný zkratkou CLSID. CLSID je GUID. Ukazatel na rozhraní IUnknown. Pokud má ukazatel hodnotu NULL, zakládáme instanci běžným způsobem. Pokud to není NULL provádíme
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
dwClsContext
riid ppv
24
takzvanou agregaci objektu. Hodnota definuje tzv. kontext, v jakém bude spuštěna. Určuje tedy, zda bude komponenta spouštěna jako součást klientského, nebo svého vlastního procesu. Také určuje, jestli půjde o lokální nebo vzdálenou komponentu. GUID rozhraní , které chceme získat po vytvoření instance komponenty. Výstupní parametr funkce obsahující ukazatel na rozhraní požadované předchozím parametrem. Tab.1: Význam parametrů funkce COCreateInstance
Podívejme se na jednotlivé parametry trochu podrobněji. Při vytváření instance komponenty potřebujeme dvě GUID čísla. Jedno pro rozhraní a druhé pro samotný objekt, jehož instanci chceme vytvořit. Problém s pojmenováním objektu je stejný jako u rozhraní. Jméno „Auto“ je sice pěkně čitelné, ale nemusí být jedinečné. Proto používáme GUID i pro označení objektu, kterému se říká CLSID z angličtiny „Class Identifier“. První parametr je právě CLSID. COM využívá informace zapsané v Registrech k zavedení komponenty do paměti počítače a vytvoření instance objektu. Pro určení binárního souboru, který komponentu obsahuje, je třeba použít klíč HKEY_CASSES_ROOT\CLSID\, ve kterém jsou uvedena všechna CLSID komponent nainstalovaných v operačním systému. Pod každým klíčem je ukryto mnoho informací o konkrétním objektu. Nejdůležitější informací je jméno DLL knihovny, nebo EXE souboru, ve kterém se nachází kód komponenty. Druhý parametr určuje, zda je základní objekt vytvořen samostatně; pak má hodnotu NULL, nebo je interní součástí jiného objektu, který ho agreguje. Třetí parametr je podstatně složitější. Jeho hodnota určuje, jak bude komponenta zavedena do paměti. Komponenta musí být pro zvolený postup správně vytvořena. Jednotlivé možnosti si ukážeme v tabulce Tab. 2, kde v prvním sloupci je uvedena konstanta, kterou můžeme použít místo třetího parametru. Druhý sloupec zobrazuje odpovídací klíč z Windows Registry. Poslední sloupec je vysvětlení jednotlivé volby. Konstanta Klíč Registry Popis CLSCTX_INPROC_SERVER
InproceServer32
Komponenta bude zavedena do adresního prostoru klientské aplikace. V klíči Registry je uvedena plná cesta k DLL knihovně CLSCTX_LOCAL_SERVER LocalServer32 Komponenta bude zavedena do svého vlastního paměťového prostoru v nově vytvořeném procesu. V Registry je uložená plná cesta k EXE programu. CLSCTX_REMOTE_SERVER AppID Komponenta bude zavedena do svého vlastního paměťového prostoru, ale v procesu na jiném počítači. CLSCTX_INPROC_HANDLER InprocHandler32 Jde o speciální konstantu pouze pro COM+ komponenty. Tab.2: Konstanty pro druhý parametr funkce CoCreateInstance
Poslední dva parametry jsou spjaty s požadovaným rozhraním, které chceme získat, abychom mohli s objektem pracovat. V našem případě můžeme přímo požádat o rozhraní IAuto, ale často se doporučuje požádat o rozhraní IUnknown. Pokud bychom si ovšem nebyli jisti, jaké rozhraní objekt implementuje, jistotou v každém případě je rozhraní IUnknown. Tímto dostaneme
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
25
komponentu do paměti a poté můžeme opakovaně používat metodu QueryInterface, abychom zjistili, jaké rozraní komponenta implementuje. Čtvrtým parametrem je IID požadovaného rozhraní a pátý parametr je ukazatel na toto rozhraní, samozřejmě pokud existuje. Úspěšnost zavedené komponenty a existence rozhraní signalizuje návratová hodnota typu HRESULT. Teď si pro malou ukázku ukážeme jak by tento kód mohl vypadat. #define _WIN32_DCOM #include
#include „komponenta\Auto.h“
//Generováno překladačem
const CLSID CLSID_Auto = {0x12345678, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}}; void main() { IUnknown* pUnknown; IAuto* pAuto; HRESULT hr = CoInitialezeEx(NULL, COINIT_APARTMENTTHREADED); if(FAILED(hr) cout << „Nelze inicializovat“ << endl; hr = CoCreateInstance(CLSID_Auto, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**) &pUnknown); if(FAILED(hr) cout << „Nelze zalozit instanci komponenty.“ << endl; hr = pUnknown->QueryInterface(IID_IAuto, (void**) &pAuto); if(FAILED(hr)) cout << “Komponenta nepodporuje rozhrani IAuto.”; pUnknown->Release(); int cislo = 0; hr = pAuto->pocetKol(&cislo); if(SUCCEEDED(hr)) cout << “Pocet kol osobniho auto je: “ << cislo << endl; pAuto->Release(); CoUnitilializace(); }
Podle mého názoru je tato ukázka dostatečně názorná a je na ni vidět základní použití jednotlivých funkcí, které jsme si popisovali v předchozích odstavcích.
2.1.7 Dědičnost a polymorfizmus V technologii COM mluvíme o tzv. implementační dědičnosti. Implementační dědičnost má vedle výhod, jako jsou opakované využití kódu a základ pro realizaci polymorfizmu, i závažné nevýhody. První nevýhodou je striktní závislost na použitém programovacím jazyce. Druhou nevýhodou je problematické chování metod základní třídy. Z toho vyplývá, že implementační dědičnost není v COM technologiích vhodná, protože nepodporuje jazykovou nezávislost a vytváří potenciálně nestabilní kód.
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
26
S dědičností v objektově orientovaném programování jde ruku v ruce polymorfizmus. Polymorfizmus v COM chápeme trochu jinak, než v objektově orientovaných jazycích. Rozdíl je v jeho implementaci. V C++ a dalších jazycích se polymorfizmus realizuje pomocí dědičnosti tříd, ale u COM komponent se tato vlastnost realizuje implementací společné množiny rozhraní. V COM technologii se doporučuje, aby rozhraní nedědila navzájem od sebe, ale aby dědila od základního rozhraní IUnknown. Tím dostaneme více nezávislých rozhraní pro implementaci různých komponent. Tímto způsobem se vyhneme mnoha nepříjemnostem a ušetříme spoustu času. V této kapitole si ještě popíšeme, jak z existujících komponent vytvořit komponenty nové. K tomu máme dvě techniky umožňující využívaní existujících komponent pro tvorbu nových. Tyto techniky se v angličtině jmenují Containment a Aggregation. Containment je jednodušší z představovaných technik. Využívá existujících komponent a pro svou jednoduchost je často využívaná. Vnější komponenta vytvoří interně instanci vnitřní komponenty klasickým způsobem a začne využívat jejich služeb. Aby to ovšem fungovalo, musí vnější komponenta kromě svých rozhraní ještě implementovat všechna rozhraní vnitřní komponenty. Agregace je druhou metodou, která využívá existujících komponent jako součásti jiné komponenty. U agregace opět vnější objekt využívá vnitřní objekt, avšak nevystupuje jako prostředník pro volání metod rozhraní vnitřní komponenty. Vnitřní komponentě je tedy umožněno přímo vystavit svá rozhraní navenek. Agregace má své výhody, ale i své nevýhody. Jedna z výhod je urychlené volání metod vnitřního rozhraní. Velkou nevýhodou je její obtížná implementace, kde je velký problém v tom, že vnitřní komponenta absolutně nic neví o komponentě vnější.
2.2
Automation
Komunikace mezi klientskou aplikací a komponentou je založena na schopnosti porozumět na obou stranách binárnímu rozhraní. To znamená, že obě komunikující strany musí využívat tabulky virtuálních funkcí. Toto jsme si popisovali až doposud. V COM technologii však existuje ještě další metoda komunikace, označovaná jako Automation, dříve jako OLE Automation, které využívá univerzální rozhraní IDispatch. Uvedenou techniku komunikace často využívají aplikace jako jsou programy rodiny Microsoft Office nebo skriptovací jazyky. Rozhraní mohou být sestavena z nekonečné množiny různých funkcí s různými parametry. Komponenty proto nabízely vedle těchto rozhraní i rozhraní IDispatch. Jeho IID i množina funkcí je napevno daná, jako to je u rozhraní IUnknown. Druhým důvodem zavedení Automation je důležitá podpora COM technologie pro skriptovací jazyky. V posledních letech vzrostla popularita skriptovacích technologiích realizovaných pomocí internetových jazyků. Většinu skriptů můžeme napsat i jako součást HTML nebo ASP stránek. Nemalou množinu aplikací tvoří programy napsané ve Visual Basic for Application, které se mohou stát součástí dokumentů popřípadě sešitů v Microsoft Wordu nebo Excelu. Technika Automation není úplně odlišná od principů COM. Její princip je postaven na základních kamenech položených technologií COM. Komponentě COM implementující rozhraní IDispatch se také říká Automation server, který poskytuje klientům různé služby. Na rozdíl od uživatelsky definovaných rozhraní nejsou funkce serveru volány přímo, ale přes mechanizmus zabudovaný přímo ve funkcích rozhraní IDispatch.
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
27
2.2.1 Rozhraní IDispatch Je to jediné rozhraní, které musí komponenta implementovat, samozřejmě vedle IUnknown, aby mohla poskytnout veškerou funkcionalitu Automation. Toto rozhraní definuje pouze čtyři funkce ( GetTypeInfoCount, GetTypeInfo, GetIDsOfNames a Invoke). Základní myšlenka chovaní tohoto rozhraní spočívá ve funkci zprostředkovatele volání mezi komponentou a klientem. Rozhraní samotné neobsahuje žádné specifické funkce, které hodlá klient volat. Skutečné funkce jsou na straně komponenty očíslovány pomocí jedinečných čísel, které nazýváme DISPID – Dispatch identifier. Nejdůležitějšími funkcemi rozhraní jsou GetIDsOfNames a Invoke. Klientská aplikace zná jedině jméno volané funkce a toto jméno dá funkci GetIDsOfNames jako její parametr. Funkce vyhledá v tabulce, která je uložená v komponentě, DISPID odpovídající zadanému jménu funkce a vrátí ji klientovi. Klient se tedy dozvěděl jakou funkci bude ve skutečnosti volat. S hledaným DISPID a množinou parametrů následovně vyvolá funkci Invoke. Ta na základě DISPID spustí konkrétní implementaci požadované funkce. Funkce Invoke používá indexovou tabulku ukazatelů na jednotlivé funkce. Aby ke komponentě mohli přistupovat všechny typy klientů, je nutné, aby komponenta implementovala rozhraní IDispatch. Existuje ale ještě další implementace Automation rozhraní, nazvané jako Duální rozhraní (Dual Interface). Komponenta, která implementuje duální rozhraní, umožňuje klientům využívat IDispatch stejně jako uživatelsky definované rozhraní. Realizace techniky Automation za pomoci duálního rozhraní je mnohem obvyklejší než předchozí metoda pracující s čistým rozhraním IDispatch. Duální rozhraní umožňuje přirozený přístup k funkcím komponenty. Nepochybnou výhodou duálního rozhraní je i možnost volat funkce rychlejším způsobem.
2.2.2 Návrh rozhraní IDispatch v komponentě Použití rozhraní IDispatch v komponentně musíme předem promyslet, pokud budeme implementovat čisté nebo duální rozhraní. Naše rozhodnutí ovlivní způsob, jak budeme definovat rozhraní v IDL souboru. Volba hlavně závisí na množině klientů, kteří budou komponentu využívat. Jeli komponenta určena pouze pro skriptovací jazyky, je použití duálního rozhraní zbytečné, ale pokud je jen malá šance využít komponentu v C++, je lepší využít duální rozhraní. Pokud se rozhodneme implementovat čisté rozhraní IDispatch v IDL souboru, použijeme na jeho definici klíčové slovo dispinterface. Tímto slovem se rozhraní často označuje. [uuid(12345678-0000-0000-0000-000000000001)] dispinterface IAuto { [id(1), propget] HRESULT pocetKol([out, retval] int *pcVysledek); }
Při definici rozhraní dispinterface musíme dodržet několik základních pravidel, které jsou: 1. V atributech rozhraní nemusíme uvádět klíčové slovo object. 2. Rozhraní ve své definici není odvozeno od IUnknown, ale klíčové slovo dispinterface zajistí potřebnou dědičnost. 3. Každá funkce je očíslovaná atributem id. Číslovaní musí být v rámci rozhraní jedinečné. Přiřazené číslo nazýváme DISPID.
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
28
4. Rozhraní může definovat tak zvané properties neboli vlastnosti. 5. Deklarované funkce mohou obsahovat jenom Automation. Poněkud jinak bude vypadat definice duálního rozhraní, které v mnohém připomíná uživatelské rozhraní. Definice opět začíná přiřazením atributů jako je například uuid a přidáme klíčové slovo
dual, které je pro duální rozhraní zásadní. Důležitá je také dědičnost ze základního rozhraní IDispatch. Tím pádem vzniká tabulka virtuálních funkcí, která obsahuje funkce rozhraní IUnknown, IDispatch a dalších rozhraní, které přidáme. [object, uuid (123445678-1234-0000-abcd-00000000001), dual ] interface IAuto : IDispatch { [id(1), propget] HRESULT pocetKol([out, retval] int *pcVysledek); }
2.2.3 Implementace rozhraní IDispatch Způsob implementace se může značně lišit. Jak jsme si řekli, programátor v C++ musí napsat implementaci všech funkcí sám. Může si ale zvolit způsoby realizace funkcí rozhraní IDispatch od nejjednodušších až po velmi složité. Je tedy nutné zvážit známý poměr cena / výkon. Ted si ukážeme, jak by vypadala naše třída Auto v Automation. class Auto : public IAuto { public: //Funkce rozhraní IUnknown ULONG __stdcall AddRef(); ULONG __stdcall Release(); HRESULT __stdcall QueryInterface(REFIID riid, void** ppv); //funkce rozhraní IDispatch HRESULT __stdcall GetTypeInfoCount(UINT* pCountTypeInfo); HRESULT __stdcall GetTypeInfo(UINT iTypeInfo, LCID lcid, ITypeInfo** ppITypeInfo); HRESULT __stdcall GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rhDispId); HRESULT __stdcall Invoke(DISPID dispIdMember, RERIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr); //Funkce rozhraní IAuto HRESULT __stdcall pocetKol(int *vysledek); private: ULONG m_cRef; ITypeInfo* m_pTypeInfo;
//počítadlo referencí //ukazatel na typové informace
};
Implementaci funkcí rozhraní IUnknown již známe. Proto si teď popíšeme jednotlivé funkce rozhraní IDispatch.
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
29
Funkce GetTypeInfoCount je volána klientem, pokud je komponenta schopna poskytnout typové informace. Výsledkem je jednoznačná odpověď ano / ne určené hodnotami 1 nebo 0. Důvodem proč může klient požádat o typové informace je realizace tzv. early binding (brzké vazby). Brzká vazba je opakem late binding (pozdní vazby). Když program zakládá instanci komponenty, musí ověřit, zda objekt existuje a jestli jsou funkce nebo vlastnosti použity správně. Brzká vazba nastává v době kompilace kódu a může urychlit jeho vykonávání. Pozdní vazba nutí program zjišťovat potřebné informace opakovaně v průběhu programu. Implementace funkce GetTypeInfoCount je velmi jednoduchá a vypadá takto: HRESULT Auto:: GetTypeInfoCount(UINT* pCountTypeInfo) { *pCountTypeInfo = 1; Return S_OK; }
Funkce GetTypeInfo je vyvolána, jakmile aplikace zjistí, že server je schopen poskytovat typové informace. Jejím účelem je vrátit ukazatel na typové informace o implementovaném rozhraní. Funkce získá v prvním parametru číslo rozhraní, jehož typové informace jsou vyžadovány. Jelikož naprostá většina komponent implementuje pouze jeden dispinterface, je platná hodnota 0. Druhý parametr určuje tzv. locale identifier, určující jazykovou variantu typových informací. Třetí parametr je ukazatel na poskytnutý výsledek. Implementace této funkce by mohla vypadat takto. HRESULT Auto:: GetTypeInfo(UINT iTypeInfo, LCID lcid, ITypeInfo** ppITypeInfo) { *ppITypeInfo = NULL; if(iTypeInfo != 0) return DISP_E_BADINDEX; m_pTypeInfo->AddRef(); *ppITypeInfo = m_pTypeInfo; Return S_OK; }
Funkce GetIDsOfNames je jedna ze složitějších funkcí tohoto rozhraní. Jejím úkolem je přeložit jméno volané funkce na DISPID, neboli najít ve zvolené datové struktuře zmíněnou asociaci. Funkce má více parametrů a proto si je shrneme v tabulce Tab. 3. Parametr Význam riid rgszName cNames lcid rgDispld
Parametr není používán a musí mít vždy hodnotu IDD_NULL Aby klient nemusel opakovaně volat tuto metodu, je možné najednou zadat pole jmen, pro které klient hledá odpovídající DISPID Tento parametr vyjadřuje počet položek v parametru rgszName Obsahuje konstantu popisující národní prostředí. Je pole s výslednými DISPID Tab. 3: Parametry funkce GetIDsOfNames
Vlastní implementace této funkce je plně v rukou programátora. Jestliže je implementace složitá můžeme využít tuto implementaci: HRESULT Auto:: GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cNames, LCID lcid, DISPID* rgDispId) { if(riid != IID_NULL) return DISP_E_UNKNOWNINTERFACE; return DispGetIDsOfNames(m_pTypeInfo, rgszNames, cNames, rgDispId); }
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
30
Funkce Invoke je poslední metodou rozhraní, která slouží ke spuštění funkcí na základě předaného DISPID. Její algoritmus najde v paměti implementaci požadované funkce a spustí ji. Její parametry si znovu vysvětlíme v tabulce Tab. 4. Parametr Význam displdMember riid Lcid wFlags
V parametru je uložena hodnota DISPID volané funkce. Parametr je rezervován pro budoucí použití a musí mít hodnotu IID_NULL. Stejně jako u předchozích metod parametr udává zvolené národní prostředí Parametr může nabývat pouze hodnot daných konstantami:
DISPATCH_METHODE – klient volá metodu rozhraní DISPATCH_PROPERTYGET – klient čte hodnotu vlastností DISPATCH_PROPERTYPUT – klient zapisuje do vlastností DISPATCH_PROPERTYGETREF – vlastnost je změněna přiřazením reference místo hodnoty. pDispParams pVarResult pExcepInfo puArgErr
Jde o ukazatel na strukturu nazvanou DISPARAMS, obsahující parametry předávané metodě. V parametru je uložena návratová hodnota. Není to přímo návratová hodnota funkce, ale jde o hodnotu parametru s atributem [out, retval]. Parametr slouží k předání rozšířeného chybového hlášení. Obsahuje přesné informace o vzniklé chybě. Parametr umožňuje klientské aplikaci zjistit, proč nastala chyba při volání metody Invoke. Nedáme-li dostatečný počet parametru nebo je parametr špatného typu, není funkce zpuštěna a je vrácen chybový kód DISP_E_TYPEMISMATCH nebo DISP_E_PARAMNOTFOUND. Tab. 4: Parametry funkce Invoke
Podobně jako u předchozí funkce můžeme volit mezi úplnou implementací a použitím již existující funkce DispInvoke, která je jednodušší variantou. Vše má své výhody a nevýhody. Jednodušší varianta nepodporuje rozšířené zpracování chyb a klientská aplikace nemůže využít předposledního parametru funkce. Následující kód ukazuje tělo funkce Invoke v jednodušší variantě. HRESULT Auto:: Invoke(DISPID dispIdMember, RERIID riid, LCID lcid, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO* pExcepInfo, UINT* puArgErr) { if(riid != IID_NULL) return DISP_E_UNKNOWNINTERFACE; return DispInvoke(this, m_pTypeInfo, dispIdMember, wFlags, pDispParams, pVarResult, pExcepInfo, puArgErr); }
2.2.4 Detekce chyb Při použití libovolné komponenty může vzniknout mnoho chybových stavů. Jejich původ může být v těle funkcí samotných, jako je výskyt nepovolené hodnoty parametru, popřípadě nedostatek operační paměti pro vykonání algoritmu. Druhým velkým zdrojem chyb jsou služby COM, používané například pro vytvoření instance komponenty. Pokud není komponenta správně zaregistrovaná, nemůže dojít k jejímu zavedení do paměti a klient se musí o tomto dozvědět. Jednou z dalších chyb bývá nepovolený přístup ke vzdálenému počítači při použití technologie
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
31
DCOM. Chyb je opravdu velké množství a o všech stavech komponenty musí být klientská aplikace informována. Všechny funkce rozhraní vracejí hodnotu typu HRESULT. Stejný typ návratové hodnoty má většina podpůrných COM API funkci, jako jsou CoCreateInstance nebo
CoInitialize. Vrácení chybového stavu je lety prověřená metoda. Abychom mohli dekódovat chybové stavy, musíme těmto stavům porozumět. Analýza chybového kódu je velmi slabým místem uvedené techniky. Velmi často vede k zdlouhavým porovnáváním návratové hodnoty s různými konstantami, abychom dospěli k výsledku. Rozluštění obsahu návratového kódu může být jednoduchou záležitostí, ale i velmi dlouhým a časově náročným kódem. Záleží na hloubce informace, kterou se potřebujeme dozvědět. Pokud nám postačí jednoduchá odpověď, můžeme využít makropříkazy SUCCEEDED a FAILED. HRESULT hr = CoInitialezeEx(NULL, COINIT_APARTMENTTHREADED); if(FAILED(hr) cout << „Klient: Nelze inicializovat apartment.“ << endl; hr = CoCreateInstance(CLSID_Auto, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, (void**) &pUnknown); if(FAILED(hr) cout << „Klient: Nelze zalozit instanci komponenty.“ << endl;
Jestliže však potřebujeme zjistit podrobnější původ chyby, musíme individuálně testovat návratovou hodnotu funkce a to je velmi obtížné. Pokud chceme pouze informovat uživatele komponenty o příčinách chyby, můžeme použít funkci Win32 API, nazvanou FormatMessage. Funkce překládá chybový kód na textový popis, který můžeme dále použít. if(HRESULT_FACILITY(hr) == FACILITY_WINDOWS) hr = HRESULT_CODE(hr); char* szError; if(FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL, hr, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&szError, 0, NULL) != 0) { cout << “Preklad chyby (“ << hr << “):” << szError << endl; LocalFree(szError); } else printf(“Nezname cislo chyby\n”);
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
32
3 Základní COM objekty pro práci s Microsoft Office Firma Microsoft pro svůj produkt Microsoft Office, který se skládá z Microsoft Word, Microsoft Excel, Microsoft PowerPoint, Microsoft Outlook a Microsoft Access, vyvinula knihovnu, která je založena na základech COM technologie, přesněji řečeno na OLE Automation. Tato knihovna vznikla proto, aby si uživatelé nebo firmy mohli některé funkce upravit popřípadě úplně přeprogramovat. Dále se budu věnovat knihovně pro Microsoft Word a Microsoft Excel. Pro oba produkty je základní třída _Application. Dále pro Microsoft Word jsou velmi důležité třídy
_Document a Documents a pro Microsoft Excel to je třída Workbooks.
3.1
Třída COleDispatchDriver
Třída COleDispatchDriver je třída, od které dědí výše zmíněné třídy, všechny ostatní třídy v knihovnách Microsoft Word a Microsoft Excel, ale není to základní třída. Tato třída implementuje OLE Automation na straně klienta. OLE vystavuje rozhraní poskytující přístup k funkcím a vlastnostem objektu. Členské funkce této třídy připojují, oddělují, vytvářejí a uvolňují spojení s IDispatch. Její další funkce se používají pro proměnlivé argumenty, uložené v seznamu pro zjednodušení volání funkce Invoke z rozhraní IDispatch. Tuto třídu můžeme přímo používat, ale obyčejně se používá pouze, pokud byla vytvořena ClassWizardem.
3.1.1 Automation Clients Automation vytváří možnost pro naše aplikace manipulovat s objekty v dalších aplikacích nebo zjistit objekty, s kterými je možno manipulovat. Automation client je aplikace, která může manipulovat s nalezenými objekty současně s dalšími aplikacemi. Aplikace vyhledá objekty volané Automation server. Klient manipuluje se serverovými instancemi objektů, které zajišťují přístup k vlastnostem a funkcím těchto objektů. Máme dva typy . První je klient, který dynamicky získává informace o vlastnostech a operacích na serveru. Druhý je klient, který vlastní statické informace, které specifikují vlastnosti a operace na serveru. Klient prvního typu získá informace o serverových metodách a vlastnostech dotazem k rozhraní IDispatch. I když je to dostatečné použití dynamického klienta, IDispatch je obtížné použití pro statického klienta, kde objekt musí být znám již v době kompilace programu. Pro staticky vázaného klienta poskytla Microsoft Foundation classes třídu COleDispatchDriver společně s podporou ClassWizard. Staticky vázaný klient využívá prostředníky, takže staticky linkuje klientské aplikace. Tato třída poskytuje určitý druh bezpečí v C++, stručně vyjádřeno vlastnostmi a funkcemi serverových aplikací. Třída COleDispatchDriver poskytuje hlavní podporu klienta ze strany
ClassWizard, můžeme vytvořit třídu odvozenou od COleDispatchDriver. Poté upřesníme typovou knihovnu souboru popsáním vlastností a
Automation.
Použitím
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
33
funkcí objektu serverových aplikací. ClassWizard si tento soubor přečte a vytvoří odvozenou třídu od COleDispatchDriver s členskými funkcemi tak, aby naše aplikace mohla volat přístup k objektům serverových aplikací. Dodatečná funkčnost dědičnosti z COleDispatchDriver zjednoduší proces volání konkrétního Automation server.
3.1.2 Automation Server Automation vytváří pro své aplikace možnosti manipulování s objekty, které implementují další aplikace, nebo zveřejňuje objekty, s kterými můžeme manipulovat podobně jako u Automation client. Automation server je aplikace, která dovoluje zveřejnit programové objekty dalším aplikacím, které jsou volány Automation client. Tedy odhaluje programové objekty a dává možnost klientům mechanizovat určitou proceduru, která umožňuje přímý přístup k objektům a funkcím, které má server k dispozici. Odhalení objektů touto cestou je užitečné, když aplikační poskytovatel funkcionality je užitečný dalším aplikacím. Odhalování objektů neboli umožnit zlepšení funkcionality aplikací použitím připravované funkce dalších aplikací. Odhalení aplikační funkcionality přes dobře definované rozhraní nám Automation pomůže vytvořit možnost stavby aplikací v jednotlivých základních programovacích jazycích jako je Microsoft Visual Basic namísto odlišných jazyků používajících makra.
3.1.3 Členské atributy COleDispatchDriver má dvě proměnné, m_lpDispatch a m_bAutoRelease. První proměnná m_lpDispatch je ukazatel na rozhraní IDispatch podléhající třídě COleDispatchDriver. Druhá proměnná m_bAutoRelease je typu Třída
bool, tedy nabývá logických hodnot. Nabývá hodnoty TRUE, pokud objekt COM získá m_lpDispatch a bude automaticky uvolněn, když je volána funkce ReleaseDispatch nebo pokud je objekt COleDispatchDriver odstraněn. Proměnná m_bAutoRelease má přednastavenou hodnotu TRUE, která se nastavuje v konstruktoru.
3.1.4 Konstruktor Tato třída má tři druhy konstruktoru. První je COleDispatchDriver ( ), který vytváří objekt COleDispatchDriver, ale nepřipojí ho k rozhraní IDispatch. Před jeho použitím, ho bychom měli připojit k IDispatch a to buď funkcí CreateDispatch nebo AttachDispatch. Druhý konstruktor COleDispatchDriver ( LPDISPATCH lpDispatch, BOOL
bAutoRelease = TRUE ) má parametr lpDispatch. Tento parametr je ukazatel na objekt IDispatch, který je připojen k objektu COleDispatchDriver. Druhý parametr konstruktoru bAutoRelease upřesňuje, zda odeslání bylo uvolněno, pokud byl objekt odstraněn. Tento konstruktor tedy zajišťuje spojení s rozhraním IDispatch.
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
34
Poslední konstruktor COleDispatchDriver( const COleDispatchDriver&
dispatchSrc ) má jediný parametr dispatchScr, který je referencí na existující objekt COleDispatchDriver. Po jeho zavolání se inkrementuje počet referencí na rozhraní IDispatch..
3.1.5 Metody třídy COleDispatchDriver Funkce CreateDispatch je typu BOOL a její návratová hodnota je různá od nuly pokud vše proběhlo v pořádku, jinak se vrátí nula. Jejím úkolem je vytvořit objekt IDispatch a připojit ho k objektu COleDispatchDriver. Funkce AttachDispatch je typu void, tedy nevrací nic. Voláním této funkce připojujeme ukazatel IDispatch k objektu COleDispatchDriver. Jakmile je připojení hotové, ukazatel na IDispatch se uvolní. Funkce DetachDispatch vrací ukazatel na předčasně uvolněný objekt IDispatch. Jejím úkolem je zrušit současné spojení mezi IDispatch a objektem, nad kterým voláme tuto funkci. Objekt IDispatch uvolněn není, stále zůstává v paměti. Funkce ReleaseDispatch je typu void. Uvolňuje spojení s IDispatch. Jestliže je spojení nastavené na automatické uvolnění, pak volá funkci IDispatch::Release před uvolněním rozhraní. Funkce InvokeHelper( DISPID dwDispID, WORD wFlags, VARTYPE
vtRet, void* pvRet, const BYTE FAR* pbParamInfo, ...) je také typu void. Funkce volá metody nebo vlastnosti objektu specifikované parametrem dwDispID v souvislosti s parametrem wFlags. Parametr pbParamInfo specifikuje typy parametrů poslaným metodám a vlastnostem. Parametr vtRet může nabývat různých hodnot od datových typu k objektům. Funkce převede parametr na typ VARIANTARG a poté se spustí funkce
IDispatch::Invoke. Pokud je funkce Invoke volána špatně, je vyvolána vyjímka typu COleException nebo COleDispatchException. Funkce SetProperty nastavuje vlastnosti OLE objektů specifikované v parametru dwDispID . Funkce GetProperty vrací vlastnosti objektu specifikovaných v parametru dwDispID.
3.2
Třída _Application
Ze třídy _Application vytvoříme objekt, který reprezentuje aplikaci Wordu, Excelu a dalších. Objekt Application má vlastnosti a metody vracející objekty na nejvyšší úrovni. Například metoda GetActiveDocument() vrací objekt _Document. Většinou používáme funkce ze třídy _Application k získání objektu Application. Následující příklad nám zobrazí jméno uživatele Wordu. _Application oWrd; … AfxMessageBox(oWrd.GetUserName());
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
35
Velké množství vlastností a funkcí vrací uživatelské objekty, jako jsou aktivní dokumenty. Ole Automation právě používáme při spouštění Wordu nebo Excelu z dalších aplikací. K tomu používáme metody CreateDispatch a GetObject, které vracejí objekt vytvořený ze třídy
_Application. Tento následující příklad ukazuje jak zpustit Word a otevřít existující dokument. _Application oWord; Documents oDocs; _Document oDoc; COleVariant vOpt(DISP_E_PARAMNOTFOUND, VT_ERROR); //Start Word if(!(oWord.CreateDispatch("Word.Application", NULL))) { AfxMessageBox("Error starting Word.", MB_SETFOREGROUND); return; } //set visible oWord.SetVisible(TRUE); //Open a document oDocs = oWord.GetDocuments(); oDoc = oDocs.Open(COleVariant(szFile), vOpt, vOpt, vOpt, vOpt, vOpt, vOpt, vOpt, vOpt, vOpt);
Metodu CreateDispatch ve třídě _Application nenajdeme, protože je to metoda třídy COleDispatchDriver, ze které třída _Application dědí. Tato metoda nám zajistí spojení s rozhraním IDispatch. Funkce SetVisible zobrazí aplikaci, nad kterou je volána. Jinak aplikace proběhne na pozadí.
3.3
Třída _Document
Tato třída je součástí knihovny pro práci s Microsoft Word. Objekty vytvořené z této třídy reprezentují dokumenty. Objekt Dokument je členem kolekce Documents. Kolekce
Documents obsahuje všechny objekty _Document, které jsou právě otevřeny ve Wordu. Tato třída má mnoho funkcí, které nám umožňují manipulovat s dokumenty, které jsou napsány ve Wordu. K nejpoužívanějším patří funkce Active (aktivuje nám dokument),
GetApplication (vrací aplikaci která ho spustila) a další.
3.4
Třída Documents
Tato třída je také součástí knihovny pro práci s Microsoft Word. Používá se pro všechny současně otevřené soubory ve Wordu. Použitím metody Add vytvoříme nový prázdný dokument, který se automaticky přiřadí do kolekce Documents. Následující příkaz vytvoří nový dokument. … Documents oDocs; oDocs.Add();
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
36
Metoda Open otevře soubor a umožňuje nám s ním manipulovat. Příklad je uveden výše.
3.5
Třída COleVariant
Třída COleVariant není základní třída. Používáme ji právě při Ole Automation. Tato třída je odvozená od třídy Variant. COleVariant má spoustu konstruktorů, ale pro naši potřebu si vystačíme s konstruktory COleVariant( long lSrc, VARTYPE vtSrc = VT_I4
), kde lSrc je kopie 32 bitové integeru v novém objektu a vtSrc musí mít jednu z těchto hodnot: VT_I4, VT_ERROR nebo VT_BOOL. Tento konstruktor je použit v příkladu na otevření dokumentu ve Wordu, kde je použita konstanta DISP_E_PARAMNOTFOUND, která je popsaná v tabulce Tab. 4. Konstruktor COleVariant( CString& strSrc ) má parametr strSrc, který je kopie stringu. Tento parametr uloží do nového objektu.
3.6
Třída Workbooks
Tato třída je součástí knihovny pro práci s Microsoft Excel. Je to kolekce všech objektů Workbook, které jsou současně otevřeny v aplikaci Microsoft Excel. Použití vlastností
Workbooks vrací kolekci Workbook. Následující příklad uzavře všechny Workbook. Workbooks oExl; … oExl.Close();
Metoda Add vytvoří nový prázdný Workbook a přidá ho do kolekce. Tento příklad přidá nový prázdný Workbook do Microsoft Excel. Workbook oExl; … oExl.Add();
Metoda Open otevře existující soubor. K tomu se musí vytvořit nový Workbook. Následující příklad otevře soubor „Array.xls“ … CString szFile = "Array.xls"; // Open a workbook... lpDisp = oBooks.Open(szFile, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional);
Teď si ukážeme jaká je základní architektura tříd použitých při práci s Microsoft Excel. Toto nejlépe vystihuje obrázek obr. 13.
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
37
Applic a tion W or k book s (W or k book ) W or k s he e ts (W ork s he e t) Ra nge Sha pe s (Sha pe ) Cha rts (Cha r t) Com m a ndBa rs (Com m a ndBa r)
An o b je ct th a t is a co lle ctio n o f o th e r o b je cts An o b je ct
Obr. 13. – Základní hierarchie tříd v Excelu
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
38
4 Implementace jednotlivých části programu Pro tvorbu programu, který bude pracovat s Microsoft Word a Excel použijeme třídy popsané v předchozí kapitole. Tyto třídy nejsou součástí standardních knihoven dostupných jakémukoliv uživateli. Tyto třídy si musíme vytvořit.
4.1
Vytvoření Automation Client pro MFC
Vytvoření těchto tříd je v MFC jednodušší než v klasickém C/C++. Použijeme ClassWizard který nám tyto třídy pomůže vygenerovat. Postup je následovný: 1. Spustíme Visual Studio. 2. Dáme File -> New 3. Zvolíme MFC AppWizard (EXE) a napíšeme jméno toho projektu a potvrdíme. 4. V menu zvolíme View a klikneme na ClassWizard 5. Zvolíme záložku Automation 6. Klikneme na Add Class a vybereme From a type library 7. Prohlédneme si adresář Microsoft Word 97 a v něm vybereme typovou knihovnu Msword8.olb a zmáčkneme OK 8. Vybereme všechny třídy, které nám ClassWizard nabízí a klikneme na OK Tento proces vygeneruje dva nové soubory v našem projektu. Tyto soubory jsou Msword8.cpp a Msword8.h. Tyto soubory tvoří obal pro všechny třídy a členské funkce, takže obalují typovou knihovnu Word. Tento postup byl ukázán pro Microsoft Word, ale platí i pro Microsoft Excel pouze s tím rozdílem, že místo souboru Msword8.olb budeme používat soubor Msexcel8.olb a budeme ho hledat v adresáři Microsoft Excel 97.
4.2
Důležitý kód pro MS Word
V této části si ukážeme část kódu, který nám spouští naši aplikaci. Je zde popsáno, jak pomoci COM technologie přesněji pomoci OLE AUTOMATION vyvolat dokument typu Word a poté reagovat na jeho změny při uzavření tohoto dokumentu. Třída _Application nemá metodu, pomocí které bychom mohli zjistit zda dokument se změnil a jestli je uzavřen. Toto je vyřešeno pomocí vyvolání vyjímek. Jedna z vyjímek je vyvolána, jakmile se snažíme volat jednu z metod nad objektem Application. Tento objekt již neexistuje, byl uživatelem odstraněn ( byl zavřen dokument ), tím pádem voláním metody nad tímto objektem vyvolá výjimku. Na tuto výjimku reagujeme ukončením aplikace a předání řízení hlavní aplikaci, která dokument uloží do databáze Market. _Application oWord; //vytvoření objektu Application Documents oDocs; //vytvoření objektu Documents _Document oDoc; //vytvoření objektu Dokument COleVariant vOpt(DISP_E_PARAMNOTFOUND, VT_ERROR); //Start Word if(!(oWord.CreateDispatch("Word.Application", NULL))) {
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
39
AfxMessageBox("Nelze otevrit Word.", MB_SETFOREGROUND); return; } BOOL Res = TRUE; oWord.SetVisible(TRUE); //otevření dokumentu document oDocs = oWord.GetDocuments(); oDoc = oDocs.Open(COleVariant(szFile), vOpt, vOpt, vOpt, vOpt, vOpt, vOpt, vOpt, vOpt, vOpt); //nalinkování na konkrétní dokument _Document oActiveDoc; oActiveDoc = oWord.GetActiveDocument();
CString thePrinter = oWord.GetActivePrinter(); long lCancelKey = oWord.GetEnableCancelKey(); //Zde je cyklus, který bude čekat dokud se Word nezavře MSG msg; while (1) { if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if(msg.message == WM_QUIT) { PostQuitMessage(0); break; } TranslateMessage(&msg); DispatchMessage(&msg); } BOOL bSpusteno, bRunGetIsObjectValid; //hlídaný blok, kde očekáváme uzavření wordu a chceme na to //reagovat try { Sleep(1000); bRunGetIsObjectValid = TRUE; lCancelKey = oWord.GetEnableCancelKey(); thePrinter = oWord.GetActivePrinter(); bSpusteno = FALSE; bRunGetIsObjectValid = FALSE; if (bSpusteno) { break; } } //reakce na uzavření wordu uživatelem
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
40
catch(COleException *e) { LPVOID lpMsg; if (!(e->m_sc == 0x00377ab0||e->m_sc == 0x01e87ad0|| e->m_sc == 0x01e87ae8 || e->m_sc == 0x01e87a88 || e->m_sc == 0x80010001)) { //e->m_sc = 0x80010108, když se Word uzavře bez nějakých změn(Vyvolaný objekt se odpojil od svých klientů) //e->m_sc = 0x800706ba, když se Word uzavře a dají se uložit změny které byly vyvolané (Server RPC není k dispozici) //e->m_sc = 0x800706ba, když se Word uzavře a nedají se uložit změny které byly udělané (Server RPC není k dispozici)
::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, e->m_sc, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),(LPTSTR) &lpMsg, 0, NULL); ::LocalFree( lpMsg ); break; } } catch(COleDispatchException *e) { char msg[512]; sprintf(msg, "Run-time error '%d':\n\n%s", e->m_scError & 0x0000FFFF, e->m_strDescription); ::MessageBox(NULL, msg, "Server Error", MB_OK | MB_SETFOREGROUND); break; } //nastala neočekávaná chyba catch (...) { if(!bRunGetIsObjectValid) { AfxMessageBox(GetLastError()); AfxMessageBox("Nastala chyba pri praci s dokumentem Word"); } return; } }
4.3
//konec while
Důležitý kód pro MS Excel
Stejně jako u spouštění wordu tak u spouštění excelu využijeme dříve popsané třídy. Využíváme zde také technologie OLE AUTOMATION pro práci s dokumentem typu Excel. Dále zde využíváme procesu a reakci na ukončení tohoto procesu. Dokument typu Excel se totiž chová odlišně od dokumenty typu Word.
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
41
Vyvoláme dokument Excelu a dáme uživateli možnost s ním pracovat. Jakmile je s prácí hotov, aplikace znovu přebírá kontrolu a reaguje na ukončení tohoto dokumentu tak, že ho uloží zpátky do databáze Market. char buf[1024]; Application oExcel; Workbooks oBooks; LPDISPATCH lpDisp;
//vytvoření objektu _Application.
// Vytvořeni objektu OleVariant pro jednoduché volání COleVariant covTrue((short)TRUE), covFalse((short)FALSE), covOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR); // Start Excelu a vrácení objektu Application. if(!oExcel.CreateDispatch("Excel.Application")) { AfxMessageBox("Couldn't CreateDispatch on Excel"); return; } //Nastavení viditelnosti. oExcel.SetVisible(TRUE); //Toto je vlastnost objektu _Application, která říká, že může být //uvolněn objekt oExcel a oBooks bez ukončeni Excelu. oExcel.SetUserControl(TRUE); //Vracení kolekce Workbooks. lpDisp = oExcel.GetWorkbooks(); // Vrátí ukazatel na IDispatch ASSERT(lpDisp); //zkouška zda je vše v pořádku. oBooks.AttachDispatch( lpDisp ); // připojení ukazatele IDispatch // k objektu oBooks. // Otevření workbook... lpDisp = oBooks.Open(szFile, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional, covOptional ); ASSERT(lpDisp);
// kontrola zda je vše v pořádku
HWND hWnd; hWnd = ::FindWindow("XLMain", // ukazatel na jméno třídy. NULL // Ukazatel na nastavení jména třídy. ); if(NULL==hWnd) { long lErr = GetLastError(); sprintf(buf, "FindWindow error code = %d", lErr); AfxMessageBox(buf); } DWORD pid; // Proměnná pro uložení ID procesu. DWORD dThread; // Proměnná pro uložení ID vlákna. dThread = GetWindowThreadProcessId(hWnd, // Handle na okno. (LPDWORD)&pid
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
42
HANDLE hProcess; // Handle na existující proces hProcess = OpenProcess(SYNCHRONIZE | PROCESS_ALL_ACCESS, TRUE pid); oBooks.ReleaseDispatch(); // uvolnění objektu IDispatch. oExcel.ReleaseDispatch(); oBooks = NULL; // odstranění objektových referencí. oExcel = NULL; DWORD dwReason; MSG msg; while (1) {
// proměnná k získání signálu o ukončení.
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if(msg.message == WM_QUIT) { PostQuitMessage(0); break; } TranslateMessage(&msg); DispatchMessage(&msg); } dwReason = WaitForSingleObject(hProcess, 50); if (dwReason == 0) { break; }
} //konec while
4.4
Hotový kód
Na základech popsaných kódů stojí jednotlivé programy, které jsou vytvořeny jako EXE soubory pro lepší ukázku funkčnosti. Jednotlivé programy se tedy spouštějí soubory Word.exe a Excel.exe. Jejich úkolem je vám ukázat funkčnost jednotlivých aplikaci. Po spuštění jedné z nich se objeví dialog pro výběr souboru s konkrétní příponou. Po vybrání jednoho souboru a potvrzení tlačítkem OK je vyvolán konkrétní dokument, který jste si vybrali a můžete s ním pracovat. Po skončení vaší práce je dokument uzavřen a vámi volána aplikace skončí.
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
43
5 Vyhodnocení výsledků Při vypracování této bakalářské práce jsem se seznámil s problematikou relační databáze a přístupu k databázi. Také s principem fungování informačního systému Market firmy CID International, a.s., kde jsem prostudoval jednotlivé vazby v databázi Market, fungování Marketu pro spouštění jednotlivých knihoven, na kterých je postaven. Prostudoval jsem COM technologii a OLE Automation, na kterých stojí objekty firmy Microsoft. Tyto objekty byly potřebné k vyvinutí modulu, který firma CID International, a.s. potřebovala a žádala. Také jsem se seznámil s objektovým modelem Microsoft Wordu® a Microsoft Excelu®, který byl pro tuto práci nezbytný. Byly vytvořeny nové moduly s rozhraním na existující moduly firmy CID International, a.s.. Názvy těchto modulů jsou _aMkDokuWord pro Microsoft Word® a _aMkDokuExcel pro Microsoft Excel®. Tyto moduly byly začleněny do modulů aplikace Market firmy CID International, a. s. i do modulů používaných u jejích zákazníků. Jak jsem uvedl, byly vyvinuty moduly pouze pro Microsoft Word® a Microsoft Excel® a je tu možnost tento výčet dále rozšířit o moduly, které by podobně řešily práci s Microsoft PowerPointem®, a celé rodiny produktů Microsoft Office®. Dále je v těchto modulech využita jen velmi malá část jejích možných funkcí, které by mohly poskytovat. Tyto moduly se mohou dále vyvíjet a na jejich základech se mohou přidat další funkce.
VŠB – TU Ostrava
Martin Čermák
Sdílení dokumentů ve stávajícím informačním systému
44
Použitá literatura ARLOW, J. NEUSTADT, I. 2003. UML a unifikovaný proces vývoje aplikací. Praha : Computer Press.386 s. ISBN 80-7226-947-X BROCKSCHMIDT, K. 1995. Inside Ole Sekond Edition. Washington : Microsoft Press 1194 s. ISBN 1-55615-843-2 KAČMÁŘ, D. 2000. Programujeme v COM a COM+. Praha : Computer Press. 304 s. ISBN 80-7226-381-1 MSDN Library – October 2001 PATTISON, T. 2000. Programming Distributed Applications with COM+ and Microsoft Visual Basic 6.0 Second Edition. Washington : Microsoft Press 472 s. ISBN 0-7356-1010X PRATA, S. 2001. Mistrovství v C++. Computer Press. 966 s. ISBN 80-7226-339-0 SHEPHERD, G. WINGO, S. KRUGLINSKI, D. 2000 Programujeme v Microsoft Visual C++. Praha : Computer Press. 1014 s. ISBN 80-7226-362-5 SCHMULLER, J. 2001. Myslíme v jazyku UML. Praha : Grada. 359 s. ISBN 80-247-00298 STROUSTRUP, B. 1997. C++ Programovací jazyk. Praha : BEN – Technická literatura.686 s. ISBN80-901507-2-1 Stránky Microsoft dostupné z www 8.5.2006 Soubory: VBAWRD8, VBAXL8
VŠB – TU Ostrava
Martin Čermák