Programovanie

Tip 17 pre Java: Integrácia jazyka Java s C ++

V tomto článku sa budem venovať niektorým problémom spojeným s integráciou kódu C ++ s aplikáciou Java. Po slove o tom, prečo by to človek chcel robiť a aké sú prekážky, vytvorím funkčný program Java, ktorý používa objekty napísané v C ++. Postupom času budem diskutovať o niektorých dôsledkoch tohto konania (napríklad o interakcii so zberom odpadu) a predstavím pohľad na to, čo môžeme v tejto oblasti v budúcnosti očakávať.

Prečo integrovať C ++ a Java?

Prečo by ste chceli vôbec integrovať kód C ++ do programu Java? Koniec koncov, jazyk Java bol čiastočne vytvorený s cieľom vyriešiť niektoré nedostatky C ++. V skutočnosti existuje niekoľko dôvodov, prečo budete chcieť integrovať C ++ s Javou:

  • Výkon. Aj keď vyvíjate platformu s kompilátorom JIT (just-in-time), je pravdepodobné, že kód vygenerovaný runtime JIT je podstatne pomalší ako ekvivalentný kód C ++. Keď sa technológia JIT zdokonalí, malo by to byť menej. (V skutočnosti môže dobrá technológia JIT v blízkej budúcnosti znamenať, že bude fungovať Java rýchlejšie ako ekvivalentný kód C ++.)
  • Na opätovné použitie pôvodného kódu a integráciu do starších systémov.
  • Priamy prístup k hardvéru alebo iné činnosti na nízkej úrovni.
  • Využiť nástroje, ktoré zatiaľ pre Java nie sú k dispozícii (vyspelé OODBMSes, ANTLR atď.).

Ak sa rozhodnete a rozhodnete sa integrovať Java a C ++, vzdáte sa niektorých dôležitých výhod aplikácie iba pre Java. Tu sú nevýhody:

  • Zmiešaná aplikácia C ++ / Java nemôže bežať ako applet.
  • Vzdáte sa bezpečnosti ukazovateľa. Váš kód v C ++ môže voľne prenášať objekty, pristupovať k odstráneným objektom alebo poškodiť pamäť akýmkoľvek z ďalších spôsobov, ktoré sú v C ++ také ľahké.
  • Váš kód nemusí byť prenosný.
  • Vaše vybudované prostredie určite nebude prenosné - budete musieť prísť na to, ako vložiť kód C ++ do zdieľanej knižnice na všetkých platformách, ktoré vás zaujímajú.
  • API pre integráciu C a Java sú v štádiu rozpracovania a s veľkou pravdepodobnosťou sa zmenia prechodom z JDK 1.0.2 na JDK 1.1.

Ako vidíte, integrácia Java a C ++ nie je pre slabé povahy! Ak však chcete pokračovať, čítajte ďalej.

Začneme jednoduchým príkladom, ktorý ukazuje, ako volať metódy C ++ z Javy. Tento príklad potom rozšírime, aby sme ukázali, ako podporiť vzor pozorovateľa. Vzor pozorovateľa okrem toho, že je jedným zo základných kameňov objektovo orientovaného programovania, slúži ako pekný príklad dôležitejších aspektov integrácie kódu C ++ a Java. Potom zostavíme malý program na testovanie nášho C ++ objektu zabaleného v Jave a skončíme diskusiou o budúcich smeroch pre Javu.

Volanie C ++ z Javy

Čo je tak náročné na integrácii Java a C ++, pýtate sa? Koniec koncov, SunSoft Výukový program Java obsahuje časť „Integrácia natívnych metód do programov Java“ (pozri Zdroje). Ako uvidíme, je to postačujúce na volanie metód C ++ z Javy, ale to nám nedáva dosť na to, aby sme volali metódy Java z C ++. Aby sme to dosiahli, budeme musieť urobiť trochu viac práce.

