Programiranje

Java Nasvet 17: Integracija Jave s C ++

V tem članku bom razpravljal o nekaterih težavah, povezanih z integracijo kode C ++ z aplikacijo Java. Po besedi o tem, zakaj bi kdo to želel in kakšne so ovire, bom sestavil delujoč program Java, ki uporablja predmete, napisane v jeziku C ++. Med potjo bom razpravljal o nekaterih posledicah tega (na primer o interakciji z zbiranjem smeti) in predstavil, kaj lahko pričakujemo na tem področju v prihodnosti.

Zakaj integrirati C ++ in Javo?

Zakaj bi sploh želeli integrirati kodo C ++ v program Java? Navsezadnje je bil jezik Java deloma ustvarjen za odpravljanje nekaterih pomanjkljivosti C ++. Pravzaprav obstaja več razlogov, zakaj bi morda želeli integrirati C ++ z Javo:

  • Izvedba. Tudi če razvijate platformo s pravočasnim prevajalnikom (JIT), obstaja verjetnost, da je koda, ki jo ustvari izvajalno okolje JIT, bistveno počasnejša od enakovredne kode C ++. Ko se tehnologija JIT izboljšuje, bi to moralo postati manj pomemben dejavnik. (Pravzaprav v bližnji prihodnosti dobra tehnologija JIT lahko pomeni, da Java teče hitreje kot enakovredna koda C ++.)
  • Za ponovno uporabo starejše kode in integracijo v stare sisteme.
  • Za neposreden dostop do strojne opreme ali opravljanje drugih dejavnosti na nizki ravni.
  • Izkoristiti orodja, ki še niso na voljo za Javo (zreli OODBMS, ANTLR itd.).

Če se poglobite in se odločite za integracijo Java in C ++, se odrečete nekaterim pomembnim prednostim aplikacije samo za Javo. Tu so slabosti:

  • Mešana aplikacija C ++ / Java se ne more zagnati kot programček.
  • Odpovedate se varnosti kazalca. Koda C ++ lahko prosto napačno predvaja predmete, dostopa do izbrisanega predmeta ali poškoduje pomnilnik na kateri koli drug način, ki je v C ++ tako enostaven.
  • Vaša koda morda ni prenosljiva.
  • Vaše zgrajeno okolje zagotovo ne bo prenosljivo - morali boste ugotoviti, kako vstaviti kodo C ++ v knjižnico v skupni rabi na vseh platformah, ki nas zanimajo.
  • API-ji za integracijo C in Jave že potekajo in se bodo s prehodom z JDK 1.0.2 na JDK 1.1 zelo verjetno spremenili.

Kot lahko vidite, integracija Jave in C ++ ni za tiste s srcem! Če pa želite nadaljevati, nadaljujte.

Začeli bomo s preprostim primerom, ki prikazuje, kako klicati metode C ++ iz Jave. Nato bomo ta primer razširili, da pokažemo, kako podpirati vzorec opazovalca. Vzorec opazovalca je poleg tega, da je eden od temeljev objektno usmerjenega programiranja, lep primer bolj vključenih vidikov integracije kode C ++ in Java. Nato bomo zgradili majhen program za preizkušanje našega Java zavitega predmeta C ++ in končali z razpravo o prihodnjih navodilih za Javo.

Klicanje C ++ iz Jave

Vprašate se, kaj je tako težko pri integraciji Java in C ++? Navsezadnje SunSoft's Vadnica za Java ima razdelek o "Vključevanju izvornih metod v programe Java" (glejte Viri). Kot bomo videli, je to primerno za klicanje metod C ++ z Jave, vendar nam ne daje dovolj, da bi klicali metode Java iz C ++. Da bi to naredili, bomo morali še malo delati.

Kot primer bomo vzeli preprost razred C ++, ki bi ga radi uporabili znotraj Jave. Predvidevali bomo, da ta razred že obstaja in da ga ne smemo spreminjati. Ta razred se imenuje "C ++ :: NumberList" (za jasnost bom pred imena vseh razredov C ++ postavil predpono z "C ++ ::"). Ta razred izvaja preprost seznam števil, z metodami za dodajanje števil na seznam, poizvedbo o velikosti seznama in pridobivanje elementa s seznama. Naredili bomo razred Java, katerega naloga je predstavljati razred C ++. Ta razred Java, ki ga bomo imenovali NumberListProxy, bo imel enake tri metode, vendar bo izvedba teh metod klicala ekvivalente C ++. To je prikazano v naslednjem diagramu tehnike objektnega modeliranja (OMT):

Primerek Java NumberListProxy mora zadržati sklic na ustrezen primerek Number ++ Seznam C ++. To je dovolj enostavno, čeprav nekoliko neprenosljivo: če smo na platformi z 32-bitnimi kazalci, lahko ta kazalec preprosto shranimo v int; če smo na platformi, ki uporablja 64-bitne kazalce (ali pa mislimo, da smo v bližnji prihodnosti), jo lahko shranimo dolgo. Dejanska koda za NumberListProxy je preprosta, čeprav nekoliko neurejena. Uporablja mehanizme iz razdelka "Vključevanje izvornih metod v programe Java" v vadnici Java za SunSoft.

