OSA VI 599 LUKU 25 25 LUKU OLE- ja COM-ohjelmoinnin ymmärtäminen Komponenttioliomallin perusteet Oman OLE-automaatio palvelimen luominen OLE-palvelimet ja säilöt
600 COM+, COMin seuraava askel Seuraava vaihe COMin kehityksessä tulee olemaan COM+. Microsoftin suunnitelmien mukaan COM+ tulee helpottamaan COM-koodausta siten, että COM-objektit tulevat muistuttamaan C++-olioita ja niitä voidaan luoda C++:n newja delete-avainsanoilla. Visual C++ ei vielä tue COM+:aa, mutta tulevaisuudessa sille lienee saatavilla oma SDK tai Service Pack. Windows 2000 tulee perustumaan COM+:n käyttöön. Sovelluksen tietojen ulkoistaminen Komponenttipohjainen ohjelmointi Kehittyneissä ohjelmointiympäristöissä, kuten Windowsissa, on ohjelmankehitys tullut yhä monimutkaisemmaksi johtuen useista ohjelmointirajapinnoista (API), standardien tarpeesta, versionhallinnasta, kehitystyön nopeudesta ja hajautetusta laskennasta. Tarpeesta yksinkertaistaa tätä kaikkea on saanut alkunsa komponenttiohjelmointitekniikka. Komponentit ovat pieniä ohjelma-alkioita (kuten luokkien instanssit, oliot), jotka suorittavat määrätyn tehtävän hyvin määritettyjen rajapintojen kautta. Toisin kuin oliot, komponentit eivät ole sidoksissa tiettyyn ajossa olevaan ohjelmaan tai edes koneeseen. Komponentteja voidaan kirjoittaa eri ohjelmointikielillä ja ne osaavat kommunikoida rajapintojen kautta myös toisilla ohjelmointikielillä kirjoitettujen komponenttien kanssa. Myös Microsoft on kehittänyt tätä tekniikkaa ja nykyään sen kehittämä komponenttitekniikka tunnetaan nimellä COM (Component Object Model, komponenttiobjektimalli). Olet saattanut tavata tätä sivuavia termejä, kuten OLE (Object Linking and Embedding, objektien linkitys ja upottaminen) sekä ActiveX-kontrollit. Nämä molemmat ovat itse asiassa COM-ohjelmoinnin tiettyjä toteutuksia. Itse COM on kieli- ja laiteriippumaton standardi, joka määrittelee, kuinka oliot kommunikoivat keskenään yhteistä protokollaa käyttäen. Tärkeintä COM-objekteissa ovat niiden liittymät (interface), itse objektit piilottavat toimintansa toteutuksen. Jotta COM-objektin toimintoja voitaisiin hyödyntää, joudutaan sopimaan tarkasti objektin ja sitä käyttävän ohjelman kesken parametrien välityksestä ja tulosten saamisesta. Tämä sopimus asiakasohjelman ja COM-objektin välillä on nimeltään liittymä (interface). Liittymien ympärille voidaan määritellä ja suunnitella kokonaisia ohjelmointirajapintoja (API). Jos olet kirjoittamassa asiakassovellusta, saat sovelluksesi keskustelemaan palvelimelle näiden rajapintojen avulla eikä palvelinohjelmiston valmistajalla ole merkitystä. Vaihtoehtoisesti voit haluta kirjoittaa palvelimen komponentteja, jotka toimivat liittymän määritysten mukaisesti, ja myydä niitä vaihtoehtona kilpailijoiden toteutuksille. Hyvä esimerkki COMin käytöstä on MAPI (Messaging API, sanomanvälitys-api). Kuka tahansa saa kirjoittaa COM-objekteja, jotka
suorittavat tähän liittyviä tehtäviä, kuten sanomien tallentamista, siirtämistä ja vastaanottajien luetteloimista osoitekirjaan. Eräs esimerkki näistä palvelinkomponenteista on Microsoft Exchange, mutta muitakin on. Varsinainen toteutuskoodi saattaa poiketa rajustikin eri toteutusten välillä, mutta tärkeintä on, että kaikki COM-objektit toimivat saman liittymämäärityksen mukaan. Näin kaikki näiden palveluiden asiakasohjelmat, kuten Microsoft Outlook, voivat käyttää kaikkien valmistajien MAPI-komponentteja sanomiensa lähettämiseen, vastaanottamiseen ja tallentamiseen. Aivan samoin kukin ohjelmistotoimittaja voi tarjota oman Exchangea tai muita palvelinkomponentteja käyttävän asiakasohjelmansa (kuten monet tekevätkin) tietämättä edes kenen komponentteja käyttävät. Ainoa vaatimus asiakas- ja palvelinkomponenteille on, että ne kutsuvat toisiaan näiden sovittujen liittymämäärittelyjen mukaisesti (eli MAPIn). OSA VI LUKU 25 601 KATSO MYÖS ActiveX-kontrollien käyttö omassa sovelluksessa, luvusta 9. ActiveX-kontrollien luominen luvussa 26. Lisätietoja MAPIsta luvussa 28. COM-liittymät Liittymä (interface) on määrittely funktiojoukolle ja niiden parametreille. Kaikilla COM-objekteilla on vähintään yksi liittymä ja useilla niitä on monia ja kullakin liittymällä on oma funktiojoukkonsa. Voit kirjoittaa COM-objekteja millä tahansa näitä liittymiä tukevalla kielellä. Toiset kielet sopivat tarkoitukseen tietenkin paremmin kuin toiset. Esimerkiksi Java toimii hyvin, koska kullakin Java-oliolla voi olla useita liittymiä ja se saadaan liitetyksi luonnollisesti COMobjekteihin. COM-objekti toteuttaa varsinaisen liittymän alla olevan koodin, joten ohjelma, joka kutsuu liittymässä olevaa funktiota löytää myös sen toteutuksen, joka suorittaa tietyssä liittymässä määritetylle funktiolle annetun tehtävän. Karkeasti ajatellen COM-liittymän rakenne on suunnilleen samanlainen kuin Visual C++:n virtuaalifunktioiden taulukkorakenne. Voit siis käyttää virtuaalifunktiotaulukkomekanismia COM-liittymien määrittelyyn ja toteutukseen. Tavallisesti virtuaalifunktiot korvataan kantaluokasta johdetulla funktiolla (kun kantaluokan määrittelyssä on käytetty virtual-avainsanaa). Tämän tehdessäsi määritellään tuohon luokkaan liittyvä virtuaalifunktiotaulukko vtable. Liittymien nimeäminen COM-liittymät nimetään monien muiden asioiden tavoin toimintansa mukaan. Yleensä niiden etuliitteeksi on tapana merkitä I. Tällaisia liittymiä ovat esimerkiksi IUnknown, IDispatch, IMoniker ja IMessageFilter.
602 Virtuaalifunktiotaulukot Jokaisen muistissa olevan C++-olioilmentymän liitteenä on olion virtuaalifunktiotaulukko (vaikka joskus tämä onkin tyhjä ja ilman alkioita). Jokaisessa taulukon alkiossa on osoitin virtuaalifunktion toteuttavaan koodiin. Aina kun jotain olion virtuaalifunktioista kutsutaan, taulukosta löytyy oikea osoite joko kantaluokan tai johdetun luokan funktiolle. Sovelluksen tietojen ulkoistaminen Pelkkiä virtuaalifunktioita sisältävän C++-luokan määritteleminen on mahdollista. Tällaista luokkaa kutsutaan abstraktiksi kantaluokaksi. Abstraktista kantaluokasta ei voida muodostaa luokan instansseja eli olioita, mutta siitä voidaan silti luoda C++-yhteensopivia COMliittymämäärittelyjä, jotka näyttävät esimerkiksi seuraavilta: class IUnknown { public: virtual HRESULT QueryInterface(REFIID riid, LPVOID FAR* ppvobj)=0; virtual ULONG AddRef()=0; virtual ULONG Release()=0; } Microsoft tarjoaa nämä määrittelyt kaikkien toimittamiensa COMobjektien liittymiin, vaikka saatatkin nähdä funktiot piilotettuina samanlaisiksi laajentuviin makroihin: STDMETHOD(QueryInterface)(THIS_REFIID riid, LPVOID FAR* ppvobj) PURE; STDMETHOD_(ULONG, AddRef)(THIS) PURE; STDMETHOD_(ULONG, Release)(THIS) PURE; Kaikkien COM-objektien tulee toteuttaa näistä kolmesta metodista (funktiosta) koostuva IUnknown-liittymä ja kaikkien liittymien tulee toteuttaa nämä kolme funktiota ennen omien funktioiden määrittelyä. Funktioiden rooli on seuraava: AddRef() Asiakkaan saadessa osoittimen liittymään kasvatetaan sisäistä viittauslukua edustamaan aktiivisten asiakasviittausten määrää. Release() Asiakkaan julkistaessa osoittimen liittymään pienennetään sisäistä viittauslukua yhdellä ja luvun nollautuessa objekti tuhotaan (objekti tekee tämän usein itse). QueryInterface() Asiakas, jolla on osoitin yhteen liittymään, voi pyytää osoitinta toiseen COM-liittymään antamalla vaaditun liittymän ID-tunnuksen (edellisessä esimerkissä riid). Jos pyydetty liittymä on olemassa, AddRef()-funktiota kutsutaan pyydetyn liittymän osoittimella ja se palauttaa ppvobj-osoittimen. Pyydetyn liittymän ID-tunnus (riid) selitetään tarkemmin seuraavassa kappaleessa. Koska jokainen liittymä takuulla toteuttaa nämä yhteiset funktiot, voit
OSA VI LUKU 25 603 kirjoitta asiakasohjelman, joka pyytää COM-objektin instanssia (mikä automaattisesti kutsuu AddRef()-funktiota). Tutki objektin liittymiä QueryInterface():n avulla. Kun olet valmis, kutsu kunkin saamasi liittymäosoittimen Release()-funktiota antaaksesi objektin tuhota itsensä (mikäli kukaan muu ei sitä käytä). Tämä sama funktio toimii aivan samoin kaikille COM-objekteille. Vastaavasti jos teet itse COM-objektin, joudut toteuttamaan siihen viittausten määrän laskennan sekä osoittimen palauttamisen haluttuun liittymään (tai NULL, ellei sellaista ole). Jos liittymä tukisi ainoastaan näitä kolmea funktiota, se olisi varsin hyödytön, joten tavallisesti kehittyneemmät liittymät saavat tämän määrittelyn perintönä ja määritelevät siihen lisäksi omia, käyttökelpoisia liittymiään. vtablen taustalla olevan koodin toteuttamiseen on useita tapoja. Yksinkertaisinta lienee periä oikea C++-luokka (siis ei abstrakti) liittymän määrittelyluokasta ja toteuttaa tarvittavat metodit tähän johdettuun luokkaan. Muistin vapauttaminen COM-objekteissa Koska COM-objekti vastaa itse siihen viittaavien objektien lukumäärän hallinnasta, se joutuu myös vapauttamaan käyttämänsä muistin, kun kaikki sen käyttäjät (asiakkaat) ovat vapauttaneet sen ja viittausluku on nollautunut. Muistia voidaan vapauttaa tuhoamalla C++:n this-osoitin, mikä saa aikaan kutsuvan objektin muistin tuhoamisen. Objektin tuhottua itsensä sen tulee palata viimeisestä funktiosta ilman lisäprosessointia. On oltava tarkkana siinä, ettei objektin jäsenmuuttujia yritetä käyttää, koska objekti ei enää omista muuttujien käyttämää muistia. Liittymän ja luokan ID-tunnukset sekä GUIDtunnukset Kuten aiemmasta kappaleesta huomasit, QueryInterace()-funktio palauttaa osoittimen toiseen COM-objektissa käytössä olevaan liittymään, kunhan annat sille parametrinä halutun liittymän ID-tunnuksen. Kullakin liittymällä on oma ID-tunnuksensa (IID), kuten kaikilla COM-luokillakin (CLSID). Nämä tunnukset ovat 128-bittisiä lukuja, jotka ovat varmasti jokaiselle COM-objektille ja liittymälle yksilöllisiä maailmanlaajuisesti. Tunnuksia kutsutaan GUID:ksi (globally unique ID). Kun kirjoitat uuden COM-objektin tai määrittelet uuden liittymän, voit tehdä näitä lukuja guidgen.exe ohjelmalla. Löydät ohjelman..\visual Studio\VC98\Tools\Bin\-hakemistosta. Ohjelman ajaessasi se luo uuden GUID-tunnuksen ja antaa mahdollisuuden ilmaista sen neljällä eri tavalla (kuva 25.1). Valitset vain tarkoitukseesi sopivan muodon ja kopioit luvun leikepöydälle Copy-painikkeella. Jos napautat New GUID painiketta, guidgen.exe tekee toisen GUID-tunnuksen. Kahta samanlaista numeroa ei tällä ohjelmalla synny ellet ole erityisen pitkäikäinen. Guidgen.exe-ohjelman löytäminen ja käyttäminen Olet saattanut huomata, että guidgen.exe-ohjelman sijainti vaihtelee riippuen Visual C++kääntäjän versioista. Ohjelman paikantaminen onnistuu helposti Käynnistä-valikon Etsi / Tiedostot ja Kansiot -komennolla (Start/ Find/Files and Folders). Ohjelman löydyttyä (esimerkiksi Visual Studio -hakemiston Common\Tools -hakemistosta) voit lisätä sen Visual Studion Tools-valikkoon valitsemalla Tools-valikosta Customizekomennon ja valitsemalla Customize-valintaikkunasta Tools-välilehden ja lisäämällä guidgen.exen työkaluluetteloon.
604 Sovelluksen tietojen ulkoistaminen KUVA 25.1 Guidgen.exe-ohjelma tekee uuden maailmanlaajuisesti yksilöllisen tunnusluvun. Tyypillinen GUID-tunnus näyttää tältä Registry-muotoiltuna: {0793B920-CF75-11d1-8647-004095A12AF9} Saatat nähdä saman luvun eri tavoin koodattuna käyttötarkoituksesta riippuen. Esimerkiksi C++-koodissa sama luku kirjoitettaisiin: 128-bittisten numeroiden lukualue 128-bittisillä luvuilla voidaan esittää kokonaislukuarvoja nollasta yli 340 282366 920938 463463 374607 430000 000000:n! // {0793B920-CF75-11d1-8647-004095A12AF9} DEFINE_GUID(<<name>>, 0x0793B920, 0xcf75, 0x11d1, 0x86, 0x47,0x0, 0x40, 0x95, 0xa1, 0x2a, 0xf9); Yleensä 128-bittisen luvun muistaminen ei ole helppoa, joten liittymille ja luokille on tapana antaa kutsumanimiä. Esimerkiksi IUnknown-liittymä tunnettaisiin nimellä IID_Unknown ja nämä nimet voitaisiin liittää 128-bittiseen lukuun DEFINE_GUID-makrolla ja näin tuottaa staattinen QueryInterace()-funktiolle liittymän IDtunnuksena syötettävä tietorakenne. Esimerkiksi seuraava koodi asettaa IID_IDirectDraw2-liittymän osoittimen pidd2 olemassa olevan pidraw direct draw olio-osoittimen mukaan: IDirectDraw2* pidd2 = NULL; HRESULT hr = pidraw- >QueryInterface(IID_IDirectDraw2, (LPVOID *) &pidd2); (Tässä DirectDraw on ainoastaan esimerkki COM-objektista. Lisätietoja Direct Draw:sta löydät halutessasi luvusta 28). Kaikkien COM-luokkien etuliitteenä on CLSID. IID_IDirectDraw2- liittymän toteuttava DirectDraw-olio saataisi olla nimetty CLSID_DirectDraw:ksi.
Pohjimmiltaan sekä liittymän että luokan ID-tunnukset ovat kuitenkin GUID-tunnuksia ja vastaavat numeeriset arvot löytyvät näin määritellystä DRAW.h header-tiedostosta: DEFINE_GUID( CLSID_DirectDraw, 0xD7B70EE0, 0x4340, 0x11CF, 0xB0, 0x63,0x00, 0x20, 0xAF, 0xC2, 0xCD, 0x35); DEFINE_GUID( IID_IDirectDraw2, 0xB3A6F3E0, 0x2B43, 0x11CF, 0xA2, 0xDE,0x00, 0xAA, 0x00, 0xB9, 0x33, 0x56); KATSO MYÖS Esimerkki DirectDraw:n ja CoCreateInstance():n käytöstä ja lisätietoja DirectDraw:sta luvussa 28. OSA VI LUKU 25 605 COM-objektien ilmentymien luominen Kun luot ilmentymän COM-objektista, käynnistyy palvelinprosessi sen ylläpitämiseksi. Palvelinprosessi voi olla oma asiakasohjelmasi, liitetty DLL-kirjasto, paikallisessa koneessa oleva exe-tiedosto tai etäkoneessa oleva ohjelma. Näitä tapauksia kutsutaan suoritusympäristöiksi (konteksteiksi) ja eri vaihtoehdot on esitetty taulukossa 25.1. TAULUKKO 25.1 COM-koodin suoritusympäristöt Suoritusympäristö Lippu Kuvaus Inproc Server CLSCTX_INPROC_SERVER Koodia ajetaan samassa prosessissa kuin sitä kutsuvaa asiakashjelmaakin. Local Server CLSCTX_LOCAL_SERVER Koodia ajetaan toisessa Exe-ohjelmassa eri proses sissa, mutta samassa koneessa kuin kutsuva ohjelma. Remote Server CLSCTX_REMOTE_SERVER Koodia ajetaan täysin eri koneessa kuin kutsuvaa ohjelmaa. Luokan CLSID-tunnus liitetään COM-objektiin pyydettäessä luovaan ja ylläpitävään.dll- tai.exe-ohjelmaan Windowsin järjestelmärekisterin avulla. Kaikki järjestelmään rekisteröidyt COM-luokat on järjestetty rekisteriin HKEY_CLASSES_ROOT\CLSID avaimen alle GUIDmukaan. Kaikilla on sitten avainalkio rekisteröityjen palvelintyyppien
606 Sovelluksen tietojen ulkoistaminen mukaan InprocServer32, LocalServer32 tai RemoteServer32 (tai muutama muu). Näiden rekisteriavaimien alta näet luokan luovan ja sitä ylläpitävän.dll- tai.exe-tiedoston nimen. Esimerkiksi kuvassa 25.2 rekisterieditorissa on esitetty DirectDrawolion CLSID-tunnus ja sen samassa prosessissa käsittelijä DLL, ddraw.dll. KUVA 25.2 Rekisterieditori esittää CLSIDtiedot. Objektien luominen etäkoneessa Jotta voisit luoda objekteja etätietokoneissa, joudut käyttämään CoCreateInstanceEx()-funktiota. Tämä hyödyntää DCOMjärjestelmää (Distributed COM, hajautettu COM), joka mahdollistaa tiedonsiirron verkon tietokoneiden välillä etäproseduurikutsuja (RPC) käyttäen. Asiakaskoneet tulee konfiguroida DCOMCNFG.EXEohjelmalla näiden COMetäpalvelimien rekisteröimiseksi. Nämä palvelimet toteuttavat erikois-com-objektin nimeltä Class Object (luokkaobjekti). Nämä objektit ovat muuten tavallisten COMobjektien kaltaisia, paitsi että ne toteuttavat IClassFactoryliittymän. Kutsumalla tämän liittymän CreateInstance()-funktiota voidaan luoda halutun tyyppinen luokka. Tavallisesti tämä tehdään COM-kirjaston CoCreateInstance()-funktiolla yhden ilmentymän luomiseksi tai CoGetClassObject()-funktiolla osoittimen saamiseksi class factory objektiin. Näitä luokkatehtaita tulisi oikeastaan kutsua objektitehtaiksi, koska ne tekevät objekteja, eivät luokkia. (Luokkia tekevät ohjelmoijat!) CoCreateInstance() määritellään seuraavasti: STDAPI CoCreateInstance(REFCLSID rclsid, LPUNKNOWN punkouter, DWORD dwclscontext, REFIID riid, LPVOID* ppv); Ensimmäinen parametri rclsid on viittaus luotavan luokan CLSIDluokkatunnukseen. Toisena parametrinä (punkother) annetaan yleensä NULL - parametrin käyttö liittyy tavallisesti aggregaatio-nimiseen edistyneeseen COM-tekniikkaan. Halutun palvelintyypin määrittelevän dwclscontext-parametrin arvoksi annetaan yleensä CLSCTX_SERVER, joka on itse asiassa yhdistelmä tietyistä taulukon 25.1 lipuista. Neljäs parametri riid on viittaus uuden COM-objektisi halutun liittymän GUID-tunnukseen ja viimeinen, ppv, on osoitin siihen asiakasohjelman liittymäosoittimeen, joka vastaanottaa uuden objektin annetun liittymän osoittimen. Jos esimekiksi halutaan luoda ilmentymä DirectDraw-objektista ja vastaanottaa osoitin IDirectDraw2-liittymään, koodi olisi seuraavanlainen:
OSA VI LUKU 25 607 IDirectDraw2* pidirectdraw2 = NULL; HRESULT hr = CoCreateInstance(&CLSID_DirectDraw, NULL, CLSCTX_ALL, &IID_IDirectDraw2,, (void**)&pidiecctdraw2); Jos funktio onnistuu, luodaan uusi DirectDraw-objekti ja pidirectdraw2 osoittaa uuden COM-objektin IDirectDraw2- liittymään. Proxy-DLL:t ja parametrien hallinta Kun asiakasohjelmasi saa osoittimen COM-objektin liittymään, sitä ei erityisesti kiinnosta varsinaisen objektin sijainti. Jos COM-objektia kuitenkin ajetaan oman prosessisi ulkopuolella, osoitin ei voi osoittaa suoraan liittymän funktioiden vtable-taulukkoon. Suoraan osoittamisen sijaan luodaan prosessiisi tynkäfunktio, joka näyttää asiakasohjelmallesi paikalliselta liittymäosoittimelta. Kun sitten kutsut funktioita tuon liittymäosoittimen avulla, pinnan alla suorittaa proxy- DLL-kirjasto asiakasohjelmasi ja COM-objektin linkittämisen. Jokainen liittymä myös rekisteröidään Windowsin Järjestelmärekisteriin HKEY_CLASSES_ROOT\Interface avaimen alle GUIDtunnuksen mukaan. Nämä alkiot tallettavat sitten varsinaiset avaimet, kuten ProxystubClsid32, jossa on liittymän funktioita hallitsevan proxy-dll-tiedoston sijainti. Proxy-DLL vastaa parametrien sovittamisesta laiteriippumattomaan muotoon siirtämistä varten. Sen jälkeen saatetaan käyttää etäproseduurikutsua (RPC) etäkoneen COMobjektin funktion kutsumiseksi. Proxy-DLL:iä voidaan tehdä automaattisesti kirjoittamalla määrittelyt IDL-kielellä (Interface Definition Language) ja syöttämällä ne Microsoftin IDL-kääntäjälle (MIDL). Kääntäjä generoi tarpeelliset RPC- ja objektinhallintakoodit proxy- DLL:iin liitettäviksi. Proxy-DLL:ien jakeleminen Jos käsittelet parametrejä eri koneiden välillä DCOMilla, joudut varmistamaan, että proxy- DLL:stä on samat versiot sekä asiakas- että palvelinkoneissa. Liittymien versiointi Vanha ongelma ohjelmistoversioiden hallinnasta saadaan hoidettua yksinkertaisella säännöllä. Sen jälkeen, kun olet julkistanut COMobjektisi suuren yleisön tietoisuuteen, joudut jäädyttämään sen liittymät samanlaisiksi myös tuleviin versioihin. Eli tulevien COMversioiden tulee lisätoimintoja halutessaan lisätä uusia liittymiä ja jättää vanhat ennalleen. Vanhat asiakasohjelmat, jotka eivät tunne muita kuin vanhat liittymät, käyttävät niitä, kun taas uudet voivat pyytää uudempia liittymiä. Uusien liittymien vakiintunut nimeämistekniikka lisää versionumeron
608 Sovelluksen tietojen ulkoistaminen liittymän nimen perään. Jos esimerkiksi alkuperäinen IClassFactoryliittymä on päivitetty lisenssien käyttöä tukevaksi, voivat luokkatehtaat toteuttaa ylimääräisenä metodina liittymän IClassFactory2. Asiakasohjelmat voivat siten pyytää käyttöön liittymää IClassFactory ja hakea kehittyneemmän liittymän IClassFactory2 käyttöön QueryInterface()-funktiolla. Ellei COM-luokan factory-olio tarjoa tätä liittymää, QueryInterface() asettaa liittymäosoittimeksi NULL ja palauttaa S_FALSE-koodin. Koska ainoastaan liittymän määrittelyllä on merkitystä, uuden liittymän toteutuskoodissa voidaan vapaasti käyttää vanhasta versiosta muuttumattomien funktioiden koodia. Microsoft Word: automaatiopalvelin Jos koneeseesi on asennettu Microsoft Outlook ja Microsoft Word, saatat huomata Outlookilla sähköpostiviestiä kirjoittaessasi, että Outlookissa toimivat saman kieliasun tarkistustoiminnot kuin Wordissa. Tämä on esimerkki Wordin toimimisesta automaatiopalvelimena Outlookille. Kieliasun tarkistuksen tekee siis Word toimien näkymättömänä ja Outlookin käynnistämänä taustalla. OLE-automaatio Alkuaan OLE-termi (Object Linking and Embedding) kuvasi eri objektityyppien lisäämistä toisentyyppisten objektien dokumentteihin, esimerkiksi Excel-laskentataulukon lisääminen Word-dokumenttiin. Linkitystä ja upottamista (embedding) tukevat dokumentit (asiakirjat) ovat ns. yhdistelmädokumentteja (compound document). Useassa sovelluksessa Lisää-valikosta löytyy Objekti-komento (Insert/Object). Tätä komentoa tukevat sovellukset käyttävät yhdistelmädokumentteja, joihin voit lisätä objekteja rekisteröityjen OLE-palvelimien luettelosta. Lisätyt objektit voidaan upottaa dokumenttiin eli tallentaa sen mukana, tai linkittää, jolloin dokumenttiin lisätään viittaus toiseen tiedostoon (moniker). Tämä kaksi toimintoa antavat juuri nimen OLElle. Termi on kuitenkin laajentunut kattamaan myös OLE vedä ja pudota toiminnon ja OLE-automaation, jolloin ohjelma voi suorittaa funktioita toisesta ohjelmasta käyttäjän huomaamatta toisen ohjelman olemassaoloa. Dispatch-liittymä Automaatio-ominaisuudet pohjaavat erään COM-objektin pääliittymän, IDispatch-liittymän, määrittelyyn. COM-objekti, joka tarjoaa dispatch-liittymän, sisältää taulukon nimetyistä metodeista (funktioista), tapahtumista (funktioista, jotka rekisteröivät itsensä kutsuttaviksi tietyissä tilanteissa) ja ominaisuuksista (funktioista, jotka hakevat tai asettavat COM-objektin erikoismuuttujia). Asiakasohjelma voi käsitellä tällaista tietoa dynaamisesti (ns. myöhäinen sidonta) ajon
OSA VI LUKU 25 609 aikana ja selvittää automaatio-objektin tietoja. IDispatch-liittymä laajentaa IUnknown-liittymää näillä neljällä funktiolla: GetTypeInfoCount() Kertoo asiakasohjelmalle, onko tyyppitietoa tarjolla. GetTypeInfo() Hakee tyyppitiedot. GetIDsOfNames() Palauttaa joukon taulukon indeksejä (Dispatch ID:t), jotka vastaavat pyydettyjen metodien, tapahtumien tai ominaisuuksien nimiä. Invoke() Kutsuu jotain dispatch-taulukon funktiota välittäen parametrinä dispatch ID:n ja muita VARIANT-tyyppisiä parametrejä ja palauttaa tuloksen VARIANT-tyyppisenä (VARIANTtietotyypistä lisää seuraavassa kappaleessa). - IDispatch-liittymää käyttävät paljon monet OLE-toiminnot, kuten ActiveX-kontrollit, OLE-dokumentit ja Visual Basic Scripting. Funktioiden hakeminen ajon aikana taulukosta on toki paljon hitaampaa kuin liittymän metodien kutsuminen suoraan, mutta se tekee järjestelmästä joustavamman. Kokonaisia tyyppitietokirjastoja voidaan helposti tuottaa.tlb-tiedostoiksi ja näitä käyttäen Visual C++ voi muodostaa nopeasti runkoluokat (dispatch drivers). Näitä voidaan kutsua metodeilla, jotka näyttävät aivan automaatio-objektin paikallisilta metodeilta. Nämä runkoluokat hyväksyvät oikeat parametrit, muuttavat ne VARIANT-taulukoksi ja kutsuvat Invoke()-funktiota oikean OLE-automaatio-objektin kutsumiseksi. VARIANT-tyyppisten arvojen käyttö Koska yhtä ainoaa Invoke()-funktiota kutsutaan monenlaisten OLEautomaatio-objektin funktioiden kutsumiseksi, sen on kyettävä välittämään hyvin monenlaisia parametrejä eri kutsuttaville funktioille. Tämä tehdään välittämällä parametrinä DISPPARAMS-tietorakenne, joka osoittaa VARIANT-tietorakenteiden taulukkoon. VARIANT-tietorakenne on käytännössä C++:n unioni eri OLE:n hallitsemista tietotyypeistä. Siinä on myös VARTYPE-tyyppilippu nimeltään vt, joka kertoo, mitä tietotyyppiä käytetään. Taulukossa 25.2 on esitetty osa VARIANT-tietorakenteen hallitsemista tietotyypeistä. VARIANTissa voi olla myös osoitin kuhunkin objektiin, mikä ilmaistaan nimen eteen lisätyllä p-kirjaimella (esim. plval) ja lisäämällä tyyppilippuun VT_BYREF (esim. VT_I4 VT_BYREF). Kaksoisliittymät Saatat törmätä dual interface - termiin (kaksoisliittymä) COMin ja OLEn yhteydessä. COM-objekti voi tukea kaksoisliittymiä tarjoamalla pääsyn liittymänsä toimintoihin suoran COMliittymän ja dispatch-liittymän kautta. Tämä tarjoaa asiakasohjelmalle molempien tapojen parhaat puolet: nopeat C++ohjelmat pääsevät objektiin käsiksi suoraan nopean suoran COM-liittymän kautta ja Visual Basic ja skriptikielet hitaamman dispatch-liittymän kautta. Variant-taulukoiden välittäminen Voit välittää kokonaisia varianttaulukoita OLE-funktioille COleSafeArray-luokan avulla. Tämä luokka sallii elementtien tyypin ja määrän sekä taulukon dimensioiden määrittämisen, jolloin yhdellä kutsulla voidaan syöttää suuria tietolohkoja. Yksityiskohtia COleSafeArrayluokan käytöstä löydät Microsoftin vakiodokumentoinnista.
610 Sovelluksen tietojen ulkoistaminen TAULUKKO 25.2 Osa variant-tietotyypeistä Tietotyyppi Nimi Tyyppilippu Kuvaus unsigned char bval VT_UI1 Yksitavuinen etumerkitön arvo short ival VT_I2 Kaksitavuinen etumerkillinen arvo long lval VT_I4 Nelitavuinen etumerkillinen arvo float fltval VT_R4 Nelitavuinen liukuluku double dblval VT_R8 Kahdeksantavuinen liukuluku BOOL boolval VT_BOOL Nelitavuinen TRUE- tai FALSE-arvo SCODE scode VT_ERROR COM-virhekoodi DATE date VT_DATE COleDateTime-yhteen sopiva liukulukuarvo BSTR bstrval VT_BSTR Visual Basic yhteensopiva merkkijono, joka voidaan muuntaa CStringiksi tai CStringistä IUnknown punkval VT_UNKNOWN Osoitin IUnknownliittymään IDispatch pdispval VT_DISPATCH Osoitin IDispatchliittymään Variantin tietotyypit eivät riipu ohjelmointikielestä ja niitä ymmärtää jokainen OLE-yhteensopiva kieli. BSTR:n (Visual Basic yhteensopiva) ja MFC CString-oliota muunnettaessa voit käyttää CStringin AllocateSysString() ja SetSysString()-funktioita, jotka varaavat uuden BSTR:n ja asettavat olemassa olevan BSTR:n CStringin sisällön mukaiseksi. Käänteisesti muunnettaessa käytetään tyyppimuunnosoperaattoria (char*) tai (const char*) muuttamaan BSTR-merkkijono nollaan päättyväksi merkkijonoksi. COleDateTimehyväksyy myös VARIANT-tietorakenteen suoraan muodostimen parametrinä tai siitä (DATE)-tyyppimuunnoksella
irrotetun DATE-tyypin. Variant-tyypit eivät ole varatut ainoastaan IDispatch-liittymälle, vaan niitä käytetään OLEssa useissa paikoissa, kun tarvitaan useantyyppisen tiedon tallentamista ja siirtämistä. OSA VI LUKU 25 611 Automaatiopalvelimen luominen Useat sovellukset ovat sellaisenaankin jo automaatiopalvelimia (kuten Word, Excel tai Visual Studio). Nämä tarjoavat asiakasohjelmalle IDispatch-liittymän, jolla sovelluksen funktioita, kuten oikeinkirjoitusta, voidaan kutsua. VB Scripting (Visual Basic Scripting) -makrotyökalu käyttää näitä metodeja automaatiopalvelindokumenttien luomiseen ja muokkaamiseen. Aina kun kirjoitat makron jollekin näistä ohjelmista, kirjoitat oikeastaan rajattua Visual Basicia, joka osaa kutsua funktioita kunkin automaatiopalvelimen IDispatch-liittymästä käsin. Automaatiopalvelimiin liitetään yleensä luettava nimi, jonka avulla niiden CLSID-luokkatunnus on löydettävissä. Nämä nimet tallennetaan Järjestelmärekisteriin HKEY_CLASSES_ROOT-avaimen alle ja niillä on automaatiopalvelimen CLSID-tunnukseen viittaavat aliavaimet. Esimerkiksi Microsoft Visual Studiolla on seuraava alkio Järjestelmärekisterissä ja vastaava CLSID-tunnus: HKEY_CLASSES_ROOT\MSDEV.APPLICATION\CLSID = {FB7FDAE2-89B8-11CF-9BE8-00A0C90A632C} Asiakas voi hakea tämän numeerisen arvon syöttämällä CLSIDFromString()-funktiolle selväkielisen merkkijonon (MSDEV.APPLICATION), joka palauttaa osoittimen numeerisen CLSIDarvon (FB7FDAE2-89B8-11CF-9BE8-00A0C90A632C) sisältävään tietorakenteeseen. Tämän jälkeen asiakasohjelma voi luoda taustalla näkymättömänä ajettavan Developer Studion ilmentymän CoCreateInstance()- funktiolla pyytäen osoitinta sen IDispatch-liittymään. Automaatiopalvelimen.exe-ohjelmat voidaan käynnistää /Automation-komentorivilipulla, jolloin niiden ikkunoita ei näytetä. Tässä tilassa ajettaessa palvelimen kanssa kommunikoidaan IDispatch-liittymän avulla. Muun muassa Word, Excel ja monet muut automaatiopalvelimet toimivat näin, jolloin asiakasohjelmat voivat käyttää hyväksi niiden toimintoja käyttäjän tietämättä mitään ohjelmien välisestä tiedonsiirrosta. Ajossa olevien objektien taulukko Ajossa olevat automaatiopalvelimet rekisteröivät itsensä yleensä Running Object Table - taulukkoon (ROT), jotta automaatioasiakkaat voisivat kytkeytyä ajossa olevaan ilmentymään. Tämä rekisteröinti tehdään moniker-nimisen mekanismin avulla, jolla voidaan antaa tunniste ohjelmalle, yhdistelmädokumenteille tai tietolohkoille. Nämä rekisteröidyt ajossa olevat objektit saadaan selville...\microsoft Visual Studio\Common\Tools\IRotview.exeohjelmalla. IRotview-ohjelmaa ajettaessa näytetään ajossa olevat (ja rekisteröidyt) objektit. Esimerkiksi Visual Studio rekisteröi CLSID-luokkatunnuksensa monikeriksi!(fb7fdae2-89b8-11cf-9be8-00a0c90a632c). Jos lataat Microsoft Word -dokumentin, näet dokumentin nimen rekisteröityneenä ROTtaulukossa.
612 Sovelluksen tietojen ulkoistaminen Voit luoda sovellusrungon omalle automaatiopalvelimellesi AppWizardilla valitsemalla Automation-asetukset AppWizardin vaiheessa Step 3 (kuva 25.3). KUVA 25.3 Automaatiotuen lisääminen AppWizardilla. Kun luot automaatiosovellusrunkoprojektin, huomaat parin ylimääräisen tiedoston ilmestyvän mukaan. Toinen näistä on.reg, jossa on uuden automaatiopalvelimen vaatimat rekisterilisäykset. Tarvitset tätä tiedostoa ainoastaan, jos aiot asentaa automaatiopalvelimesi toiseen koneeseen asennusohjelmalla. Palvelinohjelmasi tekee kyllä lisäykset Järjestelmärekisteriin automaattisesti, kun ajat sen ilman /Automation-lippua. AppWizard valitsee dokumentillesi automaattisesti uuden CLSID:n GUID-tunnuksen, jonka näet halutessasi.reg-tiedostosta. Jos esimerkiksi luot automaatiopalvelimen sovellusrungon nimeltään Autoserver, generoidaan seuraavat Järjestelmärekisteriin lisättävät alkiot.regtiedostoon: HKEY_CLASSES_ROOT\Autoserver.Document = Autose Document HKEY_CLASSES_ROOT\Autoserver.Document\CLSID = {D11ED783-CFD8-11D1-931D-444553540000} HKEY_CLASSES_ROOT\Autoserver.Document\CLSID\{D11ED783- CFD8-11D1-931D-444553540000} = Autose Document HKEY_CLASSES_ROOT\Autoserver.Document\CLSID\{D11ED783- CFD8-11D1-931D-444553540000}\ProgId = Autoserver.Document HKEY_CLASSES_ROOT\Autoserver.Document\CLSID\{D11ED783- CFD8-11D1-931D-444553540000}\LocalServer32 = AUTOSERVER.EXE
OSA VI LUKU 25 613 Toinen lisätiedosto on.odl-tiedosto. Tässä sijaitsee odl-kielinen (Object Definition Language, objektin kuvauskieli) kuvaus, jonka mukaan uudelle automaatiopalvelimelle tehdään tyyppikirjasto. Kun lisäät automaatiopalvelimeesi uusia metodeja tai ominaisuuksia, kirjoitetaan tähän tiedostoon uusia alkioita kuvaamaan funktioiden parametrejä. Et joudu huolehtimaan tämän tiedoston ylläpidosta tai kääntämisestä, vaan tiedostoa päivittää ClassWizard ja se käännetään automaattisesti aina projektia käännettäessä. Listauksessa 25.1 on esimerkki Autoserver-automaatiopalvelimen.odltiedostosta. Palvelimella on ainoastaan yksi metodi, SquareRoot(), joka on määritelty IAutoserver-nimisessä dispatch-liittymässä, rivillä 24. Koko liittymän määrittely on esitetty riveillä 11 26. Tässä esitetty Autoserver-esimerkki toteuttaa SquareRoot()-metodin, joka palauttaa double-tyyppisen syöttöparametrin double-tyyppistä neliöjuurta kuvaavan arvon. LISTAUS 25.1 LST25_1.CPP Autoserver-nimisen yhden metodin sisältävän automaatiopalvelimen odl-tiedosto MkTypLib- ja MIDLtyyppikirjastot Aiemmin objektien tyyppikirjastot käännettiin odl-tiedostoista MkTypLib-työkalulla. Vastaavasti liittymien idl-tiedostoissa määriteltiin liittymät, muttei kyetty luomaan tyyppikirjastoja. Microsoft on järkeistänyt tilannetta lisäämällä idltiedostoihin tyyppimääritysominaisuudet. Uusi Microsoftin IDL-kääntäjä (MIDL) osaa kääntää sekä.idlettä.odl-tiedostoja (ja MkTypLibohjelmaa ei enää tarvita). Ja vaikka.odl-tiedostoja yhä käytetään, ne eivät ole välttämättömiä tyyppikirjastojen luonnille, koska vaaditut määrittelyt voidaan tehdä.idltiedostoihinkin. 1 // autoserver.odl : type library source for autoserver.exe 2 // This file will be processed by the MIDL compiler to 3 // produce the type library (autoserver.tlb). 4 [ uuid(d11ed784-cfd8-11d1-931d-444553540000), version(1.0) ] 5 library Autoserver 6 { 7 importlib("stdole32.tlb"); 8 9 // Primary dispatch interface for CAutoserverDoc 10 11 [ uuid(d11ed785-cfd8-11d1-931d-444553540000) ] 12 dispinterface IAutoserver 13 { 14 properties: 15 // NOTE - ClassWizard will maintain property information here. 16 // Use extreme caution when editing this section. 17 //{{AFX_ODL_PROP(CAutoserverDoc) 1 18 //}}AFX_ODL_PROP 19 20 methods: 21 // NOTE - ClassWizard will maintain method information here. 22 // Use extreme caution when editing this section. 23 //{{AFX_ODL_METHOD(CAutoserverDoc) 2 24 [id(1)] double SquareRoot(double dinputval); 25 //}}AFX_ODL_METHOD 1 ClassWizard lisää tähän OLEominaisuudet get/set-metodeina. 2 ClassWizard lisää tähän OLEominaisuudet.
614 3 Tässä osassa määritellään dokumentti yhden dispatchliittymän luokaksi. Sovelluksen tietojen ulkoistaminen 26 }; 27 // Class information for CAutoserverDoc 28 29 [ uuid(d11ed783-cfd8-11d1-931d-444553540000) ] 3 30 coclass Document 31 { 32 [default] dispinterface IAutoserver; 33 }; 34 //{{AFX_APPEND_ODL}} 35 //}}AFX_APPEND_ODL}} 36 }; AppWizard lisää myös lähdekoodia vakiosovellusrunkoon OLEautomaatiota varten. Sovellusluokan (CMyServerApp) InitInstance()-funktiossa tulee alustaa OLE- ja COM-kirjastot, mikä tehdään kutsumalla AfxOleInit()-funktiota. Löydät myös uuden rivin, joka yhdistää sovelluksen dokumenttimallin uuteen COleTemplateServer-tyyppiseen m_server-muuttujaan: Komentorivin Embedded ja Automation -argumentit Kun asiakasohjelma kutsuu funktioita automaatiopalvelimen.exe-sovelluksesta, automaatiopalvelin (kuten Word) tulee ajaa näkymättömissä taustalla. OLE suorittaa tämän käynnistämällä palvelinohjelman komentoriviltä annetulla /Automation-lipulla. Palvelimen InitInstance()- funktiosta kutsuttu ParseCommandLine()-funktio asettaa CCommandLineInfo-olion m_bautomated-lipun. Sovellus voi sitten tutkia tätä lippua, joka kertoo, että sovellusta ajetaan asiakasohjelman kutsumana automaatiopalvelimena eikä itsenäisenä sovelluksena. Jos ohjelma käynnistetään Embedded-argumentilla, sovellus tietää, että sitä käytetään yhdistelmädokumenttiin upotetun objektin käsittelemiseen. m_server.connecttemplate(clsid, pdoctemplate, TRUE); Tämä mallipalvelin on johdettu COleObjectFactory-luokasta, joka on OLEn oma toteutus luokkatehtaasta. Asiakassovellukset luovat tämän luokkatehtaan avulla uusia ilmentymiä automaatiopalvelimen dokumenttioliosta joko CoCreateInstance()-funktiolla tai funktioilla CoGetClassObject() ja CreateInstance(). Jos sovellusta kutsutaan asiakassovelluksesta automaatiopalvelimena, se voi rekisteröidä OLE-automaatio-objektinsa käyttövalmiiksi seuraavilla InitInstance():n lisäriveillä: if (cmdinfo.m_brunembedded cmdinfo.m_brunautomated) { COleTemplateServer::RegisterAll(); return TRUE; } Muussa tapauksessa voit ajaa automaatiopalvelinta tavallisena sovelluksena ja seuraavat rivit luovat vaadittavat avaimet Järjestelmärekisteriin: m_server.updateregistry(oat_dispatch_object); COleObjectFactory::UpdateRegistryAll(); Kun tutkit AppWizardin tekemää dokumenttiluokkaa, huomaat, että sinnekin on lisätty koodia tekemään dokumentista itsestään automaatio-objekti. Dokumentin dispatch-liittymän ID-tunnus määritellään static
const tyyppisenä GUID-arvona: static const IID IID_IAutoserver = {0xd11ed785, 0xcfd8, 0x11d1, {0x93, 0x1d, 0x44, 0x45, 0x53, 0x54, 0x0, 0x0 } }; Itse dispatch-liittymä toteutetaan AppWizardista saatujen makrojen avulla: BEGIN_INTERFACE_MAP(CAutoserverDoc, CDocument) INTERFACE_PART(CAutoserverDoc, IID_IAutoserver, Dispatch) END_INTERFACE_MAP() Toiset makrot puolestaan lisäävät metodeja tähän dispatchhakutaulukkoon. Esimerkiksi Autoserver-dokumenttiin lisätty uusi SquareRoot()-metodi näyttäisi tältä: BEGIN_DISPATCH_MAP(CAutoserverDoc, CDocument) //{{AFX_DISPATCH_MAP(CAutoserverDoc) DISP_FUNCTION(CAutoserverDoc, SquareRoot, SquareRoot, VT_R8, VTS_R8) //}}AFX_DISPATCH_MAP END_DISPATCH_MAP() Merkkijono SquareRoot antaa dispatch-liittymälle metodin nimen ja VT_R8 sekä VTS_R8 liput määrittävät parametrin ja paluuarvon tyypin (eli double-tyyppisiä kaksoistarkkuuden liukulukuja). OSA VI LUKU 25 615 Älä mene muuttamaan itse näiden makrojen sisältöä ClassWizard huolehtii siitä lisätessäsi uusia automaatiometodeja. Automaatiopalvelimen metodin lisääminen ClassWizardilla 1. Käynnistä ClassWizard näppäilemällä Ctrl+W tai valitsemalla View-valikosta ClassWizard. 2. Valitse Automation-välilehti ja varmista, että Class Name yhdistelmäruudussa on valittu dokumenttiluokka. 3. Lisää uusi metodi Add Method painiketta napauttamalla. 4. Syötä metodin ulkoinen, asiakassovelluksille näkymä nimi, esimerkiksi CAutoserverDoc-luokalle SquareRoot(kuva 25.4). 5. Alla pitäisi näkyä kutsuttavan funktion sisäinen nimi: voit muuttaa nimeä, mutta se jätetään yleensä oletusarvoonsa. 6. Valitse uuden metodin palauttama tietotyyppi Return Type yhdistelmäruudusta. (Autoserver-esimerkissä tämä on double). DISP_FUNCTION-makron parametrit DISP_FUNCTION-makron ensimmäinen parametri kertoo funktion toteuttavan luokan nimen. Toinen parametri määrittää funktion ulkoisen nimen, jonka automaatioasiakkaat näkevät tutkimalla palvelimen dispatch-liittymää. Kolmas parametri on toteutusfunktion nimi ja neljäs määrittää funktion palauttaman VARIANT-tietotyypin. Viimeinen parametri on välilyönnein erotettu luettelo funtiolle välitettyjä VARIANTparametrejä. Nämä parametrit määritellään (AFXDISP.H:ssa) VTS_-alkuisina vakioina, jotka myöhemmin muunnetaan VARIANT-tietotyypin määrittäviksi indekseiksi.
616 Sovelluksen tietojen ulkoistaminen KUVA 25.4 Uuden metodin lisääminen automaatiopalvelimeen ClassWizardilla. 7. Anna metodien parametrien nimet Parameter List -luetteloon (tässä ainoa parametri on nimeltään dinputval). 8. Valitse parametrin tyyppi uuden parametrin kohdalta Typesarakeotsikon alta (tässä double). 9. Mikäli asiakassovellus välittää automaatiopalvelimen funktiolle useampia parametrejä, toista vaiheet 7 ja 8. 10. Lisää uusi metodi napauttamalla OK. Metodin tulisi ilmestyä ClassWizardin Automation-välilehdelle ulkoisten nimien joukkoon. 11. Aloita uuden metodin koodin muokkaaminen Edit Code painikkeella. Kun olet lisännyt metodin, voit lisätä siihen toteutuskoodin automaatiopalvelimen tarkoituksen mukaan. Autoserver-esimerkin SquareRoot()-metodi palauttaa yksinkertaisesti syöttöarvon neliöjuuren kutsumalla matematiikkafunktiota sqrt(): #include math.h double CAutoserverDoc::SquareRoot(double dinputval) { return sqrt(dinputval); } Uuden automaatiometodin lisäämiseen tarvittiin siis ClassWizardin toimenpiteet sekä oma toteutuskoodisi. ClassWizard lisää automaattisesti rivit dispatch-hakutaulukkoon (DISPATCH_MAP) ja.odl-tiedostoon
vaadittujen OLE-automaatiopalvelimen tyyppitietojen lisäämiseksi. Kun käännät uuden automaatiopalvelimesi, huomaat, että.odl-tiedosto on käännetty yhdessä lähdekoodisi kanssa. Käännöstulos löytyy Debughakemistosta (tai Release).tlb-tiedostona, jossa on uusien dispatchmetodien tyyppitietokirjaston määrittelyt. OSA VI LUKU 25 617 Tämän tyyppikirjaston avulla voit tehdä asiakasohjelmassa OLE dispatch ohjainluokan (mikä esitetään seuraavassa kappaleessa). Omien tyyppikirjastojen jakelu kannattaa, koska joku saattaa haluta kirjoittaa oman asiakasohjelmansa kutsumaan automaatiopalvelintasi. Voit ladata esimerkiksi Microsoftin webbipalvelusta (www.microsoft.com/officedev) Wordin tyyppikirjastoja ja rakentaa oman asiakasohjelmasi käyttämään metodeja Wordista. Automaatioasiakasohjelman luominen Voit luoda automaatiopalvelinten ilmentymiä ja kutsua niiden metodeja millaisesta asiakasohjelmasta tahansa. Ensimmäinen tehtävä näissä on OLE-kirjastojen alustaminen. Tämä tehdään yksinkertaisesti kutsumalla sovellusluokan InitInstance()-funktiossa AfxOleInit()- funktiota: BOOL CAutoclientApp::InitInstance() { AfxOleInit(); } Tämän jälkeen voit luoda OLE dispatch ohjainluokan tyyppikirjastosta ClassWizardilla ja näin yksinkertaistaa automaatiopalvelimen metodien kutsumista. Tyyppikirjastot Muista, että staattiset kirjastot (.lib) ja dynaamiset linkkikirjastot (.dll) tallentavat suoritettavan koodin, mutta tyyppikirjastot (.tlb) ainoastaan OLEautomaatiopalvelimien parametrien ja paluuarvon tyyppimäärittelyt. Automaatiopalvelimen suoritettava koodi sijaitsee itse automaatiopalvelinohjelmassa. Jos esimerkiksi imuroit Microsoft Wordin tyyppikirjaston, käytössäsi on ainoastaan tieto siitä, kuinka Wordin funktioille välitetään parametrit ja mitä tuloksia ne palauttavat. Näiden funktioiden kutsumiseksi tulee koneessasi olla itse Word asennettuna. Dispatch ohjainluokan lisääminen tyyppikirjastosta ClassWizardilla 1. Käynnistä ClassWizard näppäilemällä Ctrl+W tai valitsemalla View-valikosta ClassWizard. 2. Valitse Automation-välilehti. 3. Napauta Add Class painiketta ja valitse valikosta From a Type Library komento. 4. Etsi selaamalla haluttu tyyppikirjasto. Tämä saattaa olla.tlb-,.olb- tai.dll-tiedostossa.
618 Sovelluksen tietojen ulkoistaminen 5. Kun olet löytänyt halutun tiedoston, kaksoisnapauta sitä ja valitse Open, jolloin saat esiin Confirm Classes valintaikkunan. 6. Voit muuttaa kustakin tuotavasta luokasta generoitavien luokkien ja toteutustiedostojen oletusnimiä valitsemalla luokan ja muuttamalla nimet Class Name-, Header File- ja Implementation File tekstiruutuihin. 7. Tuo valitut luokat projektiin napauttamalla OK. Dispatch-ohjainluokkien nimet On harmillinen sattuma, että ClassWizardin tekemien dispatch-ohjainluokkien oletusnimet alkavat I:llä. Tämä sekoittaa luokan nimen COMliittymämäärittelyyn (nämä ovat kaksi eri asiaa). OLEn dispatchohjainluokka on vain kätevä paketointitapa automaatiopalvelinobjektin kutsumiseksi IDispatch-liittymän Invoke()- funktiolla. Älä siis sekoita dispatch-ohjainluokkien nimiä COM-liittymien nimiin. Tässä voisit käyttää edellisessä esimerkissä tuotettua tyyppikirjastoa (autoserver.tlb) tiedostojen autoserver.cpp ja autoserver.h generoimiseksi. Näissä on IAutoserver dispatch ohjainluokka. Dispatch-ohjainluokalla on tynkäfunktioita, jotka kutsuvat (apufunktion kautta) Invoke-funktiota oikean automaatiopalvelimen dispatch-liittymän funktion kutsumiseksi taulukosta haetun funktion ID-tunnuksen mukaan. Esimerkiksi Autoserver-palvelimen SquareRoot()-funktio näyttää tältä dispatch-ohjainluokassa: double IAutoserver::SquareRoot(double dinputval) { double result; static BYTE parms[] = VTS_R8; InvokeHelper(0x1, DISPATCH_METHOD, VT_R8, (void*)&result, parms, dinputval); return result; } Edellisen InvokeHelper()-funktion ensimmäinen parametri (0x1) on SquareRoot()-funktion ID-tunnus automaatiopalvelimen dispatchliittymän funktiotaulukossa. Toinen parametri on lippu, joka määrittää, että kyseessä on metodin, eikä tapahtuman tai ominaisuuden käsittelyn kutsu. Kolmas parametri on paluuarvo, joka tallennetaan resultmuuttujaan. Neljäs parametri, parms, määrittää parametrien formaatin. Tämän jälkeen tulevat varsinaiset parametrit, jotka InvokeHelper() paketoi DISPPARMS-tietorakenteeksi Invoke()- funktiolle. Ennen kuin pääset käyttämään näitä automaatiopalvelimen funktioita, joudut hankkimaan voimassa olevan dispatch-osoittimen olioon. Kun tutkit dispatch-ohjainluokan header-tiedostoa, huomaat, että se on johdettu COleDispatchDriver-luokasta. Tämän luokan jäsenfunktioiden avulla voidaan luoda osoitin dispatch-liittymän.
OSA VI LUKU 25 619 Voit kutsua dispatch-ohjainluokan CreateDispatch()-metodia ja syöttää sille OLEn ymmärtämän haluttuun luokkaan liitetyn merkkijonon (esimerkiksi Autoserver.document ). Tämän jälkeen CreateDispatch()-funktio etsii parametriin liitetyn CLSID-tunnuksen Järjestelmärekisteristä ja luo halutun automaatiopalvelinluokan ilmentymän. Funktion onnistuessa palautetaan TRUE ja ohjainluokan metodeja päästään kutsumaan. Listauksessa 25.2 on osa uuden projektiin tuodun dispatch-ohjainluokan ilmentymän luovasta koodista. Lisäksi koodi luo dispatchliittymän ja kutsuu automaatiopalvelimen metodia. Voit tehdä valintaikkunasovelluksen Autoclient automaatiopalvelimen koodin testaamiseksi. Lisää listauksen 25.2 koodi valintaikkunasovelluksen OnInitDialog()-funktion loppuun (tiedostoon autoclientdlg.cpp). Rivin 1 #include-lause kuuluu lähdekooditiedoston alkuun. Kun käännät ja ajat sovelluksen, tulisi sen näyttää kuvan 25.2 sanomaruutu. Automaatiopalvelin ladataan ajettavaksi automaattisesti neliöjuuren laskemista varten. LISTAUS 25.2 LST25_2.CPP Automaatiopalvelimessa olevan funktion alustaminen ja kutsuminen dispatch-ohjainluokan kautta 1 // Include the dispatch driver class header file 2 #include autoserver.h 3 4 //.. Function declaration 5 6 // ** Initialize the dispatch driver class 7 IAutoserver IAutoserver; 8 9 // ** Create an instance of the server and connect 10 // ** to ites dispatch interface 11 if (IAutoserver.CreateDispatch("Autoserver.document")) 1 12 { 13 // ** Call the SquareRoot method on the server program 14 double dnumber = 36.0; 15 double droot = IAutoserver.SquareRoot(dNumber); 16 17 // ** Display the result 18 CString strmsg; 19 strmsg.format("root of %f = %f\n",dnumber,droot); 20 AfxMessageBox(strMsg); 21 } Automaatio-objektin Järjestelmärekisteriasetukset Automaatio-objektien luettelo löytyy HKEY_CLASSES_ROOTrekisteriavaimen alta. Jokaisen automatiopalvelimen alla on CLSID-avain, jossa on kunkin automaatiopalvelimen yksilöivää 128-bittistä GUID-arvoa vastaava merkkijono. 1 CreateDispatch() hakee annetun nimen CLSID:n ja kutsuu CoCreateInstance()-funktiota pyytäen dispatch-liittymää.
620 Sovelluksen tietojen ulkoistaminen Listauksen 25.2 rivillä 2 otetaan koodiin mukaan #include-lauseella dispatch-ohjainluokan määrittely (joka on luotu tyyppikirjastosta). Riveillä 6 21 on moodi, joka kutsuu automaatiopalvelinta. Dispatchohjainluokan ilmentymä määritellään rivillä 7. CreateDispatch()- jäsenmetodia kutsutaan rivillä 11 automaatiopalvelimen dokumenttiolion ilmentymän luomiseksi ja funktio myös tallentaa dispatchliittymäosoittimensa (m_ldispatch-jäseneen). Jos yhteys onnistuu, kutsutaan rivin 15 SquareRoot()-metodia ja sen paluuarvo, droot, näytetään sanomaruudussa rivillä 20 (kuva 25.5). Kun IAutoserverluokan voimassaoloalue päättyy, vapautetaan dispatch-liittymä automaattisesti luokan tuhoajafunktiossa. Vaihtoehtoisesti voit kutsua dispatch-ohjaimen ReleaseDispatch()- jäsenfunktiota dispatch-ohjaimen vapauttamiseksi. Ohjaimella on myös funktiot AttachDispatch() ja DetachDispatch() dispatch-liittymäosoittimen liittämistä ja jo yhdistetyn osoittimen irrottamista varten automaatiopalvelinoliosta. KUVA 25.5 OLE-automaatioesimerkin toiminta.
OSA VI LUKU 25 621 OLE-säilöt, -palvelimet ja minipalvelimet OLE-palvelin on sovellus, joka voidaan käynnistää säilösovelluksesta käsin. Esimerkiksi Excel-laskentataulukko voidaan lisätä ja sitä voidaan muokata Wordissä. Tässä tapauksessa Word toimii OLE-säilönä (container) ja Excel OLE-palvelinsovelluksena. Sovellukset voivat olla sekä OLE-säilöjä että OLE-palvelimia, jolloin ne voivat toimia jommassa kummassa roolissa. Word ja Excel ovat hyviä esimerkkejä tästä, koska voit lisätä Exceliin Word-dokumentteja ja päinvastoin. Aito palvelin voi toimia sovelluksena itsenäisesti tai säilöön lisättynä objektina, mutta minipalvelimet voidaan ainoastaan käynnistää säilösovelluksesta. OLE-säilöt tarjoavat Lisää-valikossa Objekti-komennon (Insert/ Object). Esimerkiksi Windowsin WordPad on OLE-palvelin. Voit testata OLE-palvelimia WordPadin avulla valitsemalla sen Lisäävalikosta Objekti-komennon. Tällöin näet luettelon rekisteröidyistä OLE-palvelimista, jotka voidaan liittää WordPad-dokumenttiin. Kun OLE-palvelinobjekti lisätään dokumenttiin, sitä voidaan muokata paksulla katkoviivalla rajatussa suorakulmiossa. Tämä toimenpide on nimeltään paikallaan aktivointi (in-place activation) ja sen aluksi säilö syöttää palvelimelle lippuarvoja, verbejä. Kun OLE-palvelinobjekti ei ole enää aktiivinen, sen reunus häviää näkyvistä ja objektina näytetään siihen viimeksi piirretty kuva (tämän tallentaa ja esittää metatiedostopiirtopinta). Kun objekti on aktiivinen, sen reunus näytetään ja paikallaan aktivoitavan palvelinobjektin valikkokomennot lisätään säilösovelluksen valikkoon. Säilödokumentti osaa serialisoida siihen upotetun dokumentin tiedot tavallisia serialisointifunktioita käyttäen. Palvelimen dokumentit saavat säilödokumentilta CArchive-olion, jotta kukin palvelinolio voisi serialisoida omat tietonsa. Voit luoda OLE-palvelimen ja OLE-säilön sovellusrungon AppWizardilla, mutta osa säilön ja palvelimen yhteisestä toiminnasta joudutaan kuitenkin toteuttamaan koodaamalla, jotta molemmat toimisivat kunnolla. OLE-palvelimen sovellusrunkoon joudutaan paikallaan editointia varten lisäämään kaksi luokkaa. Toinen näistä on palvelinalkioluokka, joka on johdettu COleServerItem-kantaluokasta. Tämä edustaa säilössä esitettyä upotettua alkiota ja se osaa piirtää alkion OnDraw()- Aktiiviset dokumentit Saatat törmätä termiin Active Documents OLE-palvelimien ja säilöjen yhteydessä. Kyseessä on varsin uusi ActiveX:ään liittyvä tekniikka, joka laajentaa OLEsäilö/palvelin -arkkitehtuuria antamalla upotetuille objekteille mahdollisuuden hallita koko säilön työaluetta (eikä pientä kehystä) ja muokata suoraan säilön kehystä, valikoita ja työkalurivejä. Saat lisätietoa aktiivisista dokumenteista Microsoftin dokumentoiinnista hakemalla IOleDocumentliittymää ja siihen liittyviä liittymiä. Yhdistelmädokumentit: upotus ja linkitys Vaikka upotettu dokumentti on tallennettu täysin asiakassovelluksen yhdistelmädokumenttiin (compound document), linkitetyt objektit takllennetaan ainoastaan monikereina. Moniker on pieni objekti, joka yksilöi, missä oikea tieto on ja kuinka se tulee esittää ja näyttää asiakassovelluksessa.
622 Sovelluksen tietojen ulkoistaminen funktiolla säilön pyynnöstä. Luokalla on myös OnDoVerb()-funktio, joka käsittelee säilön pyynnöt esittää, avata tai piilottaa säilöön upotettu alkio. Säilö voi myös siirtää tietoa palvelimen kanssa palvelinalkion tarjoaman IOleObject-liittymän kautta. Toinen paikallaan editointi ikkunassa käytettävä luokka on johdettu luokasta COleIPFrameWnd. Tämän luokan avulla toteutetaan palvelimen omat työkalurivit ja valikot säilön työkalurivien ja valikoiden paikalle palvelimen aktivoituessa. Säilölle tehty sovellusrunko lisää ainoastaan yhden luokan, joka on johdettu COleClientItem-kantaluokasta. Tällä uudella luokalla on useita funktioita, joiden avulla yhdistelmädokumenttiin voidaan luoda linkitettyjä tai upotettuja alkioita ja ylläpitää niitä. Se tarjoaa myös säilöpuolen linkin palvelinalkiolle siirtäen tietoa DoVerb()-funktion kautta. Lisäksi luokan funktioilla saadaan ylläpidettyä palvelinalkion paikkaa ja aktivointitilaa. Kun räätälöit näitä kahta palvelinluokkaa ja yhtä säilöluokkaa, voit hyödyntää varsin mutkikkaitakin paikallaan editointi ominaisuuksia säilön sovellusikkunassa. Palvelimen eikä säilönkään tarvitse tietää, millaisessa säilösovelluksessa sitä käytetään tai millainen objekti siihen on upotettu. Kunhan sekä palvelin- että säilöalkiot toimivat saman standardin mukaan, ne voidaan liittää toimimaan saumattomasti yhdessä ja näyttämään käyttäjälle yhdeltä ja samalta sovellukselta.