Programavimas

„Java“ patarimas 17: „Java“ integravimas su „C ++“

Šiame straipsnyje aptarsiu keletą klausimų, susijusių su C ++ kodo integravimu su „Java“ programa. Po žodžio apie tai, kodėl norėtų tai padaryti ir kokios yra kliūtys, sukursiu veikiančią „Java“ programą, kurioje naudojami objektai, parašyti C ++. Pakeliui aptarsiu kai kurias to pasekmes (pvz., Sąveiką su šiukšlių surinkimu) ir pateiksiu žvilgsnį į tai, ko galime tikėtis šioje srityje ateityje.

Kodėl reikia integruoti „C ++“ ir „Java“?

Kodėl pirmiausia norėtumėte integruoti C ++ kodą į „Java“ programą? Galų gale, Java kalba buvo sukurta iš dalies tam, kad būtų pašalinti kai kurie C ++ trūkumai. Iš tikrųjų yra kelios priežastys, kodėl galbūt norėsite integruoti C ++ su „Java“:

  • Spektaklis. Net jei kuriate platformą su tiesioginio laiko (JIT) kompiliatoriumi, yra tikimybė, kad JIT vykdymo metu sugeneruotas kodas yra žymiai lėtesnis nei lygiavertis C ++ kodas. Tobulėjant JIT technologijoms, tai turėtų tapti mažiau svarbus faktorius. (Tiesą sakant, netolimoje ateityje gera JIT technologija gali reikšti, kad „Java“ veikia greičiau nei lygiavertis C ++ kodas.)
  • Pakartotiniam seno kodo naudojimui ir integravimui į senas sistemas.
  • Norėdami tiesiogiai pasiekti aparatinę įrangą ar atlikti kitą žemo lygio veiklą.
  • Norėdami pasinaudoti įrankiais, kurių dar nėra „Java“ (subrendusiems OODBMS, ANTLR ir t. T.).

Jei žengsite žingsnį ir nuspręsite integruoti „Java“ ir „C ++“, atsisakysite kai kurių svarbių tik „Java“ programos pranašumų. Čia yra minusai:

  • Mišri C ++ / Java programa negali veikti kaip programėlė.
  • Atsisakote rodyklių saugumo. Jūsų C ++ kodas gali neteisingai persiųsti objektus, pasiekti ištrintą objektą ar sugadinti atmintį kitais būdais, kurie yra tokie lengvi C ++.
  • Jūsų kodas gali būti nešiojamas.
  • Jūsų sukurta aplinka tikrai nebus nešiojama - turėsite sugalvoti, kaip įdėti C ++ kodą į bendrą biblioteką visose dominančiose platformose.
  • C ir Java integravimo API yra vykdomi darbai, kurie greičiausiai pasikeis perėjus iš JDK 1.0.2 į JDK 1.1.

Kaip matote, „Java“ ir „C ++“ integravimas nėra malonus! Tačiau, jei norite tęsti, skaitykite toliau.

Pradėsime nuo paprasto pavyzdžio, rodančio, kaip iš „Java“ iškviesti C ++ metodus. Tada mes išplėsime šį pavyzdį, kad parodytume, kaip palaikyti stebėtojų modelį. Stebėtojų modelis yra ne tik vienas iš objektinio programavimo kertinių akmenų, bet ir puikus pavyzdys labiau įtrauktų C ++ ir Java kodų integravimo aspektų. Tada sukursime nedidelę programą, kad išbandytume „Java“ apipintą „C ++“ objektą, ir pabaigsime diskusijomis apie būsimas „Java“ kryptis.

Skambinimas C ++ iš „Java“

Kuo sunku integruoti „Java“ ir „C ++“, klausiate jūs? Juk „SunSoft“ „Java“ pamoka turi skyrių „Vietinių metodų integravimas į„ Java “programas“ (žr. Ištekliai). Kaip pamatysime, tai yra tinkama norint iškviesti C ++ metodus iš „Java“, tačiau tai nepakankamai mums paskambina „Java“ metodams iš „C ++“. Norėdami tai padaryti, turėsime atlikti šiek tiek daugiau darbo.

