Ohjelmointi

Java-vinkki 17: Java-integrointi C ++: n kanssa

Tässä artikkelissa käsittelen joitain kysymyksiä, jotka liittyvät C ++ -koodin integroimiseen Java-sovellukseen. Sanan jälkeen miksi haluaisimme tehdä tämän ja mitkä ovat jotkut esteet, rakennan toimivan Java-ohjelman, joka käyttää C ++: lla kirjoitettuja objekteja. Matkan varrella keskustelen joistakin tämän tekemisen seurauksista (kuten vuorovaikutus roskien keräyksen kanssa), ja esitän välähdyksen siitä, mitä voimme odottaa tällä alueella tulevaisuudessa.

Miksi integroida C ++ ja Java?

Miksi haluaisit integroida C ++ -koodin ensin Java-ohjelmaan? Loppujen lopuksi Java-kieli luotiin osittain korjaamaan joitain C ++: n puutteita. Itse asiassa on useita syitä, miksi haluat ehkä integroida C ++ Java: iin:

  • Esitys. Vaikka kehitätkin käyttöympäristöä, jossa on just-in-time (JIT) -kääntäjä, on todennäköistä, että JIT: n ajon aikana luotu koodi on huomattavasti hitaampi kuin vastaava C ++ -koodi. Kun JIT-tekniikka paranee, tämän pitäisi olla vähemmän tekijä. (Itse asiassa lähitulevaisuudessa hyvä JIT-tekniikka voi hyvinkin tarkoittaa sitä, että Java toimii nopeammin kuin vastaava C ++ -koodi.)
  • Vanhan koodin uudelleenkäyttö ja integrointi vanhoihin järjestelmiin.
  • Voit käyttää laitteistoa suoraan tai tehdä muita matalan tason toimintoja.
  • Hyödynnetään työkaluja, joita ei vielä ole saatavana Java-käyttöjärjestelmälle (aikuiset OODBMS-tiedostot, ANTLR ja niin edelleen).

Jos otat askeleen ja päätät integroida Java ja C ++, luoput joistakin vain Java-sovelluksen tärkeistä eduista. Tässä on haittoja:

  • Sekoitettua C ++ / Java -sovellusta ei voi suorittaa sovelmana.
  • Luovutat osoittimen turvallisuudesta. C ++ -koodisi voi vapaasti lähettää objekteja väärin, käyttää poistettua objektia tai vioittaa muistia millä tahansa muulla tavalla, joka on C ++: ssa niin helppoa.
  • Koodisi ei ehkä ole kannettava.
  • Rakennettu ympäristösi ei todellakaan ole kannettava - sinun on selvitettävä, kuinka C ++ -koodi laitetaan jaettuun kirjastoon kaikilla kiinnostavilla alustoilla.
  • API: t C: n ja Java: n integroimiseksi ovat käynnissä ja todennäköisesti muuttuvat siirtyessä JDK 1.0.2: sta JDK 1.1: een.

Kuten näette, Java: n ja C ++: n integrointi ei ole heikkohermoisille! Jos kuitenkin haluat jatkaa, lue lisää.

Aloitamme yksinkertaisella esimerkillä, joka osoittaa, kuinka C ++ -menetelmiä voidaan kutsua Java-sovelluksesta. Laajennamme sitten tätä esimerkkiä osoittamaan, kuinka tuetaan tarkkailijan mallia. Tarkkailumalli on yksi objektikeskeisen ohjelmoinnin kulmakivistä, mutta se on myös hieno esimerkki C ++ - ja Java-koodien integroinnin osallistuneimmista näkökohdista. Rakennamme sitten pienen ohjelman testaamaan Java-kääritty C ++ -objektimme ja lopetamme keskustelun Java-ohjelman tulevista ohjeista.

Soittaminen C ++: lle Java-sovelluksesta

Mikä on niin vaikeaa Java: n ja C ++: n integroinnissa, kysyt? Loppujen lopuksi SunSoftin Java-opetusohjelma on osa "Natiivien menetelmien integrointi Java-ohjelmiin" (katso Resurssit). Kuten näemme, tämä riittää C ++ -menetelmien kutsumiseen Java-sovelluksesta, mutta se ei anna meille tarpeeksi kutsua Java-menetelmiä C ++: sta. Tätä varten meidän on tehtävä vähän enemmän työtä.

