Programmering

Java Tips 17: Integrering av Java med C ++

I denne artikkelen vil jeg diskutere noen av problemene som er involvert i integrering av C ++ - kode med et Java-program. Etter et ord om hvorfor man ønsker å gjøre dette og hva noen av hindringene er, bygger jeg opp et fungerende Java-program som bruker objekter skrevet i C ++. Underveis vil jeg diskutere noen av implikasjonene av å gjøre dette (som interaksjon med søppeloppsamling), og jeg vil gi et glimt av hva vi kan forvente i dette området i fremtiden.

Hvorfor integrere C ++ og Java?

Hvorfor vil du i utgangspunktet integrere C ++ - kode i et Java-program? Tross alt ble Java-språket delvis opprettet for å løse noen av manglene ved C ++. Det er faktisk flere grunner til at du kanskje vil integrere C ++ med Java:

  • Opptreden. Selv om du utvikler for en plattform med en just-in-time (JIT) kompilator, er oddsen at koden generert av JIT-kjøretiden er betydelig tregere enn den tilsvarende C ++ - koden. Når JIT-teknologien forbedres, bør dette bli mindre av en faktor. (Faktisk kan god JIT-teknologi i nær fremtid bety at Java kjører raskere enn tilsvarende C ++ - kode.)
  • For gjenbruk av eldre koder og integrering i eldre systemer.
  • For å få direkte tilgang til maskinvare eller gjøre andre aktiviteter på lavt nivå.
  • Å utnytte verktøy som ennå ikke er tilgjengelige for Java (modne OODBMSer, ANTLR og så videre).

Hvis du tar steget og bestemmer deg for å integrere Java og C ++, gir du opp noen av de viktige fordelene med et Java-eneste program. Her er ulempene:

  • En blandet C ++ / Java-applikasjon kan ikke kjøres som en applet.
  • Du gir opp pekersikkerheten. C ++ -koden din er fri til å feilsøke objekter, få tilgang til et slettet objekt eller ødelegge minne på noen av de andre måtene som er så enkle i C ++.
  • Koden din er kanskje ikke bærbar.
  • Det innebygde miljøet ditt vil definitivt ikke være bærbart - du må finne ut hvordan du kan plassere C ++ - kode i et delt bibliotek på alle interesserte plattformer.
  • API-ene for å integrere C og Java pågår og vil sannsynligvis endres med overgangen fra JDK 1.0.2 til JDK 1.1.

Som du ser er integrering av Java og C ++ ikke for svak av hjertet! Men hvis du ønsker å fortsette, kan du lese videre.

Vi begynner med et enkelt eksempel som viser hvordan du kaller C ++ -metoder fra Java. Vi utvider dette eksemplet for å vise hvordan du kan støtte observatørmønsteret. Observatørmønsteret, i tillegg til å være en av hjørnesteinene i objektorientert programmering, fungerer som et fint eksempel på de mer involverte aspektene ved integrering av C ++ og Java-kode. Deretter bygger vi et lite program for å teste vårt Java-pakket C ++ - objekt, og vi avslutter med en diskusjon om fremtidige retninger for Java.

Ringer til C ++ fra Java

Hva er så vanskelig med å integrere Java og C ++, spør du? Tross alt, SunSofts Java-veiledning har et avsnitt om "Integrering av innfødte metoder i Java-programmer" (se Ressurser). Som vi får se, er dette tilstrekkelig for å ringe C ++ -metoder fra Java, men det gir oss ikke nok til å ringe Java-metoder fra C ++. For å gjøre det, må vi gjøre litt mer arbeid.

Som et eksempel tar vi en enkel C ++ klasse som vi ønsker å bruke fra Java. Vi antar at denne klassen allerede eksisterer, og at vi ikke har lov til å endre den. Denne klassen heter "C ++ :: NumberList" (for klarhetens skyld vil jeg foran alle C ++ klassenavn ha "C ++ ::"). Denne klassen implementerer en enkel liste med tall, med metoder for å legge til et nummer i listen, spørre størrelsen på listen og få et element fra listen. Vi lager en Java-klasse hvis jobb det er å representere C ++ -klassen. Denne Java-klassen, som vi vil kalle NumberListProxy, vil ha de samme tre metodene, men implementeringen av disse metodene vil være å kalle C ++ -ekvivalenter. Dette er avbildet i følgende diagram for objektmodellering (OMT):

En Java-forekomst av NumberListProxy må holde på en referanse til den tilsvarende C ++ -forekomsten av NumberList. Dette er enkelt nok, hvis det ikke er bærbart: Hvis vi er på en plattform med 32-biters pekere, kan vi ganske enkelt lagre denne pekeren i en int; hvis vi er på en plattform som bruker 64-biters pekepinner (eller vi tror vi kan være i nær fremtid), kan vi lagre den på lenge. Den faktiske koden for NumberListProxy er grei, om noe rotete. Den bruker mekanismene fra delen "Integrering av innfødte metoder i Java-programmer" i SunSofts Java-veiledning.