Kaip pavyzdį paimsime paprastą C ++ klasę, kurią norėtume naudoti iš „Java“. Manysime, kad ši klasė jau egzistuoja ir mums neleidžiama jos keisti. Ši klasė vadinama „C ++ :: NumberList“ (aiškumo dėlei visus C ++ klasių pavadinimus pridedu prieš „C ++ ::“). Ši klasė įgyvendina paprastą skaičių sąrašą su metodais, kaip pridėti skaičių į sąrašą, pateikti užklausą dėl sąrašo dydžio ir gauti elementą iš sąrašo. Sukursime „Java“ klasę, kurios darbas yra atstovauti C ++ klasei. Ši „Java“ klasė, kurią pavadinsime „NumberListProxy“, turės tuos pačius tris metodus, tačiau įgyvendinant šiuos metodus bus paskambinta C ++ ekvivalentais. Tai pavaizduota šioje objektų modeliavimo technikos (OMT) diagramoje:

„Java“ „NumberListProxy“ egzempliorius turi palaikyti nuorodą į atitinkamą „NumberList“ C ++ egzempliorių. Tai pakankamai lengva, jei šiek tiek neperkeliama: jei mes esame platformoje su 32 bitų rodyklėmis, mes galime tiesiog laikyti šį žymeklį int; jei esame platformoje, kurioje naudojami 64 bitų rodyklės (arba manome, kad galbūt būsime artimiausiu metu), galime jas ilgai saugoti. Tikrasis „NumberListProxy“ kodas yra paprastas, jei šiek tiek netvarkingas. Jis naudoja „SunSoft“ „Java Tutorial“ skyriaus „Vietinių metodų integravimas į„ Java “programas“ mechanizmus.