Otetaan esimerkkinä yksinkertainen C ++ -luokka, jota haluaisimme käyttää Javan sisällä. Oletetaan, että tämä luokka on jo olemassa ja että emme saa muuttaa sitä. Tätä luokkaa kutsutaan nimellä "C ++ :: NumberList" (selkeyden vuoksi liitän kaikki C ++ -luokkien nimet eteen "C ++ ::"). Tämä luokka toteuttaa yksinkertaisen numeroluettelon, jossa on tapoja lisätä numero luetteloon, kysellä luettelon kokoa ja saada elementti luettelosta. Teemme Java-luokan, jonka tehtävänä on edustaa C ++ -luokkaa. Tällä Java-luokassa, jota kutsumme NumberListProxy, on samat kolme menetelmää, mutta näiden menetelmien toteutus on kutsua C ++ -ekvivalentit. Tämä on esitetty seuraavassa objektimallintotekniikan (OMT) kaaviossa:

NumberListProxy-Java-ilmentymän on pidettävä kiinni viitteestä vastaavaan NumberListin C ++ -esiintymään. Tämä on tarpeeksi helppoa, joskin vähän kannettavaa: Jos olemme alustalla, jossa on 32-bittisiä osoittimia, voimme yksinkertaisesti tallentaa tämän osoittimen int; Jos olemme alustalla, joka käyttää 64-bittisiä osoittimia (tai uskomme olevan lähitulevaisuudessa), voimme tallentaa sen pitkään. NumberListProxy todellinen koodi on yksinkertainen, joskin hieman sotkuinen. Se käyttää SunSoftin Java-opetusohjelman "Natiivien menetelmien integrointi Java-ohjelmiin" -osion mekanismeja.