Et første kutt i Java-klassen ser slik ut:

 offentlig klasse NumberListProxy {statisk {System.loadLibrary ("NumberList"); } NumberListProxy () {initCppSide (); } offentlig innfødt ugyldig addNumber (int n); offentlig innfødt int størrelse (); offentlig innfødt int getNumber (int i); privat innfødt ugyldig initCppSide (); privat int nummerListPtr_; // Nummerliste *} 

Den statiske delen kjøres når klassen lastes inn. System.loadLibrary () laster det navngitte delte biblioteket, som i vårt tilfelle inneholder den kompilerte versjonen av C ++ :: NumberList. Under Solaris vil den forvente å finne det delte biblioteket "libNumberList.so" et sted i $ LD_LIBRARY_PATH. Navnekonvensjoner for delt bibliotek kan variere i andre operativsystemer.

De fleste metodene i denne klassen blir erklært som "innfødte". Dette betyr at vi vil tilby en C-funksjon for å implementere dem. For å skrive C-funksjonene kjører vi javah to ganger, først som "javah NumberListProxy", deretter som "javah -stubs NumberListProxy." Dette genererer automatisk litt "lim" -kode som trengs for Java-kjøretiden (som den setter i NumberListProxy.c) og genererer erklæringer for C-funksjonene som vi skal implementere (i NumberListProxy.h).

Jeg valgte å implementere disse funksjonene i en fil som heter NumberListProxyImpl.cc. Det begynner med noen typiske #include-direktiver:

 // // NumberListProxyImpl.cc // // // Denne filen inneholder C ++ - koden som implementerer stubber generert // av "javah -stubs NumberListProxy". jfr. NumberListProxy.c. #include #include "NumberListProxy.h" #include "NumberList.h" 

er en del av JDK, og inkluderer en rekke viktige systemerklæringer. NumberListProxy.h ble generert for oss av javah, og inkluderer erklæringer om C-funksjonene vi skal skrive. NumberList.h inneholder erklæringen fra C ++ klasse NumberList.

I NumberListProxy-konstruktøren kaller vi den opprinnelige metoden initCppSide (). Denne metoden må finne eller opprette C ++ - objektet vi vil representere. I forbindelse med denne artikkelen vil jeg bare tilordne et nytt C ++ - objekt, selv om vi generelt sett kanskje vil koble proxyen vår til et C ++ - objekt som ble opprettet andre steder. Implementeringen av vår opprinnelige metode ser slik ut:

 ugyldig NumberListProxy_initCppSide (struct HNumberListProxy * javaObj) {NumberList * list = new NumberList (); unhand (javaObj) -> numberListPtr_ = (lang) liste; } 

Som beskrevet i Java-veiledning, får vi et "håndtak" til Java NumberListProxy-objektet. Metoden vår oppretter et nytt C ++ - objekt, og fester det deretter til numberListPtr_-datamedlemet til Java-objektet.

Nå videre til de interessante metodene. Disse metodene gjenoppretter en peker til C ++ -objektet (fra numberListPtr_ data-medlem), og påkaller deretter ønsket C ++ -funksjon:

 ugyldig NumberListProxy_addNumber (struct HNumberListProxy * javaObj, long v) {NumberList * list = (NumberList *) unhand (javaObj) -> numberListPtr_; liste-> addNumber (v); } lang NumberListProxy_size (struct HNumberListProxy * javaObj) {NumberList * list = (NumberList *) unhand (javaObj) -> numberListPtr_; returliste-> størrelse (); } lang NumberListProxy_getNumber (struct HNumberListProxy * javaObj, long i) {NumberList * list = (NumberList *) unhand (javaObj) -> numberListPtr_; returliste-> getNumber (i); } 

Funksjonsnavnene (NumberListProxy_addNumber, og resten) bestemmes for oss av javah. For mer informasjon om dette, hvilke typer argumenter som sendes til funksjonen, makroen unhand () og andre detaljer om Java's støtte for innfødte C-funksjoner, se Java-veiledning.

Selv om dette "limet" er litt kjedelig å skrive, er det ganske greit og fungerer bra. Men hva skjer når vi vil ringe Java fra C ++?

Ringer Java fra C ++

Før du dykker ned i hvordan for å kalle Java-metoder fra C ++, la meg forklare Hvorfor dette kan være nødvendig. I diagrammet jeg viste tidligere presenterte jeg ikke hele historien om C ++ klassen. Et mer komplett bilde av C ++ klassen vises nedenfor:

Som du ser, har vi å gjøre med en observerbar nummerliste. Denne nummerlisten kan endres fra mange steder (fra NumberListProxy, eller fra et hvilket som helst C ++ - objekt som har en referanse til vårt C ++ :: NumberList-objekt). NumberListProxy skal trofast representere alle av oppførselen til C ++ :: NumberList; dette bør inkludere å varsle Java-observatører når nummerlisten endres. Med andre ord må NumberListProxy være en underklasse av java.util.Observable, som vist her:

Det er lett nok å gjøre NumberListProxy til en underklasse av java.util.Observable, men hvordan blir det varslet? Hvem vil kalle setChanged () og varsleObservers () når C ++ :: NumberList endres? For å gjøre dette trenger vi en hjelperklasse på C ++ - siden. Heldigvis vil denne ene hjelperklassen fungere med hvilken som helst Java som kan observeres. Denne hjelperklassen må være en underklasse av C ++ :: Observer, slik at den kan registrere seg med C ++ :: NumberList. Når nummerlisten endres, vil vår hjelperklasse 'update () -metode bli kalt. Implementeringen av vår oppdateringsmetode () vil være å ringe setChanged () og varsleObservers () på Java proxy-objektet. Dette er avbildet i OMT:

Før jeg går inn i implementeringen av C ++ :: JavaObservableProxy, la meg nevne noen av de andre endringene.

NumberListProxy har fått et nytt datamedlem: javaProxyPtr_. Dette er en peker til forekomsten av C ++ JavaObservableProxy. Vi trenger dette senere når vi diskuterer ødeleggelse av objekter. Den eneste andre endringen i vår eksisterende kode er en endring i C-funksjonen NumberListProxy_initCppSide (). Det ser nå slik ut:

 ugyldig NumberListProxy_initCppSide (struct HNumberListProxy * javaObj) {NumberList * list = new NumberList (); struct HObservable * observerbar = (struct HObservable *) javaObj; JavaObservableProxy * proxy = ny JavaObservableProxy (observerbar, liste); unhand (javaObj) -> numberListPtr_ = (lang) liste; unhand (javaObj) -> javaProxyPtr_ = (lang) proxy; } 

Merk at vi kaster javaObj til en peker til en HObservable. Dette er OK, fordi vi vet at NumberListProxy er en underklasse av Observable. Den eneste andre endringen er at vi nå oppretter en C ++ :: JavaObservableProxy-forekomst og opprettholder en referanse til den. C ++ :: JavaObservableProxy vil bli skrevet slik at den varsler Java Observable når den oppdager en oppdatering, og derfor trengte vi å kaste HNumberListProxy * til HObservable *.

Gitt bakgrunnen så langt, kan det se ut som om vi bare trenger å implementere C ++ :: JavaObservableProxy: update () slik at den varsler en Java som kan observeres. Den løsningen virker konseptuelt enkel, men det er en hake: Hvordan holder vi på en referanse til et Java-objekt fra et C ++ -objekt?

Opprettholde en Java-referanse i et C ++ - objekt

Det kan virke som om vi bare kunne lagre et håndtak til et Java-objekt i et C ++ - objekt. Hvis dette var tilfelle, kan vi kode C ++ :: JavaObservableProxy slik:

 klasse JavaObservableProxy public Observer {public: JavaObservableProxy (struct HObservable * javaObj, Observable * obs) {javaObj_ = javaObj; observerteOne_ = obs; observerteOne _-> addObserver (dette); } ~ JavaObservableProxy () {observertOne _-> deleteObserver (dette); } ugyldig oppdatering () {execute_java_dynamic_method (0, javaObj_, "setChanged", "() V"); } privat: struct HObservable * javaObj_; Observerbar * observertEn_; }; 

Dessverre er løsningen på vårt dilemma ikke så enkel. Når Java gir deg et håndtak til et Java-objekt, forblir håndtaket] gyldig for samtalen. Det vil ikke nødvendigvis forbli gyldig hvis du lagrer det på dyngen og prøver å bruke det senere. Hvorfor er dette slik? På grunn av Java's søppelinnsamling.