Ako príklad si vezmeme jednoduchú triedu C ++, ktorú by sme chceli použiť v prostredí Java. Budeme predpokladať, že táto trieda už existuje a že ju nemôžeme meniť. Táto trieda sa volá „C ++ :: NumberList“ (pre prehľadnosť uvediem pred všetky názvy tried C ++ predponu „C ++ ::“). Táto trieda implementuje jednoduchý zoznam čísel s metódami na pridanie čísla do zoznamu, dotaz na veľkosť zoznamu a získanie prvku zo zoznamu. Vytvoríme triedu Java, ktorej úlohou bude reprezentovať triedu C ++. Táto trieda Java, ktorú budeme nazývať NumberListProxy, bude mať rovnaké tri metódy, ale implementáciou týchto metód bude volanie ekvivalentov C ++. Toto je zobrazené na nasledujúcom diagrame techniky modelovania objektov (OMT):

Inštancia Java NumberListProxy musí obsahovať odkaz na zodpovedajúcu inštanciu NumberList v C ++. To je dosť ľahké, ak je mierne neprenosné: Ak sme na platforme s 32-bitovými ukazovateľmi, môžeme tento ukazovateľ jednoducho uložiť do súboru int; ak sme na platforme, ktorá používa 64-bitové ukazovatele (alebo si myslíme, že by sme mohli byť v blízkej budúcnosti), môžeme to uložiť na dlho. Aktuálny kód pre NumberListProxy je priamy, aj keď trochu chaotický. Využíva mechanizmy z časti „Integrácia natívnych metód do programov Java“ príručky Java SunSoft.