Pirmasis „Java“ klasės pjūvis atrodo taip:

 viešoji klasė „NumberListProxy“ {static {System.loadLibrary ("NumberList"); } NumberListProxy () {initCppSide (); } public native void addNumber (int n); viešasis gimtoji int dydis (); public native int getNumber (int i); privatus gimtoji negaliojanti initCppSide (); privatus int numerisListPtr_; // Skaičių sąrašas *} 

Statinė sekcija vykdoma įkėlus klasę. „System.loadLibrary“ () įkelia pavadintą bendrąją biblioteką, kurioje mūsų atveju yra sukompiliuota C ++ :: NumberList versija. „Solaris“ sistemoje ji tikisi rasti bendrinamą biblioteką „libNumberList.so“ kažkur $ LD_LIBRARY_PATH. Bendros bibliotekos pavadinimų suteikimo taisyklės gali skirtis kitose operacinėse sistemose.

Dauguma šios klasės metodų yra paskelbti „vietiniais“. Tai reiškia, kad pateiksime C funkciją jiems įgyvendinti. Norėdami parašyti C funkcijas, du kartus vykdome „javah“, pirmiausia kaip „javah NumberListProxy“, tada kaip „javah -stubs NumberListProxy“. Tai automatiškai sukuria tam tikrą „klijų“ kodą, reikalingą „Java“ vykdymui (kurį jis įdeda į „NumberListProxy.c“) ir sugeneruoja deklaracijas C funkcijoms, kurias turime įgyvendinti („NumberListProxy.h“).

Pasirinkau šias funkcijas įgyvendinti faile, pavadintame „NumberListProxyImpl.cc“. Tai prasideda keliomis tipiškomis #include direktyvomis:

 // // NumberListProxyImpl.cc // // // Šiame faile yra C ++ kodas, kuris įgyvendina „javah -stubs NumberListProxy“ sugeneruotas kamienas //. plg. NumberListProxy.c. #include #include "NumberListProxy.h" # įtraukti "NumberList.h" 

yra JDK dalis ir apima daug svarbių sistemos deklaracijų. „NumberListProxy.h“ mums sugeneravo „javah“ ir apima C funkcijų, kurias ketiname rašyti, deklaracijas. „NumberList.h“ yra C ++ klasės „NumberList“ deklaracija.

Konstruktoriuje „NumberListProxy“ mes vadiname savąjį metodą initCppSide (). Šis metodas turi rasti arba sukurti C ++ objektą, kurį norime atstovauti. Šiame straipsnyje aš tiesiog sukaupsiu naują C ++ objektą, nors apskritai galbūt norėtume susieti savo tarpinį serverį su C ++ objektu, kuris buvo sukurtas kitur. Mūsų vietinio metodo įgyvendinimas atrodo taip:

 void NumberListProxy_initCppSide (struct HNumberListProxy * javaObj) {NumberList * list = new NumberList (); unsand (javaObj) -> numberListPtr_ = (ilgas) sąrašas; } 

Kaip aprašyta „Java“ pamoka, mes perdavėme „rankeną“ objektui „Java NumberListProxy“. Mūsų metodas sukuria naują C ++ objektą, tada prijungia jį prie „Java“ objekto duomenų nario numberListPtr_.

Dabar apie įdomius metodus. Šie metodai atkuria žymeklį į C ++ objektą (iš „numberListPtr_“ duomenų nario), tada iškviečiama norima C ++ funkcija:

 void NumberListProxy_addNumber (struct HNumberListProxy * javaObj, long v) {NumberList * list = (NumberList *) unsand (javaObj) -> numberListPtr_; sąrašas-> addNumber (v); } ilgas NumberListProxy_size (struct HNumberListProxy * javaObj) {NumberList * list = (NumberList *) nenurodytas (javaObj) -> numberListPtr_; grąžinimo sąrašas-> dydis (); } long NumberListProxy_getNumber (struct HNumberListProxy * javaObj, long i) {NumberList * list = (NumberList *) unsand (javaObj) -> numberListPtr_; grįžimo sąrašas-> getNumber (i); } 

Funkcijų pavadinimus (NumberListProxy_addNumber ir likusius) mums nustato javah. Norėdami gauti daugiau informacijos apie tai, argumentų, siunčiamų į funkciją, tipus, „unsand“ („makrokomandą“) ir kitą „Java“ palaikymo vietinėms C funkcijoms informaciją, žr. „Java“ pamoka.

Nors šį „klijų“ rašyti yra šiek tiek varginantis, jis gana paprastas ir gerai veikia. Bet kas nutinka, kai norime paskambinti „Java“ iš C ++?

Skambinama „Java“ iš „C ++“

Prieš įsigilinant kaip Norėdami iškviesti „Java“ metodus iš C ++, leiskite man paaiškinti kodėl to gali prireikti. Anksčiau pateiktoje diagramoje nepateikiau visos C ++ klasės istorijos. Išsamesnis C ++ klasės vaizdas pateiktas žemiau:

Kaip matote, mes susiduriame su stebimu skaičių sąrašu. Šis skaičių sąrašas gali būti modifikuotas daugelyje vietų (iš „NumberListProxy“ arba iš bet kurio C ++ objekto, turinčio nuorodą į mūsų objektą „C ++ :: NumberList“). Manoma, kad „NumberListProxy“ teisingai atstovauja visi apie C ++ elgesį :: NumberList; tai turėtų apimti „Java“ stebėtojų informavimą, kai pasikeičia numerių sąrašas. Kitaip tariant, „NumberListProxy“ turi būti „java.util“ poklasis. Stebimas, kaip pavaizduota čia:

Pakanka lengvai padaryti „NumberListProxy“ java.util poklasiu. Stebima, bet kaip apie tai pranešama? Kas paskambins „setChanged“ () ir praneš „Observers“ (), kai pasikeis „C ++ :: NumberList“? Norėdami tai padaryti, mums reikės pagalbininkų klasės C ++ pusėje. Laimei, ši viena pagalbininkų klasė dirbs su bet kuria pastebima Java. Ši pagalbininkų klasė turi būti C ++ :: Observer poklasis, todėl ji gali užsiregistruoti C ++ :: NumberList. Kai pasikeis numerių sąrašas, bus vadinamas mūsų pagalbininkų klasės „update ()“ metodas. Mūsų „update ()“ metodo įgyvendinimas bus „Java“ tarpinio serverio objekto iškvietimas „setChanged“ () ir „Pranešti apie stebėtojus“ (). Tai pavaizduota OMT:

Prieš pradėdami diegti C ++ :: JavaObservableProxy, leiskite paminėti keletą kitų pakeitimų.

„NumberListProxy“ turi naują duomenų narį: javaProxyPtr_. Tai yra žymeklis į C ++ JavaObservableProxy egzempliorių. Mums to prireiks vėliau, kai aptarsime objektų sunaikinimą. Vienintelis kitas esamo kodo pakeitimas yra mūsų C funkcijos NumberListProxy_initCppSide () pakeitimas. Dabar atrodo taip:

 void NumberListProxy_initCppSide (struct HNumberListProxy * javaObj) {NumberList * list = new NumberList (); struct HObservable * stebimas = (struct HObservable *) javaObj; JavaObservableProxy * proxy = naujas JavaObservableProxy (stebimas, sąrašas); unsand (javaObj) -> numberListPtr_ = (ilgas) sąrašas; nešvarus (javaObj) -> javaProxyPtr_ = (ilgas) tarpinis serveris; } 

Atkreipkite dėmesį, kad mes nukreipėme „javaObj“ į rodyklę į „HObservable“. Tai gerai, nes žinome, kad „NumberListProxy“ yra „Observable“ poklasis. Vienintelis kitas pakeitimas yra tai, kad dabar mes sukuriame C ++ :: JavaObservableProxy egzempliorių ir išlaikome nuorodą į jį. C ++ :: JavaObservableProxy bus parašyta taip, kad ji praneštų apie bet kurią „Java Observable“, kai aptinka atnaujinimą, todėl mums reikėjo perduoti HNumberListProxy * į HObservable *.

Atsižvelgiant į iki šiol buvusias aplinkybes, gali atrodyti, kad mums tiesiog reikia įdiegti C ++ :: JavaObservableProxy: update () taip, kad jis praneštų apie pastebimą „Java“. Atrodo, kad šis sprendimas yra konceptualiai paprastas, tačiau yra kliūtis: kaip mes laikysimės nuorodos į „Java“ objektą iš C ++ objekto?

„Java“ nuorodos palaikymas C ++ objekte

Gali atrodyti, kad paprasčiausiai „Java“ objekto rankeną galime laikyti C ++ objekte. Jei taip būtų, galime koduoti C ++ :: JavaObservableProxy taip:

 klasė JavaObservableProxy public Observer {public: JavaObservableProxy (struct HObservable * javaObj, Observable * obs) {javaObj_ = javaObj; stebėtasVienas_ = obs; observOne _-> addObserver (tai); } ~ JavaObservableProxy () {observOne _-> deleteObserver (tai); } void update () {execute_java_dynamic_method (0, javaObj_, "setChanged", "() V"); } privatus: struct HObservable * javaObj_; Stebimas * stebimasVienas_; }; 

Deja, mūsų dilemos sprendimas nėra toks paprastas. Kai „Java“ perduos jums rankeną „Java“ objektui, rankena] liks galiojanti skambučio metu. Jis nebūtinai galios, jei jį laikysite ant krūvos ir bandysite naudoti vėliau. Kodėl taip yra? Dėl „Java“ šiukšlių surinkimo.