Først og fremst prøver vi å opprettholde en referanse til et Java-objekt, men hvordan vet Java-kjøretiden at vi opprettholder den referansen? Det gjør det ikke. Hvis ingen Java-objekter har en referanse til objektet, kan søppeloppsamleren ødelegge det. I dette tilfellet ville C ++ -objektet ha en dinglende referanse til et minneområde som tidligere inneholdt et gyldig Java-objekt, men som nå kan inneholde noe helt annet.

Selv om vi er sikre på at Java-objektet ikke vil samle inn søppel, kan vi fortsatt ikke stole på et Java-objekt etter hvert. Søppeloppsamleren fjerner kanskje ikke Java-objektet, men det kan godt føre til at det flyttes til et annet sted i minnet! Java-spesifikasjonen inneholder ingen garanti mot denne hendelsen. Suns JDK 1.0.2 (i det minste under Solaris) vil ikke flytte Java-objekter på denne måten, men det er ingen garantier for andre kjøretider.

Det vi virkelig trenger er en måte å informere søppeloppsamleren om at vi planlegger å opprettholde en referanse til et Java-objekt, og be om en slags "global referanse" til Java-objektet som garantert forblir gyldig. Dessverre har JDK 1.0.2 ingen slik mekanisme. (En vil sannsynligvis være tilgjengelig i JDK 1.1; se slutten av denne artikkelen for mer informasjon om fremtidige veibeskrivelser.) Mens vi venter, kan vi tømme oss rundt dette problemet.

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