Ensimmäinen leikkaus Java-luokassa näyttää tältä:

 public class NumberListProxy {static {System.loadLibrary ("NumberList"); } NumberListProxy () {initCppSide (); } public native void addNumber (int n); julkinen kotimainen int-koko (); julkinen syntyperäinen int getNumber (int i); yksityinen syntyperäinen void initCppSide (); yksityinen int numberListPtr_; // Numeroluettelo *} 

Staattinen osa suoritetaan, kun luokka ladataan. System.loadLibrary () lataa nimetyn jaetun kirjaston, joka tapauksessamme sisältää käännetyn version C ++ :: NumberLististä. Solaris-käyttöjärjestelmässä se odottaa löytävänsä jaetun kirjaston "libNumberList.so" jonnekin $ LD_LIBRARY_PATH -kirjastosta. Jaettujen kirjastojen nimeämiskäytännöt voivat poiketa muista käyttöjärjestelmistä.

Suurin osa tämän luokan menetelmistä julistetaan "alkuperäisiksi". Tämä tarkoittaa, että tarjoamme C-toiminnon niiden toteuttamiseksi. C-funktioiden kirjoittamiseksi suoritetaan javah kahdesti, ensin nimellä "javah NumberListProxy", sitten nimellä "javah -stubs NumberListProxy". Tämä luo automaattisesti jonkin Java-ajon aikana tarvittavan "liimakoodin" (jonka se asettaa NumberListProxy.c-tiedostoon) ja tuottaa ilmoituksia C-funktioille, jotka meidän on tarkoitus toteuttaa (in NumberListProxy.h).

Päätin toteuttaa nämä toiminnot tiedostossa nimeltä NumberListProxyImpl.cc. Se alkaa joillakin tyypillisillä # sisällytä direktiiveillä:

 // // NumberListProxyImpl.cc // // // Tämä tiedosto sisältää C ++ -koodin, joka toteuttaa "javah -stubs NumberListProxy": n luoma // -tukit. vrt. NumberListProxy.c. #include #include "NumberListProxy.h" #include "NumberList.h" 

on osa JDK: ta ja sisältää useita tärkeitä järjestelmäilmoituksia. NumberListProxy.h loi meille javah, ja se sisältää ilmoitukset C-funktioista, joita aiomme kirjoittaa. NumberList.h sisältää C ++ -luokan ilmoituksen NumberList.

Kutsumme NumberListProxy-konstruktorissa natiivia menetelmää initCppSide (). Tämän menetelmän on löydettävä tai luotava C ++ -objekti, jota haluamme edustaa. Tätä artikkelia varten aion vain kasata uuden C ++ -objektin, vaikka yleensä haluaisimme sen sijaan linkittää välityspalvelimemme muualle luotuun C ++ -objektiin. Natiivimenetelmän käyttöönotto näyttää tältä:

 void NumberListProxy_initCppSide (struct HNumberListProxy * javaObj) {NumberList * list = uusi NumberList (); käsittelemätön (javaObj) -> numberListPtr_ = (pitkä) lista; } 

Kuten on kuvattu Java-opetusohjelma, me välitämme "kahvan" Java NumberListProxy -objektille. Menetelmämme luo uuden C ++ -objektin ja liittää sen sitten Java-objektin dataListPtr_ datajäseneen.

Nyt mielenkiintoisista menetelmistä. Nämä menetelmät palauttavat osoittimen C ++ -objektiin (dataListaPtr_-datajäsenestä) ja kutsuvat sitten halutun C ++ -funktion:

 void NumberListProxy_addNumber (struct HNumberListProxy * javaObj, pitkä v) {NumberList * list = (NumberList *) käsittelemätön (javaObj) -> numberListPtr_; list-> addNumber (v); } pitkä NumberListProxy_size (struct HNumberListProxy * javaObj) {NumberList * list = (NumberList *) käsittelemätön (javaObj) -> numberListPtr_; paluulista-> koko (); } pitkä NumberListProxy_getNumber (struct HNumberListProxy * javaObj, pitkä i) {NumberList * list = (NumberList *) käsittelemätön (javaObj) -> numberListPtr_; paluulista-> getNumber (i); } 

Funktion nimet (NumberListProxy_addNumber ja loput) määritetään meille javahin avulla. Lisätietoja funktiolle lähetetyistä argumenttityypeistä, käsittelemätön () makro ja muut Java-tuen alkuperäiset C-toiminnot -ominaisuudet, katso Java-opetusohjelma.

Vaikka tämä "liima" onkin hieman tylsää kirjoittaa, se on melko yksinkertainen ja toimii hyvin. Mutta mitä tapahtuu, kun haluamme soittaa Javalle C ++: sta?

Java-soittaminen C ++: sta

Ennen syventymistä Miten Jos haluat kutsua Java-menetelmiä C ++: sta, selitän miksi tämä voi olla tarpeen. Aiemmassa kaaviossa en esittänyt koko C ++ -luokan tarinaa. Täydellisempi kuva C ++ -luokasta on esitetty alla:

Kuten näette, käsittelemme havaittavaa numerolistaa. Tätä numeroluetteloa voidaan muokata monista paikoista (NumberListProxy tai mistä tahansa C ++ -objektista, joka viittaa C ++ :: NumberList -objektiimme). NumberListProxy on tarkoitus edustaa uskollisesti kaikki C ++: n käyttäytymisestä :: NumberList; Tähän tulisi kuulua Java-tarkkailijoiden ilmoittaminen numeroluettelon muuttuessa. Toisin sanoen NumberListProxy on oltava java.util.Observable -alaluokka, kuten tässä kuvassa:

On tarpeeksi helppoa tehdä NumberListProxy-tiedostosta java.util.Ob-luokan alaluokka, mutta miten siitä ilmoitetaan? Kuka kutsuu setChanged (): tä ja ilmoittaisiObservers (): lle, kun C ++ :: NumberList muuttuu? Tätä varten tarvitsemme auttajaluokan C ++ -puolella. Onneksi tämä yksi apuluokka toimii minkä tahansa Java-havaittavan kanssa. Tämän auttajaluokan on oltava C ++ :: Observer -alaluokka, jotta se voi rekisteröityä C ++ :: NumberList -palveluun. Kun numeroluettelo muuttuu, kutsutaan auttajaluokan update () -menetelmää. Update () -menetelmämme toteutus on kutsua setChanged () ja notificationObservers () Java-välityspalvelinobjektissa. Tämä on kuvattuna OMT: ssä:

Ennen kuin ryhdyn C ++ :: JavaObservableProxy -sovelluksen käyttöönottoon, haluan mainita muutamia muita muutoksia.

NumberListProxylla on uusi tietojäsen: javaProxyPtr_. Tämä on osoitin C ++ JavaObservableProxy -esiintymälle. Tarvitsemme tätä myöhemmin, kun keskustelemme esineiden tuhoamisesta. Ainoa muu muutos olemassa olevaan koodiin on muutos C-funktiollemme NumberListProxy_initCppSide (). Se näyttää nyt tältä:

 void NumberListProxy_initCppSide (struct HNumberListProxy * javaObj) {NumberList * list = uusi NumberList (); struct HObservable * havaittavissa = (struct HObservable *) javaObj; JavaObservableProxy * proxy = uusi JavaObservableProxy (havaittavissa, luettelo); käsittelemätön (javaObj) -> numberListPtr_ = (pitkä) lista; käsittelemätön (javaObj) -> javaProxyPtr_ = (pitkä) välityspalvelin; } 

Huomaa, että heitimme javaObj: n osoittimeen HObservable-palveluun. Tämä on OK, koska tiedämme, että NumberListProxy on havaittavissa olevan alaluokka. Ainoa muu muutos on, että luomme nyt C ++ :: JavaObservableProxy -esiintymän ja ylläpidämme siihen viittausta. C ++ :: JavaObservableProxy kirjoitetaan niin, että se ilmoittaa jokaiselle Java Observable -palvelulle havaitessaan päivityksen, minkä vuoksi meidän piti heittää HNumberListProxy * HObservable * -palveluun.

Tähänastisen taustan perusteella saattaa tuntua siltä, ​​että meidän on vain toteutettava C ++ :: JavaObservableProxy: update () siten, että se ilmoittaa havaittavasta Java: sta. Tämä ratkaisu näyttää käsitteellisesti yksinkertaiselta, mutta siinä on törmäys: Kuinka pidämme kiinni viitteestä Java-objektiin C ++ -objektin sisällä?

Java-viitteen ylläpitäminen C ++ -objektissa

Saattaa tuntua siltä, ​​että voimme yksinkertaisesti tallentaa kahvan Java-objektiin C ++ -objektiin. Jos näin olisi, voimme koodata C ++ :: JavaObservableProxy näin:

 luokka JavaObservableProxy public Observer {public: JavaObservableProxy (struct HObservable * javaObj, Observable * obs) {javaObj_ = javaObj; havaittuYksi = obs; observOne _-> addObserver (tämä); } ~ JavaObservableProxy () {observOne _-> deleteObserver (tämä); } void update () {suorita_java_dynamic_method (0, javaObj_, "setChanged", "() V"); } yksityinen: struct HObservable * javaObj_; Havaittavissa * havaittuYksi_; }; 

Valitettavasti ongelman ratkaisu ei ole niin yksinkertainen. Kun Java välittää sinulle kahvan Java-objektille, kahva] pysyy voimassa puhelun ajaksi. Se ei välttämättä ole voimassa, jos tallennat sen kasaan ja yrität käyttää sitä myöhemmin. Miksi näin on? Javan roskakorin takia.