Visų pirma, mes stengiamės išlaikyti nuorodą į „Java“ objektą, bet iš kur „Java“ vykdymo laikas žino, kad palaikome šią nuorodą? Taip nėra. Jei nė vienas „Java“ objektas neturi nuorodos į objektą, šiukšlių surinkėjas gali jį sunaikinti. Tokiu atveju mūsų C ++ objekte būtų kabanti nuoroda į atminties sritį, kurioje anksčiau buvo galiojantis „Java“ objektas, bet dabar gali būti kažkas visai kito.

Net jei esame įsitikinę, kad mūsų „Java“ objekte nebus surinkta šiukšlių, po kurio laiko vis tiek negalime patikėti „Java“ objekto rankenos. Šiukšlių surinkėjas gali nepašalinti „Java“ objekto, tačiau jis gali labai gerai perkelti jį į kitą atminties vietą! „Java“ specifikacijoje nėra jokių garantijų dėl šio įvykio. „Sun“ JDK 1.0.2 (bent jau naudojant „Solaris“) tokiu būdu nepajudins „Java“ objektų, tačiau nėra jokių kitų vykdymo garantijų.

Mums iš tikrųjų reikia būdas informuoti šiukšlių surinkėją, kad planuojame išlaikyti nuorodą į „Java“ objektą ir paprašyti kažkokios „visuotinės nuorodos“ į „Java“ objektą, kuris garantuotai išliks galiojantis. Deja, JDK 1.0.2 tokio mechanizmo neturi. (Vieną turbūt bus galima rasti JDK 1.1; daugiau informacijos apie būsimas kryptis rasite šio straipsnio pabaigoje.) Kol laukiame, galime apeiti šią problemą.