Prvý rez v triede Java vyzerá takto:

 verejná trieda NumberListProxy {static {System.loadLibrary ("NumberList"); } NumberListProxy () {initCppSide (); } public native void addNumber (int n); public native int size (); public native int getNumber (int i); private native void initCppSide (); private int numberListPtr_; // NumberList *} 

Statická časť sa spustí po načítaní triedy. System.loadLibrary () načíta pomenovanú zdieľanú knižnicu, ktorá v našom prípade obsahuje skompilovanú verziu C ++ :: NumberList. V systéme Solaris sa očakáva, že niekde v $ LD_LIBRARY_PATH nájde zdieľanú knižnicu „libNumberList.so“. Konvencie názvov zdieľaných knižníc sa môžu v iných operačných systémoch líšiť.

Väčšina metód v tejto triede je deklarovaná ako „pôvodná“. To znamená, že poskytneme funkciu C na ich implementáciu. Aby sme mohli napísať funkcie C, spustíme javah dvakrát, najskôr ako „javah NumberListProxy“, potom ako „javah -stubs NumberListProxy“. Toto automaticky generuje nejaký „lepiaci“ kód potrebný pre runtime Java (ktorý vkladá do NumberListProxy.c) a generuje vyhlásenia pre funkcie C, ktoré máme implementovať (v NumberListProxy.h).

Tieto funkcie som sa rozhodol implementovať do súboru s názvom NumberListProxyImpl.cc. Začína to niekoľkými typickými smernicami #include:

 // // NumberListProxyImpl.cc // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // // por. NumberListProxy.c. #include #include "NumberListProxy.h" #include "NumberList.h" 

je súčasťou JDK a obsahuje množstvo dôležitých systémových vyhlásení. NumberListProxy.h pre nás vygeneroval javah a obsahuje vyhlásenia o funkciách C, ktoré sa chystáme napísať. NumberList.h obsahuje vyhlásenie triedy C ++ NumberList.

V konštruktore NumberListProxy voláme natívnu metódu initCppSide (). Táto metóda musí nájsť alebo vytvoriť objekt C ++, ktorý chceme reprezentovať. Na účely tohto článku iba alokujem nový objekt C ++, aj keď vo všeobecnosti by sme mohli namiesto toho chcieť prepojiť náš proxy s objektom C ++, ktorý bol vytvorený inde. Implementácia našej natívnej metódy vyzerá takto:

 void NumberListProxy_initCppSide (struct HNumberListProxy * javaObj) {NumberList * list = new NumberList (); unfand (javaObj) -> numberListPtr_ = (dlhý) zoznam; } 

Ako je opísané v Výukový program Java, odovzdali sme „handle“ do objektu Java NumberListProxy. Naša metóda vytvorí nový objekt C ++ a potom ho pripojí k dátovému členu numberListPtr_ objektu Java.

Teraz k zaujímavým metódam. Tieto metódy obnovia ukazovateľ na objekt C ++ (z dátového člena numberListPtr_) a potom vyvolajú požadovanú funkciu C ++:

 void NumberListProxy_addNumber (struct HNumberListProxy * javaObj, long v) {NumberList * list = (NumberList *) unfand (javaObj) -> numberListPtr_; zoznam-> pridať číslo (v); } dlhá NumberListProxy_size (struct HNumberListProxy * javaObj) {NumberList * list = (NumberList *) unfand (javaObj) -> numberListPtr_; návratový zoznam-> veľkosť (); } long NumberListProxy_getNumber (struct HNumberListProxy * javaObj, long i) {NumberList * list = (NumberList *) unhand (javaObj) -> numberListPtr_; návratový zoznam-> getNumber (i); } 

Názvy funkcií (NumberListProxy_addNumber a ostatné) pre nás určuje javah. Ďalšie informácie o tomto, typoch argumentov odoslaných funkcii, makru unfand () a ďalších podrobnostiach podpory jazyka Java pre natívne funkcie C nájdete v dokumente Výukový program Java.

Aj keď je toto „lepidlo“ trochu zdĺhavé pri písaní, je pomerne priame a funguje dobre. Čo sa však stane, keď chceme volať Javu z C ++?

Volanie Javy z C ++

Pred ponorením sa do ako volať metódy Java z C ++, dovoľte mi to vysvetliť prečo to môže byť nevyhnutné. V diagrame, ktorý som ukázal skôr, som neuviedol celý príbeh triedy C ++. Kompletnejší obrázok triedy C ++ je uvedený nižšie:

Ako vidíte, máme dočinenia s pozorovateľným číselným zoznamom. Tento číselný zoznam je možné upraviť na mnohých miestach (z NumberListProxy alebo z ľubovoľného objektu C ++, ktorý má odkaz na náš objekt C ++ :: NumberList). NumberListProxy má verne reprezentovať všetko správania C ++ :: NumberList; to by malo zahŕňať upozornenie pozorovateľov Java, keď sa zoznam čísel zmení. Inými slovami, NumberListProxy musí byť podtriedou java.util.Observable, ako je to znázornené tu:

Je dosť ľahké urobiť z NumberListProxy podtriedu java.util.Observable, ale ako sa to zobrazuje? Kto zavolá setChanged () a notifyObservers (), keď sa zmení C ++ :: NumberList? Aby sme to dosiahli, budeme potrebovať pomocnú triedu na strane C ++. Našťastie bude táto jedna pomocná trieda pracovať s akoukoľvek pozorovateľnou Java. Táto pomocná trieda musí byť podtriedou C ++ :: Observer, aby sa mohla zaregistrovať v C ++ :: NumberList. Keď sa zoznam čísel zmení, zavolá sa naša metóda update () triedy pomocníka. Implementáciou našej metódy update () bude volanie setChanged () a notifyObservers () na proxy objekt Java. Toto je zobrazené na OMT:

Predtým, ako sa pustíme do implementácie C ++ :: JavaObservableProxy, dovoľte mi spomenúť niektoré z ďalších zmien.

NumberListProxy má nového dátového člena: javaProxyPtr_. Toto je ukazovateľ na inštanciu C ++ JavaObservableProxy. Budeme to potrebovať neskôr, keď budeme diskutovať o ničení objektov. Jedinou ďalšou zmenou nášho existujúceho kódu je zmena našej funkcie C NumberListProxy_initCppSide (). Teraz to vyzerá takto:

 void NumberListProxy_initCppSide (struct HNumberListProxy * javaObj) {NumberList * list = new NumberList (); struct HObservable * pozorovateľné = (struct HObservable *) javaObj; JavaObservableProxy * proxy = nový JavaObservableProxy (pozorovateľný, zoznam); unfand (javaObj) -> numberListPtr_ = (dlhý) zoznam; unfand (javaObj) -> javaProxyPtr_ = (dlhý) proxy; } 

Všimnite si, že vrháme javaObj na ukazovateľ na HObservable. To je v poriadku, pretože vieme, že NumberListProxy je podtriedou Observable. Jedinou ďalšou zmenou je, že teraz vytvoríme inštanciu C ++ :: JavaObservableProxy a udržujeme na ňu odkaz. C ++ :: JavaObservableProxy bude napísaný tak, aby upozornil akýkoľvek pozorovateľný program Java Observable, keď zistí aktualizáciu, a preto sme potrebovali vložiť HNumberListProxy * do HObservable *.

Vzhľadom na doterajšie pozadie sa môže zdať, že stačí implementovať C ++ :: JavaObservableProxy: update () tak, aby oznamoval pozorovateľnú Javu. Toto riešenie sa zdá byť koncepčne jednoduché, ale je tu zádrhel: Ako uchováme odkaz na objekt Java zvnútra objektu C ++?

Udržiavanie referencie Java v objekte C ++

Mohlo by sa zdať, že by sme mohli jednoducho uložiť popisovač do objektu Java v objekte C ++. Ak by to tak bolo, mohli by sme kódovať C ++ :: JavaObservableProxy takto:

 trieda JavaObservableProxy public Observer {public: JavaObservableProxy (struct HObservable * javaObj, Observable * obs) {javaObj_ = javaObj; ObservOne_ = obs; ObservedOne _-> addObserver (this); } ~ JavaObservableProxy () {ObservOne _-> deleteObserver (this); } void update () {execute_java_dynamic_method (0, javaObj_, "setChanged", "() V"); } súkromné: struct HObservable * javaObj_; Pozorovateľné * ObservedOne_; }; 

Bohužiaľ, riešenie našej dilemy nie je také jednoduché. Keď vám Java odovzdá popisovač objektu Java, popisovač] zostane platný po celú dobu hovoru. To nemusí nevyhnutne zostať v platnosti, ak ho uložíte na haldu a pokúsite sa ho použiť neskôr. Prečo je to tak? Kvôli zbieraniu odpadu v Jave.