Prvi rez v razredu Java je videti tako:

 javni razred NumberListProxy {static {System.loadLibrary ("NumberList"); } NumberListProxy () {initCppSide (); } javna izvorna void addNumber (int n); javna nativna velikost int (); javni izvorni int getNumber (int i); zasebna domača void initCppSide (); zasebni int numberListPtr_; // NumberList *} 

Statični odsek se zažene, ko je razred naložen. System.loadLibrary () naloži imenovano knjižnico v skupni rabi, ki v našem primeru vsebuje prevedeno različico C ++ :: NumberList. Pod Solarisom bo pričakoval, da bo nekje v $ LD_LIBRARY_PATH našel knjižnico v skupni rabi "libNumberList.so". Dogovori o poimenovanju knjižnic v skupni rabi se lahko razlikujejo v drugih operacijskih sistemih.

Večina metod v tem razredu je razglašenih kot "izvornih". To pomeni, da bomo za njihovo izvedbo zagotovili funkcijo C. Za pisanje funkcij C zaženemo javah dvakrat, najprej kot "javah NumberListProxy", nato kot "javah -stubs NumberListProxy." To samodejno ustvari nekaj "lepilne" kode, potrebne za izvajanje Java (ki jo vstavi v NumberListProxy.c) in generira deklaracije za funkcije C, ki jih moramo implementirati (v NumberListProxy.h).

Te funkcije sem izbral za uporabo v datoteki z imenom NumberListProxyImpl.cc. Začne se z nekaj tipičnimi direktivami #include:

 // // NumberListProxyImpl.cc // // // Ta datoteka vsebuje kodo C ++, ki izvaja klice, ki jih generira // "javah -stubs NumberListProxy". prim. NumberListProxy.c. #include #include "NumberListProxy.h" #include "NumberList.h" 

je del JDK in vključuje številne pomembne sistemske izjave. NumberListProxy.h je za nas ustvaril javah in vključuje izjave funkcij C, ki jih bomo napisali. NumberList.h vsebuje izjavo razreda C ++ NumberList.

V konstruktorju NumberListProxy pokličemo izvorno metodo initCppSide (). Ta metoda mora najti ali ustvariti objekt C ++, ki ga želimo predstaviti. Za namene tega članka bom samo dodelil nov objekt C ++, čeprav bi na splošno morda želeli svoj proxy povezati s objektom C ++, ki je bil ustvarjen drugje. Izvedba naše izvorne metode je videti takole:

 void NumberListProxy_initCppSide (struct HNumberListProxy * javaObj) {NumberList * list = new NumberList (); unhand (javaObj) -> numberListPtr_ = (dolg) seznam; } 

Kot je opisano v Vadnica za Java, smo predali "ročaj" objektu Java NumberListProxy. Naša metoda ustvari nov objekt C ++, nato ga pritrdi na podatkovni član numberListPtr_ predmeta Java.

Zdaj pa k zanimivim metodam. Te metode obnovijo kazalec na objekt C ++ (iz podatkovnega člana numberListPtr_) in nato prikličejo želeno funkcijo C ++:

 void NumberListProxy_addNumber (struct HNumberListProxy * javaObj, long v) {NumberList * list = (NumberList *) unhand (javaObj) -> numberListPtr_; seznam-> addNumber (v); } dolg NumberListProxy_size (struct HNumberListProxy * javaObj) {NumberList * list = (NumberList *) unhand (javaObj) -> numberListPtr_; vrni seznam-> size (); } long NumberListProxy_getNumber (struct HNumberListProxy * javaObj, long i) {NumberList * list = (NumberList *) unhand (javaObj) -> numberListPtr_; vrni seznam-> getNumber (i); } 

Imena funkcij (NumberListProxy_addNumber in ostala) nam določi javah. Za več informacij o tem, vrste argumentov, poslanih funkciji, makro unhand () in druge podrobnosti o podpori Java za izvorne funkcije C, glejte Vadnica za Java.

Čeprav je to "lepilo" nekoliko dolgočasno pisati, je dokaj enostavno in dobro deluje. Toda kaj se zgodi, ko želimo Java poklicati s C ++?

Klicanje Jave iz C ++

Preden se poglobite kako da pokličem metode Java iz C ++, naj razložim zakaj to je lahko potrebno. V diagramu, ki sem ga prikazal prej, nisem predstavil celotne zgodbe razreda C ++. Podrobnejša slika razreda C ++ je prikazana spodaj:

Kot lahko vidite, imamo opravka z opaznim seznamom številk. Ta seznam številk je mogoče spremeniti z več krajev (iz NumberListProxy ali iz katerega koli predmeta C ++, ki se sklicuje na naš objekt C ++ :: NumberList). NumberListProxy naj bi zvesto predstavljal vse vedenja C ++ :: NumberList; to mora vključevati obveščanje opazovalcev Java, ko se seznam številk spremeni. Z drugimi besedami, NumberListProxy mora biti podrazred java.util.Observable, kot je prikazano tukaj:

Dovolj je enostavno narediti NumberListProxy podrazred java.util.Observable, kako pa je obveščen? Kdo bo poklical setChanged () in notifyObservers (), ko se C ++ :: NumberList spremeni? Za to bomo potrebovali pomožni razred na strani C ++. Na srečo bo ta en pomožni razred deloval s katero koli opazno Java. Ta pomožni razred mora biti podrazred C ++ :: Observer, da se lahko registrira s C ++ :: NumberList. Ko se seznam številk spremeni, bomo poklicali metodo pomožnega razreda 'update (). Izvedba naše metode update () bo poklicala setChanged () in notifyObservers () na objektu Java proxy. To je na sliki v OMT:

Preden se lotim izvajanja C ++ :: JavaObservableProxy, naj omenim še nekatere druge spremembe.

NumberListProxy ima novega podatkovnega člana: javaProxyPtr_. To je kazalec na primerek C ++ JavaObservableProxy. To bomo potrebovali kasneje, ko bomo razpravljali o uničenju predmetov. Edina druga sprememba naše obstoječe kode je sprememba naše funkcije C NumberListProxy_initCppSide (). Zdaj je videti takole:

 void NumberListProxy_initCppSide (struct HNumberListProxy * javaObj) {NumberList * list = new NumberList (); struct HObservable * opazljiv = (struct HObservable *) javaObj; JavaObservableProxy * proxy = nov JavaObservableProxy (opazen, seznam); unhand (javaObj) -> numberListPtr_ = (dolg) seznam; unhand (javaObj) -> javaProxyPtr_ = (dolg) proxy; } 

Upoštevajte, da smo javaObj oddali v kazalec na HObservable. To je v redu, ker vemo, da je NumberListProxy podrazred Observable. Edina druga sprememba je ta, da zdaj ustvarimo primerek C ++ :: JavaObservableProxy in vzdržujemo sklic nanj. C ++ :: JavaObservableProxy bo napisan tako, da bo ob zaznavanju posodobitve obvestil katero koli Java Observable, zato smo morali HNumberListProxy * preusmeriti v HObservable *.

Glede na dosedanje ozadje se morda zdi, da moramo samo implementirati C ++ :: JavaObservableProxy: update () tako, da obvesti Java opazljivo. Ta rešitev se zdi konceptualno enostavna, vendar obstaja težava: kako pridržimo sklic na objekt Java znotraj predmeta C ++?

Vzdrževanje sklica Java v objektu C ++

Morda se zdi, da bi lahko preprosto shranili ročico v objekt Java v objektu C ++. Če bi bilo tako, bi lahko C ++ :: JavaObservableProxy kodirali tako:

 razred JavaObservableProxy public Observer {public: JavaObservableProxy (struct HObservable * javaObj, Observable * obs) {javaObj_ = javaObj; opaženoOno_ = obs; obserOne _-> addObserver (to); } ~ JavaObservableProxy () {opaženoOne _-> deleteObserver (to); } void update () {execute_java_dynamic_method (0, javaObj_, "setChanged", "() V"); } zasebno: struct HObservable * javaObj_; Observable * спостерігаloOne_; }; 

Rešitev naše dileme žal ni tako preprosta. Ko vam Java posreduje ročaj predmetu Java, ostane ročaj] veljaven med klicem. Ne bo nujno ostalo veljavno, če ga shranite na kup in ga poskusite uporabiti pozneje. Zakaj je temu tako? Zaradi zbiranja smeti na Javi.

