Samu Kohtala
LuK-tutkielma
10.9.1998
Jyväskylän yliopiston
tietotekniikan laitos
Tiivistelmä. Tässä tutkielmassa kuvataan C++ Builder
ja Delphi-ohjelmointiympäristöjen yhteiskäytön mahdollisuuksia
sekä eri menetelmiä koodin, lomakkeiden ja komponenttien jakamiseen
näiden ohjelmien välillä.
Sisältö
1 Johdanto2 Yleiskatsaus yhteiskäyttöön
2.1 Miksi yhteiskäyttö?
2.2 Koodin yhteiskäytön perusperiaatteet3 Yhteiskäytön yksinkertaiset perusmenetelmät3.1 Delphin ohjelmayksiköiden ja lomakkeiden lisääminen suoraan C++ Builderin projekteihin4 ActiveX4.1 ActiveX-kontrollit5 Objektitiedostot6.1. Dynaamisten linkkikirjastojen perusteet
6.2 Käsittelyrutiinit DLL:ssä7 Yhteenveto
Delphi ja C++ Builder ovat ohjelmointityökaluja Windows 95 ja Windows NT -käyttöjärjestelmiin Inpriseltä (entinen Borland). Delphi ja C++ Builder ovat lisänneet nopeasti suosiotaan helppokäyttöisyytensä ansiosta. Niiden helppokäyttöisyys ja nopeus ohjelmointityökaluina perustuvat visuaaliseen käyttöliittymään ja valmiisiin komponentteihin. Ero käyttäjäystävällisyydessä on suuri etenkin verrattuna vanhempiin Borlandin C++ -sarjan kaltaisiin enemmän koodaamista vaativiin Windowsin ohjelmointityökaluihin. Delphin ja C++ Builderin lisäksi on olemassa muita samantyyppisiä visuaalisia ohjelmien kehitysympäristöjä, kuten esimerkiksi Microsoftin Visual C++ ja Visual Basic sekä Inprisen Java Builder. Delphin ja C++ Builderin yhteiskäyttöä tukevat kuitenkin niiden erityiset yhteiset piirteet ja samanlainen tekninen toteutus.
Delphin ohjelmointikielenä käytetään Object Pascal -kieltä ja C++ Builderin ohjelmointikielenä C++ -kieltä. Tutkielman kirjoittaja on viime aikoina käyttänyt ohjelmointiin molempia kieliä ja Windows -ohjelmointiin lähinnä kyseisiä työkaluja, joten kirjoittajaa alkoi kiinnostaa ajatus C++ Builderin ja Delphin yhteiskäytöstä. Tämä mahdollistaisi mm. toisessa ohjelmointiympäristössä aikaisemmin tehdyn koodin, lomakkeiden ja komponenttien uudelleenhyödyntämisen toisella ohjelmointityökalulla. Tämä tutkielma perustuu lähteisiin [Calvert] ja [Powell] sekä Delphin ja C++ Builderin kolmosversioiden käyttökokemuksiin Windows NT -käyttöjärjestelmässä.
Luvussa 2 kuvataan syitä, miksi Delphin ja C++ Builderin yhteiskäyttö
mahdollisesti kannattaa sekä myös yhteiskäytön teknisiä
perusteita. Luvussa 3 kuvataan yhteiskäytön yksinkertaisimpia
perusmenetelmiä. Luvussa 4 käsitellään ActiveX-kontrollien
ja aktiivilomakkeiden hyödyntämistä yhteiskäytössä.
Luvussa 5 kuvataan lyhyesti objektitiedostojen merkitystä yhteiskäytössä.
Luvussa 6 käsitellään dynaamisia linkkikirjastoja ja miten
niitä voi hyödyntää yhteiskäytössä.
2. Yleiskatsaus yhteiskäytöstä
Tässä luvussa esitellään mahdollisia syitä C++ Builderin ja Delphin yhteiskäyttöön sekä kerrotaan lyhyesti yhteiskäytön perusteista. Luku perustuu lähteeseen [Calvert].
Delphin ja C++ Builderin samankaltaisuudesta huolimatta näissä ohjelmointiympäristöissä on hyödynnettäviä eroja.
Delphi kääntää vastaavan lähdekoodin nopeammin kuin C++ Builder. Tämä johtuu siitä, että Pascal-koodia on helpompi parsia (engl. parse) kuin C++-koodia. Parsiminen tarkoittaa lähdekoodin muuntamista muotoon, josta se sitten käännetään konekielelle. Lisäksi dcu-päätteisestä tiedostonimestä tunnistettavat Delphin binääritiedostot ovat helpompia muodostaa, koska niiden rakenne on yksinkertaisempi ja joustavampi kuin C++ Builderin obj-tarkenteisten binääritiedostojen. Nopeusero on suurimmillaan ensimmäistä kertaa käännettäessä ja projektia uudelleenrakennettaessa (engl. project rebuilding), mutta Delphi on nopeampi muulloinkin.
Delphin ja C++ Builderin nopeuseroa testattiin Run-komennolla yhden lomakkeen sovelluksella. Sovelluksessa oli valikkokomponentti, kolme painonappia ja rivi koodia. Tietokoneen prosessorina oli Pentium 133. Muistia tietokoneessa oli 32 megatavua. Ensimmäistä kertaa ohjelmaa käännettäessä Delphillä kului aikaa 3 sekuntia ja C++ Builderillä 20 sekuntia. Testausta jatkettiin lisäämällä aina yksi painonappi lomakkeelle ja käyttämällä uudelleen Run-komentoa. Toiseen käännökseen Delphillä meni sekunti ja C++ Builderilla 7 sekuntia. Delphillä kului jatkossakin noin yksi sekunti. C++ Builderilla meni kolmannella kerralla 4 sekuntia, neljännellä kerralla 2 sekuntia ja viidennellä kerralla enää hieman yli sekunti. Tämän pienimuotoisen ja hieman epätarkan testin tulos antaa jonkinlaisen käsityksen siitä miten nopeuserot ovat ensimmäisillä käännöskerroilla suurimmat. On huomioitava, että käytettävä laitteisto ja käännettävä ohjelma vaikuttavat nopeuseroihin. C++ Builder vaatii parempaa laitteistoa kuin Delphi toimiakseen tehokkaasti.
C++ ja Object Pascal muistuttavat suuresti syntaksiltaan toisiaan. Pelkistäen voidaan sanoa, että C++-koodi kääntyy hiukan nopeammaksi ohjelmaksi ja C++ tarjoaa hieman suuremmat hallintamahdollisuudet. Toisaalta C++:lla saa helpommin virheitä aikaan ja sen sanotaan olevan jonkin verran vaikeammin opittavan kuin Object Pascalin. C++ on nykyisin todennäköisesti suosituin ja laajimmalle levinnyt ohjelmointikieli. Pascalia käytetään edelleen paljon opetuksessa ja suosittu Delphi on lisännyt Pascalin suosiota. Kielten samankaltaisten rakenteiden ja monien yhtäläisyyksien ansiosta toisen näistä kielistä pystyy oppimaan nopeasti, jos osaa jo toisen kielen ennestään.
Varsinkin useamman henkilön projektissa Delphiä kannattaa käyttää sen nopeuden ja helppouden vuoksi, erityisesti kun aikataulu on kireä. C++ Builderia kannattaa puolestaan käyttää silloin, kun halutaan tehdä mahdollisimman tehokasta koodia ja hyödyntää eräitä C++:n ominaisuuksia. C++:ssa on esimerkiksi olio-ohjelmointiominaisuudet moninperintä (engl. multiple inheritance) ja metodien kuormittaminen (engl. function overloading). C++ Builderillä aikaansaatu koodi voidaan kuitenkin tarvittaessa siirtää Delphiin tai päinvastoin. Toisaalta myös Object Pascalissa on käyttökelpoisia ominaisuuksia, joita ei ole C++:ssa. Tälläisiä ovat mm. joukot ja sisäkkäiset aliohjelmat
Lisäksi mahdollisuus ylläpitää yhtä koodipohjaa ja markkinoida sitten tuotteitaan valintansa mukaan joko Delphi- tai C++ Builder -tuotteina, tai vaihtoehtoisesti Pascal- tai C++ -tuotteina, voi tuoda markkinointietua ohjelmointiyrityksille. Tällöin niiden ei tarvitsisi ylläpitää erikseen molempia koodipohjia.
Paras argumentti yhteiskäytön puolesta lienee mahdollisuus vanhan olemassaolevan C++- ja Pascal-koodin tehokkaampaan hyödyntämiseen. Kaiken koodin kääntäminen toiselle kielelle ei ole välttämättä mielekästä, koska kääntämiseen tarvittava työmäärä voi olla kohtuumaton suuri. Melkein kaiken Delphillä tehdyn, kuten lähdekoodin, lomakkeet, komponentit, voi hyödyntää suoraan C++ Builderissa. Vastaavasti suuren osan C++ Builderillä tehdystä voi hyödyntää Delphissä, joskaan ei ihan yhtä helposti.
2.2. Koodin yhteiskäytön perusperiaatteet
Delphin lomakkeen ohjelmakoodeineen ja komponentteineen voi liittää suoraan C++ Builderin projektiin, jolloin lomake toimii vastaavasti kuin alkuperäinen C++ Builder -lomake. Miten tämä on teknisesti mahdollista? Kyseessähän on kuitenkin kaksi eri ohjelmointiympäristöä, jotka käyttävät eri kieliä.
Ensinnäkin Delphi ja C++ Builder käyttävät samaa 32-bittistä kääntäjää. Ero on vain parsimisessa (engl. parsing). Delphi parsii Object Pascal -koodia ja C++ Builder parsii C++ -kielistä koodia ennen kuin se lähetetään kääntäjälle. Ero on lähinnä siinä, miltä parsimaton koodi näyttää.
Lisäksi C++ Builder käyttää Delphin omaa visuaalista komponenttikirjastoa (engl. visual component library, VCL), joten C++ Builderin on pystyttävä ymmärtämään Delphin tyypit, objektit ja syntaksin. Tämän mahdollistaa osaltaan C++- ja Object Pascal -kielten samanlainen rakenne. C++ ja Pascal ovat rakenteellisesti hyvin lähellä toisiaan (toisin kuin esimerkiksi C++ ja BASIC). Joukko-opillisesti voisi sanoa, että Object Pascal on tavallaan C++:n osajoukko. Suurin osa Object Pascal -kielen ominaisuuksista löytyy myös C++-kielestä.
C++ Builder ja Delphi rakentavat oliot, käsittelevät muuttujatyyppejä ja määrittelevät metodit samaan tapaan. Delphin olioiden virtuaalinen metoditaulu (engl. Virtual method table, Virtual Table, VTable, VMT, VT) on identtinen rakenteeltaan sellaisten C++ -olioiden VMT:n kanssa, jotka ei käytä moniperintää.
C++:n muuttujatyypit ovat suurelta osin melkein identtisiä Delphin Object Pascalin muuttujatyyppien kanssa. Esimerkiksi C++:n int on identtinen Object Pascalin integer-muuttujatyypin kanssa. Myös useat muut muuttujatyypit, kuten merkkijonot, ovat joko identtisiä tai hyvin samanlaisia. Esimerkiksi C++:n char * on identtinen Object Pascalin Pchar-muuttujatyypin kanssa ja C++:n AnsiString on suunniteltu yhteensopivaksi Delphin string-muuttujatyypin kanssa.
Lomakkeiden, ohjelmayksiköiden ja komponenttien suora siirtäminen
ei toimi yhtä helposti C++ Builderista Delphiin. Delphillä ei
ole samanlaista tarvetta parsia C++ -kieltä, koska Delphi käyttää
omaa visuaalista komponenttikirjastoaan. Lisäksi C++ sisältää
paljon sellaista, mitä ei löydy Object Pascalista, kuten metodien
kuormittaminen ja moniperintä. Suoran siirtämisen lisäksi
on kuitenkin olemassa muita menetelmiä, kuten esimerkiksi dynaamisten
linkkikirjastojen ja aktiivilomakkeiden hyödyntäminen, joita
kuvataan tarkemmin seuraavissa luvuissa.
3. Yhteiskäytön yksinkertaiset perusmenetelmät
Tässä luvussa esitellään tarkemmin yksinkertaisimmat menetelmät C++ Builderin ja Delphin yhteiskäyttöön. Luvuissa 3.1. ja 3.2. esitellään menetelmät Delphin lomakkeiden ja ohjelmayksiköiden siirtämiseen C++ Builderin projekteihin ja Delphi-komponentin asentamiseen C++ Builderiin. Kyseiset alaluvut perustuvat lähteeseen [Calvert]. Luvussa 3.3. kuvataan, miten leikekirjaa voi hyödyntää, kun C++ Builder ja Delphi ovat käytössä samassa koneessa. Luku 3.3. perustuu kirjoittajan omiin käytännön kokemuksiin.
3.1. Delphin ohjelmayksiköiden ja lomakkeiden lisääminen suoraan C++ Builderin projekteihin
Ohjelmayksiköiden (engl. Unit) ja lomakkeiden (engl. Form) suora lisääminen projekteihin toimii vain Delphistä C++ Builderiin. Tässä alaluvussa esitellään tapa toteuttaa lisääminen.
Menuvalikon projektikohdasta (Project) valitaan ohjelmayksikön tai lomakkeen lisääminen projektiin (Add to Project). Tiedoston tyypiksi valitaan Delphi-ohjelmayksikkö (pas-päätteiset tiedostot) ja selataan, kunnes haluttu tiedosto löytyy. Tiedosto lisätään normaaliin tapaan open-painikkeella . Pascal-ohjelmayksikkö ja -lomake ilmestyvät projektiin. C++ Builder luo automaattisesti C++-kielisen hpp-päätteisen otsikkotiedoston (engl. C++ headerfile). Otsikkotiedosto päivitetään projektin kääntämisen yhteydessä, jos Pascal-ohjelmayksikköön on tehty muutoksia ja aina uudelleenrakennettaessa projektia. Otsikkotiedostolla ohjelmayksikkö ja lomake esitellään niitä kutsuvalle ohjelmayksikölle #include-direktiivillä. Lomake ja ohjelmayksikkö pysyvät Object Pascal -kielisinä, mutta toimivat sovelluksessa aivan kuin olisivat C++ Builderin omia C++ -kielisiä lomakkeita ja ohjelmayksiköitä.
On mahdollista tehdä C++ Builderin projekti melkein kokonaan edellä kuvatulla tavalla. Tällöin C++ Builder -projektissa ei olisi mitään muuta C++ -kielistä kuin projektitiedosto, sillä kaikki lomakkeet olisi tehty Delphillä. Projekti määriteltäisiin silti teknisesti C++ Builder -projektiksi, koska itse projektitiedoston kieli on C++. Mainittakoon, että C++ Builderkin parsi kokeiltaessa Object Pascalia nopeammin kuin C++:aa, joten Delphi-lomakkeiden käyttäminen C++ Builderin lomakkeiden sijaan vähentää ainakin periaatteessa C++ Builderin projektin kääntämiseen käyttämää aikaa.
On huomattava se tärkeä seikka, että samaan tiedostoon ei saa sekoittaa C++:n ja Object Pascalin koodia. Erikielisille koodeille on aina oltava omat tiedostonsa. Lisäksi projektiin liitetään nimenomaan lähdekoodinen tiedosto, esikäännetty binääritiedosto ei kelpaa. Binääritiedoston hyödyntämistä yhteiskäytössä kuvataan luvussa 5.
3.2. Delphin komponentin asentaminen C++ Builderiin
Delphillä tehtyjen komponenttien lisääminen C++ Builderiin tapahtuu samalla tavalla kuin C++ Builderin omien komponenttien, tällöin kuitenkin valitaan ohjelmayksikkötiedoston tyypiksi Pascal-tiedosto (tiedostojen pääte pas). C++ Builder luo komponentille automaattisesti otsikkotiedoston (engl. C++ headerfile), jonka tiedostonimen pääte on hpp.
Lisäämisen jälkeen komponenttia voi käyttää aivan normaalisti kuten alkuperäistä C++ Builderin komponenttia. Sen voi siis ottaa komponenttivalikosta ja laittaa lomakkeelle sekä muutella arvoja olioselaimella (engl. object inspector). Komponentin metodien kutsumiseen koodista käsin käytetään C++ -kieltä. Jos kutsumisessa on ongelmia, otsikkotiedostossa on komponentin metodien esittely C++-kielellä, jota kannattaa tutkia.
3.3. Leikekirjan hyödyntäminen
Leikekirja tarjoaa joitain käteviä mahdollisuuksia, kun Delphi ja C++ Builder ovat käytössä samaan aikaan. Leikekirjan avulla voi kopioida tai leikata halutut lomakkeella olevat komponentit C++ Builderista Delphiin tai päinvastoin. On siis mahdollista kopioida vaikka koko lomakkeen sisältö "leikkaa ja liimaa"- menetelmällä. Komponenteille asetetut arvot kopioituvat myös. Tosin tämä kopioi vain lomakkeen komponentteineen, mutta ei ohjelmayksikön koodeja. Ohjelmakoodi joudutaan kääntämään erikseen. Komponenttien siirtäminen leikekirjan avulla toimii vain, jos toisessakin ohjelmassa on kyseinen komponentti asennettuna. Jos kyseessä on esimerkiksi itse tehty komponentti, se pitää asentaa ensin molempiin ohjelmointiympäristöihin.
Leikekirjan käyttökin toimii paremmin Delphistä C++ Builderiin
kuin päinvastoin, koska uusien komponenttien lisääminen
Delphistä C++ Builderiin on helpompaa kuin päinvastoin. Lisäksi
kaikki vakiokomponentitkaan eivät siirry C++ Builderista Delphiin
ongelmitta, ainakaan vielä kolmosversioissa. Esimerkiksi C++ Builderin
vierityspalkki (engl. scrollbar) sisältää
pagesize-nimisen
ominaisuuden, jota ei ole Delphin vierityspalkissa. Lisäksi C++ Builderin
lomakkeelta kopioidun vierityspalkin liimaaminen Delphin lomakkeelle ei
onnistunut kokeiltaessa.
Tässä luvussa kuvataan, miten ActiveX-komponentteja ja aktiivilomakkeita tehdään sekä miten niitä voidaan hyödyntää Delphin, C++ Builderin ja muidenkin ohjelmien yhteiskäytössä. Luku perustuu lähteeseen [Powell].
Delphin tai C++ Builderin komponentista voi tehdä ActiveX-velhon (engl. ActiveX-Wizard) avulla ActiveX-kontrollin. ActiveX-kontrollin voi tämän jälkeen rekisteröidä ja asentaa toiseen näistä ohjelmista tai muihinkin ympäristöihin, kuten mm. IntraBuilderiin, Paradox 8:een, Microsoftin Visual C++:aan, Visual Basiciin ja FrontPageen. C++ Builderissa ja Delphissä ActiveX-komponentti luodaan melkein samalla tavalla. ActiveX-komponentin tekeminen Delphissä ja C++ Builderissa onnistuu seuraavassa esitettävien ohjeiden avulla.
Ensin täytyy olla olemassa visuaalisen komponenttikirjaston kontrolli, joka halutaan muuttaa ActiveX-kontrolliksi. Sen on oltava Twincontrol-luokan jälkeläinen. Useimmissa tapauksissa myös Tgraphiccontrol-luokan jälkeläinen käy, kunhan sen esivanhemmaksi ensin muutetaan Tcustomcontrol-luokka komponentin lähdekooditiedostossa.
Seuraavaksi käynnistetään ActiveX-kontrollivelho valitsemalla ensin File-valikon komento New, sekä tällöin aukeavasta dialogista ActiveX-sivu ja sieltä ActiveX-Control-ikoni. Velhossa valitaan visuaalisen komponenttikirjaston komponentiksi se, josta halutaan tehdä ActiveX-kontrolli. Tämän jälkeen määrätään uuden ActiveX-kontrollin nimi, asetetaan lisensiointi ja versioinformaatio päälle tai pois päältä. Painettaessa OK-painiketta Delphi luo tyyppikirjaston, jossa on kaikki julkiset ja julkaistut ominaisuudet, metodit ja tapahtumat. Lisäksi Delphi luo kaksi lähdekooditiedostoa. Ensimmäinen lähdekooditiedosto on Delphin tyyppikirjastoinformaatiota (mm. rajapinnat ja välitetyt rajapinnat) varten. Toista lähdekooditiedostoa käytetään toteuttamaan ensimmäisessä tiedostossa määriteltyjen rajapintojen metodit.
Tyyppikirjastoa luodessaan Delphi ei aina pysty kääntämään aivan kaikkea. Tällöin joko jonkin asian kääntämisessä ei ole mieltä tai Delphi ei ymmärrä, miten kääntäminen pitäisi toteuttaa. Esimerkiksi toteutettavan komponentin aliominaisuudet (engl. sub-properties) eivät ehkä käänny automaattisesti. Lisäksi ohjelmoija voi muutenkin haluta lisätä omia metodeja, ominaisuuksia ja tapahtumia. Tämä tehdään avaamalla tyyppikirjasto (valitsemalla View-valikon komento Type Library) ja tekemällä siellä tarvittavat lisäykset. Lopuksi painetaan päivityksen (engl. refresh) painiketta, jolloin Delphi luo liittymäkoodin (engl. wrapper code) tehdyille lisäyksille
Get- ja Set-metodit ovat käsittelymetodeja, jotka sallivat kontrollien arvojen lukemisen ja muuttamisen. Seuraavana vaiheena on kirjoittaa niille koodi, koska Delphin automaattisesti luomat Get- ja Set- metodit ovat joko toiminnallisesti epätäydellisiä tai tyhjiä.
Seuraavassa on esimerkki Delphillä tehtyyn ActiveX-komponentiin TcircleX käsin kirjoitetuista Get- ja Set-metodeista, joilla luetaan ja asetetaan siveltimen (engl. brush) väri.
Kun ActiveX-kontrolli on valmis, halutaan todennäköisesti luoda yksi tai useampi ominaisuussivu kontrollin ominaisuuksien käsittelyä varten muissa ympäristöissä kuin C++ Builderissa ja Delphissä.// Palauttaa siveltimen värin.
function TCircleX.Get_BrushColor: Integer; safecall;
beginResult := FDelphiControl.Brush.Color;end;// Asettaa siveltimen värin.
procedure TCircleX.Set_BrushColor(Value: Integer); safecall;
beginFdelphiControl.Brush.Color := Value;end;
Lopuksi käännetään ja rekisteröidään kontrolli. Tämän jälkeen ActiveX-kontrollin voi tuoda mihin tahansa ympäristöön, jossa Delphin ActiveX-kontrolleja voi käyttää.
Aktiivilomake (engl. ActiveForm) on joukko visuaalisia ja muita komponentteja. Ne on liitetty yhteen lomakkeelle, jonka kanssa ne muodostavat eräänlaisen ActiveX-kontrollien yhteenliittymän. On mahdollista tehdä useista visuaalisista kontrolleista koostekontrolli (engl. compound control) tai tehdä kokonainen yhden lomakkeen sovellus, joka luovutetaan yhtenä aktiivilomakkeena.
On huomattava, että vain aktiivilomakkeeseen itseensä liitetyt ominaisuudet, metodit ja tapahtumat voivat tulla näkyviin. Lomakkeella olevan visuaalisen komponenttikirjaston komponenttien ominaisuuksia, metodeja tai tapahtumia ei siis näytetä. Ne ovat teknisesti aktiivilomakkeen sisäisiä asioita.
Aktiivilomake tarjoaa paljon mahdollisuuksia. On kohtuullisen helppoa toteuttaa aktiivilomakkeeksi esimerkiksi C++ Builderillä komponentteja ja ohjelmakoodia sisältävä lomake, joka voi olla vaikka kokonainen yhden lomakkeen sovellus. Aktiivilomaketta voi sitten käyttää kuten normaalia ActiveX-kontrollia. Sen voi tuoda Delphiin tai johonkin toiseen ActiveX-kontrolleja tukevaan ympäristöön ja kiinnittää tavallisen visuaalisen komponentin tapaan lomakkeelle. Kun ohjelma käännetään ja ajetaan, toimii aktiivilomake osana sovellusta. Tämä on yksinkertainen tapa C++ Builderin lomakkeen hyödyntämiseksi Delphissä. Samalla tavalla voidaan Delphin ja C++ Builderin lomakkeita hyödyntää Visual Basicissa, Visual C++:ssa tai muissa ympäristöissä, joihin voi tuoda ActiveX-kontrolleja. Heikkoutena on se, että lomakkeen komponentteja ei voi kutsua suoraan koodista, kuten Delphi-lomakkeen suorassa liittämisessä C++ Builderin projektiin, vaan sitä varten on kirjoitettava aktiivilomakkeelle käsittelyrutiinit. Aktiivilomakkeen luonti Delphissä ja C++ Builderissa tapahtuu seuraavassa esitetyllä tavalla.
Aluksi käynnistetään aktiivilomakevelho (engl. Activeform wizard) valitsemalla File-valikon komento New ja valitsemalla tällöin avautuvasta dialogista ActiveX-sivu ja ActiveForm-ikoni. Avautuvaan dialogiin annetaan lomakkeen nimi, toteutusyksikön nimi, projektin nimi sekä asetetaan lisensiointi ja versiokontrolli päälle tai pois. Kun painetaan OK-painiketta, Delphi luo tyyppikirjaston, jossa ovat kaikki TactiveForm-luokan julkiset ja julkaistut ominaisuudet, metodit ja tapahtumat. Lisäksi luodaan kaksi lähdekoodiyksikköä. Ensimmäinen niistä sisältää Delphin tyyppikirjastoinformaation (rajapinnat, välitetyt rajapinnat jne.). Toinen yksikkö toteuttaa ensimmäisessä määritellyt rajapintametodit.
Tämän jälkeen lisätään halutut visuaaliset ja ei-visuaaliset komponentit lomakkeelle. Säädetään komponenttien ja lomakkeen ominaisuudet ja kirjoitetaan niiden metodit normaaliin tapaan.
Aktiivilomakkeeseen lisätään haluttuja ominaisuuksia, tapahtumia ja metodeja avaamalla tyyppikirjasto (View-valikon komennolla Type Library) ja tekemällä tarvittavat lisäykset. Tämän jälkeen valitaan päivitä (Refresh), jolloin Delphi luo liittymäkoodin (engl. wrapper code) uusille lisäyksille.
Kun aktiivilomakkeen toiminnallisuus on valmis, luodaan yleensä ainakin yksi ominaisuussivu. Tällöin aktiivilomakkeen käyttäjällä on mahdollisuus muokata aktiivilomakkeen ominaisuuksia oikeanpuoleisen hiiren painikkeen avulla sellaisissa ympäristöissä, jotka eivät tue Delphin ja C++ Builderin olioeditorin (engl. object inspector) kaltaisia työkaluja ominaisuuksien muokkaamiseksi. Aktiivilomakkeella tulisi olla ainakin yksi yleisluontoinen ominaisuussivu. Delphi tarjoaa neljä standardiominaisuussivua väreille, fonteille, kuville ja Delphin merkkijonolistoille.
Lopuksi käännetään ja rekisteröidään aktiivilomake, jolloin syntyy ocx-tiedosto. Kyseisen tiedoston voi tämän jälkeen tuoda ympäristöön, jossa Delphin ja C++ Builderin ActiveX-kontrolleja voi käyttää.
Tässä luvussa kerrotaan, mikä objektitiedosto on ja miten niitä voidaan hyödyntää C++ Builderin ja Delphin yhteiskäytössä. Luku perustuu lähteeseen [Powell].
Delphi tukee C++:n obj-päätteisten objektitiedostojen käyttöä. Objektitiedostot generoidaan formaattiin, jota kutsutaan nimellä "Intel relocatable object file". C++ Builder ja Delphi käyttävät tätä formaattia objektitiedostoissaan. Delphi tosin käyttää kyseistä formaattia vain silloin, kun obj-tiedostojen generoinnin asetus on päällä. Muuten ja oletusarvoisesti Delphi generoi dcu-objektitiedostoja. Muista ohjelmointiympäristöistä esimerkiksi Visual C++ ei tunne obj-tiedostoja, vaan se käyttää coff-binääritiedostoja.
C++:n obj-tiedoston voi linkittää Delphin ohjelmatiedostoon käyttämällä käännösohjetta {$L MyFile}, jossa MyFile on obj-tiedoston nimi. Tiedoston pääte on oletusarvoisesti obj, ellei muuta erikseen kirjoiteta käännösohjeeseen. Linkittämisen aikana Delphin linkittäjä sitoo modulin staattisesti eli kiinteästi tuloksena syntyvään suoritettavaan moduuliin. Koska linkitys on staattinen, obj-tiedostosta tulee osa suoritettavaa moduulia. Tämä tietysti lisää moduulin kokonaiskokoa.
Seuraava esimerkkilistaus OBJESIM.CPP on osa yksinkertaisesta C++-ohjelmasta. Se sisältää globaalin kokonaislukumuuttujan ja funktiot, joista ensimmäinen palauttaa ja jälkimmäinen asettaa muuttujan arvon. Koko esimerkkiohjelma löytyy alihakemistosta OBJESIM.
Seuraava esimerkkilistaus OBJESIM.PAS on osa Delphin ohjelmayksikköä ja se käyttää edellisestä listauksesta rakennettua objektitiedostoa.int MyGlobal = 0;int __stdcall GetValue()
{ return MyGlobal; }int __stdcall SetValue(int value)
{ MyGlobal = value; }
Edellä esitellyn menetelmän mahdollisuudet ovat melko rajoittuneet. Binääritiedostossa sijaitsevat globaalit muuttujat eivät toimi suoraan. Jos binääritiedoston rutiini käyttää toisessa moduulissa olevia rutiineja, Delphin linkittäjä yksinkertaisesti epäonnistuu. Linkitetty C++-kielinen obj-tiedosto ei saa myöskään sisältää C++-luokkien esittelyjä, koska ne perustuvat ulkoisiin moduuleihin....
implementation
{$R *.DFM}
{$L OBJESIM.CPP} // Tällä direktiivillä linkitetään // OBJESIM.CPP tähän ohjelmayksikköön.
function GetValue: integer; stdcall; external;
function SetValue(value: integer): stdcall; external;
Niistä Delphin linkittäjä ei tiedä mitään.
Näitä ongelmia voi jonkin verran kiertää, kuten edellisissä esimerkkilistauksissa on tehty. Delphin linkittäjä ei osaa käsitellä globaalisia muuttujia suoraan C++:n objektitiedostoissa. Niitä voi silti käyttää kirjoittamalla objektitiedostoon funktioita varta vasten asettamaan ja palauttamaan globaalien muuttujien arvoja Delphistä käsin.
Menetelmä toimii hieman paremmin toiseen suuntaan. C++ Builder ymmärtää Delphin globaaleja muuttujia. Toisaalta mahdollisuus lisätä Delphin lomakkeita ja ohjelmayksiköitä suoraan C++ Builderin projekteihin sekä objektitiedostojen käytön suuret rajoitukset tuntuvat tekevän tämän tavan hyödyntää objektitiedostoja melko turhaksi. Tosin tällä menetelmällä binäärimuotoista Delphin koodia saa linkitettyä samalle lomakkeelle C++-koodin kanssa. Menetelmä saattaa olla hyödyllisempi muiden C++-ohjelmointityökalujen kuin C++ Builderin objektitiedostojen linkittämisessä Delphin koodiin.
Joka tapauksessa Delphin saa muodostamaan obj-tiedostoja valitsemalla projektiasetusten dialogista Linker-sivulta valintapainike Generate Object Files. Dialogi avataan valitsemalla Delphin Project-valikosta Options-komento.
Pascalin obj-tiedosto esittelee esimerkiksi seuraavan globaalisen muuttujan:
Tällöin muuttujaa voidaan käsitellä C++-koodissa suoraan, kunhan ensin esitellään muuttuja uudelleen extern-direktiivin kanssa muodossavar MyGlobalInt : integer;
extern int MyGlobalInt;
Dynaamiset linkkikirjastot (engl. dynamic link libraries, DLL) tarjoavat yksinkertaisen ja loogisen tavan yhdistää C++ Builderin ja Delphin lähdekoodeja. DLL:ien käytön osaaminen mahdollistaa vakaiden, vakiomuotoisten, uudelleenkäytettävien ja kielestä riippumattomien kirjastojen luomisen. Tässä luvussa kerrotaan miten DLL:ää voidaan käyttää C++ Builderin ja Delphin yhteiskäytössä. Luku perustuu lähteeseen [Powell].
6.1. Dynaamisten linkkikirjastojen perusteet
DLL voidaan ladata ohjelmatiedostosta tai toisesta DLL:stä joko implisiittisesti tai eksplisiittisesti. Implisiittinen menetelmä lataa kerralla koko DLL:n tuovan (engl. import) ohjelman osaksi. Menetelmä käyttää erityistä DLL-tiedoston esittelyä. Se vaatii vähemmän koodaamista, mutta ei mahdollista joustavaa dynaamista muistinkäyttöä, jossa vain kulloinkin tarvittavat DLL:n osat ovat muistissa. Eksplisiittinen menetelmä lataa tarvittavat osat yksitellen tarpeen mukaan muistiin käyttämällä rutiineja LoadLibrary tai LoadLibraryEx ja GetProcAddress.
Delphissä ja C++ Builderissa DLL-kirjasto luodaan seuraavassa esitetyllä tavalla.Valitaan File-valikosta New-komento ja esiin tulevasta New Items-dialogista DLL-ikoni, jolloin ohjelma luo uuden DLL-projektin. Mitään oletuslomaketta tai lisäyksiköitä ei tarjota, sillä uudessa projektissa on pelkkä projektitiedosto. DLL-projektiin voi sitten lisätä lomakkeita ja koodia aivan kuten tavalliseen C++ Builder tai Delphi-projektiin.
Delphin ja C++ Builderin objekteja voi käyttää DLL:ien avulla ainakin hyödyntämällä käsittelyrutiineja, jotka luovat liittymän viedyn (engl. exported) luokan funktioihin, tietorakenteisiin ja luokkiin tai suorittavat itse jotain hyödyllistä. Toinen käytännöllinen tapa on luokkien virtuaalitaulujen kartoittaminen.
6.2. Käsittelyrutiinit DLL:ssä
Käsittelyrutiini tai liittymäfunktio (engl. flat function or access routine) on DLL:n viemä rutiini.
Käsittelyrutiinit yleensä joko sisältävät suoraan hyödyllistä prosessointikoodia tai kutsuvat toisia funktioita, jotka sisältävät hyödyllistä prosessointikoodia. Tällaisia käsittelyrutiineja kutsutaan kirjastorutiineiksi. Käsittelyrutiineja voidaan myös käyttää liittymänä johonkin DLL:ssä määriteltyyn luokkaan.
Seuraavassa listauksessa DLLESIM.CPP on yhden vietävän (engl. export) funktion sisältävä esimerkki yksinkertaisesta C++ Builderin DLL-tiedoston lähdekoodista. Koko esimerkkiohjelma löytyy alihakemistosta YNNA.
Direktiivillä extern "C" kerrotaan C++ Builderille, että metodin nimeä ei viedä sekoitettuna (engl. mangled). Oletusarvoisesti C++-funktion nimi viedään sekoitettuna. Tämä tarkoittaa, että parametriluettelon tyypit muunnetaan erityisiksi symbolimerkeiksi, jotka lisätään funktion nimen loppuun. Tämä tehdään sitä varten, että C++ voisi tukea funktion kuormittamista (engl. function overloading). Tätä piirrettä ei ole Pascalissa. Delphi-listaus ROUTINEYNNALOADER.PAS edellisen listauksen Ynna-funktion tuomiseen (engl. import) Delphissä näyttää seuraavalta:#include <vcl.h>
#pragma hdrstop
extern "C" int _stdcall _export Ynna(int i1, int i2)
{ return (i1 + i2); }//----------------------------------------------------
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*)
{ return 1; }
//----------------------------------------------------
Edellisen listauksen Ynna-funktio on niin sanottu vientifunktio eli funktio, joka viedään DLL:ää käyttävään ohjelmaan tai toiseen DLL:ään. Kutsutapana on kaikissa tämän tutkielman esimerkeissä stdcall-kutsutapa, jonka käyttäminen on yhteensopivuuden vuoksi suositeltavaa.unit RoutineYnnaLoader;interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
type
Tform1 = class(TForm)
Button1: TButton;procedure Button1Click(Sender: TObject);
Label1: TLabel;end;
var
Form1: TForm1;external 'DLLESIM.DLL';
function Ynna(i1 : integer; i2 : integer):
integer; stdcall;implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
begin
label1.Caption := '2 + 2 = ' +end;
IntToStr(Ynna(2,2));
end.
Edellisessä esimerkissä vientifunktio esiteltiin C++ Builderissa. Vientifunktion esittelemiseksi Delphissä pitää huolehtia seuraavista kolmesta asiasta:
1. Funktio esitellään ohjelmayksikön interface-osassa.
2. Funktio luetellaan ohjelmayksikön exports-osassa.
3. Funktion lauseosa määritellään ohjelmayksikön implementation-osassa.
6.3. Luokkien käsittelyrutiinit
DDL:ssä sijaitsevia luokkia voi käyttää samaan tapaan kuin edellä käytettiin käsittelyrutiinien avulla globaaleja muuttujia ja kirjastofunktioita. Seuraava esimerkkilistaus YMPYRA.CPP on C++ Builderillä kirjoitettu cYmpyra-luokka, joka kääntyy DLL-tiedostoksi. Koko esimerkkiohjelma löytyy alihakemistosta Koko esimerkkiohjelma löytyy alihakemistosta YMPYRA.
Edellisessä esimerkissä on ensin luokan esittely ja sen jälkeen muutamia käsittelyrutiineja, joiden avulla luokan metodeja voidaan kutsua DLL-tiedoston ulkopuolelta käsin. Ensimmäinen käsittelyrutiini LoadCircle on erittäin tärkeä, sillä se luo luokan esiintymän ja palauttaa kahvan esiintymään. Esiintymää käytetään sitten kahvan avulla. Viimeisenä on C++ Builderin automaattisesti luoma DllEntryPoint-metodi.#include <vcl.h>
#pragma hdrstopclass cYmpyra
{private:};int x,y;public:
double r;cYmpyra() {AsetaXY(100,100); AsetaSade(10);}
void AsetaXY(int ix,int iy){x = ix; y = iy;}
AsetaSade(double ir) { r = ir; }
int AnnaX() { return x; }
int AnnaY() { return y; }
int AnnaSade() { return r; }
double LaskeAla() {return(2*3.1415 * r*r); }extern "C"
{
// Palauttaa kahvan cYmpyra-luokan esiintymaan}HANDLE _stdcall _export LoadCircle()
{ cYmpyra* Ympyra = new cYmpyra(); return (HANDLE)Ympyra;}// Vapauttaa cYmpyra-luokan esiintymän
void _stdcall _export FreeCircle (cYmpyra*Ympyra){ delete Ympyra; }
int _stdcall _export GetX( cYmpyra* Ympyra )
{ return Ympyra->AnnaX(); }
double _stdcall _export GetA( cYmpyra* Ympyra )
{ return Ympyra->LaskeAla(); }
void _stdcall _export SetR( cYmpyra* Ympyra, double ir )
{ Ympyra->AsetaSade(ir); }
//-----------------------------------------------------int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned longreason, void*)
{return 1;}
Seuraava listaus YMPYRA.PAS on osa Delphin ohjelmaa, joka käyttää edellisen esimerkin DLL-tiedostoa:
Button1-niminen painonappi on testausta varten. Sitä painettaessa luodaan ensin cYmpyra-luokan esiintymä kutsumalla käsittelyrutiinia nimeltä LoadCircle ja tallennetaan luodun esiintymän (cYmpyra-olion) kahva hInst-muuttujaan. Tämän jälkeen kahvaa käyttäen kokeillaan SetR- ja GetA-käsittelyrutiineja, joiden avulla käytetään cYmpyra-luokan AsetaSade- ja LaskeAla-metodeja. Lopuksi vapautetaan luokan esiintymä.YMPYRA.PAS
...var
Form1: Tform1;
const // DLL-tiedoston hakemistopolku:
CYMPYRA = 'YMPYRA.DLL';// DLL:stä tuotuja (imported) käsittelyrutiineja:
function LoadCircle: Thandle; stdcall; external CYMPYRA;
function GetX(hCircle: THANDLE): integer; stdcall;
external CYMPYRA;
function GetA(hCircle: THANDLE): double; stdcall;
external CYMPYRA;
procedure SetR(hCircle: THANDLE; newR : double); stdcall; external CYMPYRA;
procedure FreeCircle(hCircle: THANDLE); stdcall; external CYMPYRA;
implementation{$R *.DFM}
// Testausaliohjelma:
procedure TForm1.Button1Click(Sender: Tobject);
var hInst: THandle;
i : double;begin
hInst := LoadCircle;end;
SetR(hInst,20);
i := GetA(hInst);
label1.Caption := IntToStr(Trunc(i));
FreeCircle(hInst);
6.4. Virtuaalisen metoditaulun hyödyntäminen
Delphin ja C++ Builderin luokkien rakenteeltaan samanlaiset virtuaaliset metoditaulut (VMT) tarjoavat suoremman lähestymistavan DDL:ssä esiteltyjen luokkien käyttöön ohjelmakoodista kuin edellä esitellyt luokkien käsittelyrutiinit.
C++ Builderin ja Delphin oliot ovat sisäisesti tietorakenteita, joilla on tunnistettava muoto. Niiden sisäinen muoto on yhteensopiva Microsoftin COM-spesifikaatiossa (engl. Component Object Model, komponenttioliomalli) määritellyn olioformaatin kanssa. Käyttämällä tätä olioformaattia C++ Builder ja Delphi-oliot voivat tukea suoraan COM-objektien kanssa yhteensopivia rajapintoja (liittymiä).
COM-yhteensopiva osa C++ Builderin ja Delphin olioformaateissa on melko yksinkertainen. Luokan rakentajan (engl. constructor) kutsulla saatava luokan ilmentymä on pelkkä osoitin muistilohkoon. Muistilohkon ensimmäinen kenttä sisältää virtuaalisen metoditaulun osoittimen osoitteen. Seuraava kenttä muistilohkossa on kantaluokassa esitellyn ensimmäisen tietokentän osoite. Viimeinen kenttä on sen luokan viimeiseksi esitelty tietokenttä, jonka ilmentymä luokan olio on. Toisin sanoen luokan tietokenttien osoitteet näkyvät ilmentymän muistilohkossa kenttinä, luokan esittelyjärjestyksen ja koko luokkahierarkian mukaan järjestettynä.
Virtuaalisesta metoditaulusta käytetään eri yhteyksissä erilaisia nimityksiä, joita ovat Virtual Method Table (VMT), Virtual Table (VT) ja Vtable (VT). Niillä tarkoitetaan kuitenkin yleensä samaa asiaa.
Virtuaalisen metoditaulun osoitin osoittaa luokan ensimmäiseen
virtuaaliseksi määriteltyyn metodiin. Vähentämällä
tästä osoitteesta neljä tavua saadaan luokan hävittäjän
(engl.
destructor) osoite ja lisäämällä neljä tavua
luokan toisen virtuaalisen metodin osoite (katso taulukko 1).
VMT-vienti Siirtymä VMT:n osoitteesta tavuina vmtNewInstance -12 vmtFreeInstance, -8 -8 vmtDestroy, -4 -4 Metodi 1 0 Metodi 2 4 Metodi 3 8
VMT:n avulla on mahdollista päästä käsiksi tietyn luokan ilmentymän metodeihin DLL:ssä. Tätä rajoittavat kuitenkin seuraavat viisi seikkaa:Taulukko 1. Delphin virtuaalisen metoditaulun vientejä.
Edellisessä listauksessa GetCYmpyra on ainoa extern "C" -direktiivillä DDL-tiedostosta ulos viety rutiini. Se palauttaa luokan uuden ilmentymän. Ilmentymän tuhoamista varten luokan esittelyyn on lisätty metodi, joka kutsuu luokan tuhoajafunktiota delete-käskyllä.#include <vcl.h>
#pragma hdrstopclass cYmpyra
{private:};int x,y;cYmpyra() { AsetaXY(100,100); AsetaSade(10); }
double r;
public:
virtual void _stdcall DeleteYmpyra(){ ::delete this; }
virtual void _stdcall AsetaXY(int ix,int iy){ x = ix; y = iy; }
virtual _stdcall AsetaSade(double ir) { r = ir; }
virtual _stdcall int AnnaX() { return x; }
virtual _stdcall int AnnaY() { return y; }
virtual _stdcall int AnnaSade() { return r; }
virtual _stdcall double LaskeAla(){ return (2 * 3.1415 * r*r );extern "C" cYmpyra* _stdcall _export GetCYmpyra(){ return new cYmpyra; }
//------------------------------------------------
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned longreason, void*)
{ return 1; }
//-----------------------------------------------------
Seuraava listaus YMPYRA2.PAS on tarkoitettu edellisen DLL-luokan käsittelyyn:
Listauksen alussa luodaan DLL-tiedostossa sijaitsevan luokan käsittelyä varten luokka, jonka kaikki metodit ovat abstrakteja. Metodien nimet ovat tässä esimerkissä samat kuin DLL-tiedoston luokan metodeilla. Ne voisivat olla erilaisiakin, sillä tärkeintä on metodien esittelyjen identtinen järjestys. On tärkeää huomata, että molemmissa listauksissa kaikki tuotavaksi halutut metodit ovat virtuaalisia. Ilman tätä ei olisi VMT:tä, jota hyödyntää....cYmpyra = class
publicprocedure DeleteYmpyra stdcall; virtual; abstract;end;
procedure AsetaXY(ix: integer; iy: integer)
stdcall; virtual; abstract;
procedure AsetaSade(ir: double) stdcall; virtual; abstract;
function AnnaX: integer stdcall; virtual; abstract;
function AnnaY: integer stdcall; virtual; abstract;
function AnnaSade: integer stdcall; virtual; abstract;
function LaskeAla: double stdcall; virtual; abstract;var
Form1: TForm1;function GetCYmpyra: cYmpyra; stdcall; external 'YMPYRA2.DLL';implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: Tobject);
beginwith GetCYmpyra doend;tryend;
AsetaSade(20.1);
AsetaXY(50,50);
label1.Caption := IntToStr(Trunc(LaskeAla));
finally
DeleteYmpyra;
Delphillä tehdyn työn kuten koodin, lomakkeiden ja komponenttien hyödyntäminen C++ Builderissa on hyvin helppoa ja onnistuu melkein aina. Delphin lomakkeet ja ohjelmayksiköt voi lisätä suoraan C++ Builderin projekteihin ja Delphin komponentit lisätään samalla tavalla kuin C++ Builderin omat komponentit.
C++ Builderillä tehdyn koodin, lomakkeiden ja komponenttien hyödyntäminen Delphissä vaatii hieman perusteellisempaa perehtymistä eri menetelmiin, mutta se kuitenkin onnistuu yleensä. Tekemällä C++ Builderin komponentista ActiveX-komponentin, sen voi asentaa Delphin lisäksi muihinkin ActiveX-yhteensopiviin ohjelmistoihin. Aktiivilomakkeet ovat melko kätevä tapa C++ Builderin lomakkeiden, erityisesti paljon käytettävien yhden lomakkeen sovellusten, liittämiseen Delphillä ja muilla ActiveX-yhteensopivilla ohjelmointiympäristöillä tehtyihin ohjelmiin. DLL-tiedostojen avulla C++ Builderin ja Delphin olioita, aliohjelmia ja muuta koodia saa käytettyä melkein millä tahansa ohjelmointityökalulla tehdyissä ohjelmissa tai DLL-tiedostoissa. Virtuaalinen metoditaulu tarjoaa erityisen kätevän menetelmän olioiden jakoon C++ Builderin ja Delphin välillä DLL-tiedostojen avulla. Objektitiedostojen linkittäminen lähdekoodiin on aika rajoittunut menetelmä koodin jakamiseen, joten siitä saattaa olla ehkä enemmän hyötyä jonkin muun C++-kääntäjän ja Delphin yhteiskäytössä.
Yhteiskäytön menetelmien ehkä suurin etu ovat mahdollisuudet
olemassaolevan koodin uudelleenkäyttämiseen. Ohjelmien erilaisia
ominaisuuksia voi hyödyntää varsinkin isommissa projekteissa
tarvitsematta liikaa huolehtia yhteensopivuusongelmista. Jotkut tässä
tutkielmassa käsitellyt menetelmät kuten ActiveX-teknologia ja
DLL-tiedostot mahdollistavat yhteiskäytön monien muidenkin ohjelmointiympäristöjen
kanssa.
Miller Todd ja Powell David, "Delphi 3 Tehokäyttäjän opas", Suomen Atk-kustannus, 1997.
Calvert Charlie,"Application
Development with C++ Builder and Delphi",
1996, http://www.borland.com/bcppbuilder/papers/delwp/, WWW-julkaisu.