Najskôr sa snažíme udržiavať odkaz na objekt Java, ale ako runtime Java vie, že udržiavame tento odkaz? Nie je. Ak žiadny objekt Java nemá odkaz na objekt, smetiar ho môže zničiť. V takom prípade by náš objekt C ++ mal visiaci odkaz na oblasť pamäte, ktorá obsahovala platný objekt Java, ale teraz mohla obsahovať niečo celkom iné.

Aj keď sme si istí, že náš objekt Java nezhromaždí odpadky, stále nemôžeme po nejakom čase dôverovať popisovaču objektu Java. Smetiar nemusí odstrániť objekt Java, ale mohol by ho veľmi dobre presunúť na iné miesto v pamäti! Špecifikácia Java neobsahuje žiadnu záruku proti tomuto výskytu. Sun JDK 1.0.2 (minimálne v systéme Solaris) nebude takýmto spôsobom presúvať objekty Java, ale neexistujú žiadne záruky pre ďalšie prevádzkové časy.

To, čo skutočne potrebujeme, je spôsob informovania zberača odpadu, že plánujeme zachovať odkaz na objekt Java, a požiadať o akýsi „globálny odkaz“ na objekt Java, ktorý zaručene zostane platný. Je smutné, že JDK 1.0.2 takýto mechanizmus nemá. (Jeden bude pravdepodobne k dispozícii v JDK 1.1; ďalšie informácie o ďalších smeroch nájdete na konci tohto článku.) Zatiaľ čo čakáme, dokážeme sa týmto problémom vyhnúť.

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