Ensinnäkin yritämme ylläpitää viittausta Java-objektiin, mutta mistä Java-ajoaika tietää, että ylläpidämme tätä viittausta? Ei. Jos yhdelläkään Java-objektilla ei ole viittausta kohteeseen, roskakorin kerääjä saattaa tuhota sen. Tässä tapauksessa C ++ -objektillamme olisi roikkuva viittaus muistialueeseen, joka aiemmin sisälsi kelvollisen Java-objektin, mutta nyt saattoi sisältää jotain aivan erilaista.

Vaikka olemme varmoja siitä, että Java-objektimme ei kerää roskia, emme voi silti luottaa Java-objektin kahvaan jonkin ajan kuluttua. Roskakeräin ei välttämättä poista Java-objektia, mutta se voi hyvin siirtää sen toiseen paikkaan muistissa! Java-spesifikaatio ei takaa tätä tapahtumaa. Sunin JDK 1.0.2 (ainakin Solaris-käyttöjärjestelmässä) ei siirrä Java-objekteja tällä tavalla, mutta muille ajonajoille ei ole takeita.

Mitä todella tarvitsemme, on tapa ilmoittaa roskien keräilijälle, että aiomme säilyttää viittauksen Java-objektiin ja pyytää jonkinlaista "globaalia viittausta" Java-objektiin, jonka taataan pysyvän voimassa. Valitettavasti JDK 1.0.2: lla ei ole tällaista mekanismia. (Yksi on todennäköisesti saatavana JDK 1.1: ssä; katso lisätietoja artikkeleista tämän artikkelin lopusta.) Odottaessamme voimme estää tämän ongelman.

$config[zx-auto] not found$config[zx-overlay] not found