Najprej poskušamo ohraniti referenco na objekt Java, kako pa izvajalno okolje Java ve, da ohranjamo to referenco? Ne. Če se noben objekt Java ne sklicuje na objekt, ga lahko zbiralnik smeti uniči. V tem primeru bi naš objekt C ++ imel visečo referenco na področje pomnilnika, ki je prej vsebovalo veljaven objekt Java, zdaj pa lahko vsebuje nekaj povsem drugega.

Tudi če smo prepričani, da naš objekt Java ne bo zbiral smeti, še vedno ne moremo zaupati ročice predmetu Java po določenem času. Zbiralec smeti morda ne bo odstranil predmeta Java, vendar bi ga lahko zelo dobro premaknil na drugo mesto v pomnilniku! Specifikacija Java ne vsebuje nobenega jamstva za ta pojav. Sun-ov JDK 1.0.2 (vsaj pod Solarisom) ne bo premikal objektov Java na ta način, vendar ni nobenih jamstev za druge izvedbe.

Zares potrebujemo način, kako zbiralca smeti obvestiti, da nameravamo ohraniti referenco na objekt Java, in zahtevati nekakšno "globalno referenco" na objekt Java, ki bo zagotovo ostal veljaven. Na žalost JDK 1.0.2 nima takega mehanizma. (Eden bo verjetno na voljo v JDK 1.1; glejte konec tega članka za več informacij o prihodnjih navodilih.) Med čakanjem lahko težavo rešimo.

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