B. Ενσωμάτωση Ιθαγενών Μεθόδων Στο τέλος αυτού του κεφαλαίου θα μπορείτε: Να δημιουργείτε κώδικα Java που θα φορτώνει βιβλιοθήκες και θα καλεί ιθαγενείς μεθόδους (native methods). Να χρησιμοποιείτε τη javah για να δημιουργείτε τα απαραίτητα αρχεία για ιθαγενείς μεθόδους. Να δημιουργείτε και να μεταγλωττίζετε δυναμικές βιβλιοθήκες στη C. B.1 Ιθαγενείς μέθοδοι Έχουμε δει αρκετά χαρακτηριστικά και βασικές λειτουργίες στη γλώσσα προγραμματισμού Java. Όμως, μπορεί να θέλετε να πραγματοποιήσετε λειτουργίες μέσα από εφαρμογές που βασίζονται σε Java, οι οποίες δεν μπορούν να γίνουν μόνο με τη γλώσσα προγραμματισμού Java. Σε αυτές τις περιπτώσεις μπορείτε να χρησιμοποιείτε ιθαγενείς μεθόδους (native methods) για να τις συνδέετε (link) με προγράμματα C που θα χειρίζονται τις συγκεκριμένες ανάγκες σας. Αυτή η επιπρόσθετη λειτουργικότητα έχει το κόστος της οι εφαρμογές σας δεν είναι πλέον μεταφέρσιμες παντού. Οι υπόλοιποι υπολογιστές που έχουν την ίδια αρχιτεκτονική με εσάς πρέπει να έχουν τοπικά μεταγλωττισμένο ένα αντίγραφο των C προγραμμάτων σας. Σε άλλους υπολογιστές διαφορετικής αρχιτεκτονικής θα πρέπει να μεταφέρετε (port) τα C προγράμματα στις αντίστοιχες αρχιτεκτονικές και να τις μεταγλωττίζετε με τον τοπικό μεταγλωττιστή. Η διαδικασία αυτή μπορεί να είναι δύσκολη για σύνθετα προγράμματα ή αδύνατη για προγράμματα που βασίζονται σε χαρακτηριστικά του υποκείμενου λειτουργικού συστήματος. Όμως, αν λειτουργείτε με μία μόνο αρχιτεκτονική ή θέλετε να προσθέσετε ένα καλά ορισμένο προσαρμόσιμο χαρακτηριστικό, οι ιθαγενείς μέθοδοι μπορεί να είναι η καλύτερη επιλογή για να ικανοποιήσετε τις ανάγκες σας. B.2 Ιθαγενής Hello World Η πρώτη εργασία θα είναι να καλέσετε την ιθαγενή μέθοδο από ένα πρόγραμμα Java. Για να το κάνετε αυτό, δημιουργείτε μία ιθαγενή μέθοδο για το πρόγραμμα HelloWorld. Στη συνέχεια δίνεται μία ανασκόπηση των πέντε βασικών βημάτων που απαιτούνται για να ενσωματώσετε ιθαγενείς μεθόδους στα προγράμματα Java 1. Ορίστε την κλάση Java με τις κατάλληλες δηλώσεις ιθαγενών μεθόδων 2. Δημιουργείστε ένα αρχείο επικεφαλίδας (header file) για χρήση με το C πρόγραμμά σας 3. Δημιουργείστε το αρχείο stub για χρήση με το C πρόγραμμά σας (Τα αρχεία stub απλά παρέχουν τον κώδικα που θα «κολλήσει» (glue code) ανάμεσα στα δεδομένα της Java και τα αντίστοιχά τους στη C) 4. Γράψτε το C πρόγραμμα 1
5. Συνδυάστε τον C κώδικα, τα αρχεία επικεφαλίδας και το αρχείο stub σε μία δυναμικά φορτωνόμενη βιβλιοθήκη (dynamically loadable library). B.2.1. Ορισμός ιθαγενών μεθόδων Όπως και με τις άλλες μεθόδους πρέπει να δηλώσετε όλες τις ιθαγενείς μεθόδους που σκοπεύετε να χρησιμοποιήσετε και πρέπει να έχουν οριστεί μέσα σε μία κλάση. Μπορείτε να ορίσετε την ιθαγενή μέθοδο Hello World ως εξής: public native void nativehelloworld(); Υπάρχουν δύο αλλαγές από τις υπόλοιπες public void μεθόδους που έχετε γράψει: 1. Χρησιμοποιείται ως τροποποιητής μεθόδου η δεσμευμένη λέξη native. 2. Το σώμα της μεθόδου (η πραγματική υλοποίηση) δεν ορίζεται εδώ. Αντικαθιστούμε το σώμα της μεθόδου με ένα (;) Πρέπει να τοποθετήσετε τη δήλωση της ιθαγενούς μεθόδου μέσα σε έναν ορισμό κλάσης. Η κλάση που περιέχει την ιθαγενή μέθοδο θα περιέχει επίσης ένα static μπλοκ κώδικα που φορτώνει τη δυναμική βιβλιοθήκη για την απλή μέθοδο nativehelloworld(). class NativeHello { public native void nativehelloworld(); static { System.loadLibrary( nativetest ); Ο διερμηνέας της Java εκτελεί το ορισμένο static μπλοκ κώδικα όταν φορτώνεται η κλάση. Στο πιο πάνω παράδειγμα όπως φορτώνεται η κλάση NativeHello θα φορτωθεί και η βιβλιοθήκη nativetest. B.3 Κλήση ιθαγενών μεθόδων Από τη στιγμή που έχετε «περιτυλίξει» (wrap) τις ιθαγενείς μεθόδους σας σε μία κλάση, μπορείτε να δημιουργήσετε αντικείμενα της κλάσης αυτής για να προσπελαύνετε τις ιθαγενείς μεθόδους ακριβώς όπως θα κάνατε για τις συνήθεις μεθόδους κλάσεων. Για παράδειγμα το πρόγραμμα που ακολουθεί δημιουργεί ένα νέο αντικείμενο NativeHello και καλεί τη μέθοδο nativehelloworld. class UseNative { public static void main (String args[]) { NativeHello nh = new NativeHello(); nh.nativehelloworld(); Μεταγλωττίζετε τα αρχεία.java (χρησιμοποιώντας το javac) ώστε τα αρχεία.class να είναι διαθέσιμα για τη συνέχεια. Επίσης αυτό θα σας βοηθήσει να ανακαλύψετε τυχόν μικρά τυπογραφικά λάθη που μπορεί να έχετε κάνει. 2
B.3.1. Η βοηθητική εφαρμογή javah Μπορείτε να δημιουργήσετε ένα αρχείο επικεφαλίδας C και ένα stub αρχείο για τη C με το πρόγραμμα javah. Με βάση το αρχείο NativeHello.java, η javah μπορεί να παράγει αυτόματα και τα δύο αρχεία. Για να δημιουργήσετε το αρχείο επικεφαλίδας % javah NativeHello Αυτό παράγει το αρχείο NativeHello.h Για να δημιουργήσετε το stub αρχείο % javah stubs NativeHello Αυτό παράγει το αρχείο NativeHello.c Από τη στιγμή που έχετε δημιουργήσει αυτά τα αρχεία είναι προσπελάσιμα. Αν και δε θα τροποποιήσετε τα συγκεκριμένα αρχεία, το αρχείο επικεφαλίδας σας παρέχει με την πληροφορία που χρειάζεστε για να γράψετε το πρόγραμμα C. Το αρχείο stub θα συνδεθεί στη βιβλιοθήκη σας. B.4 Κωδικοποίηση συναρτήσεων C για ιθαγενείς μεθόδους Στο σημείο αυτό το μόνο κομμάτι κώδικα που λείπει είναι το πρόγραμμα C. Για να γράψετε τον κώδικα C θα χρειαστείτε δύο ειδικά include αρχεία και τον ορισμό της συνάρτησης από το αρχείο επικεφαλίδας. Το αρχείο επικεφαλίδας (NativeHello.h) θα είναι το ένα από τα δύο ειδικά include αρχεία. Το άλλο είναι το StubPreamble.h που παρέχεται από το JDK στον κατάλογο $JAVA_HOME/include. Φυσικά θα πρέπει να γίνουν include και όσα άλλα αρχεία επικεφαλίδας είναι απαραίτητα για τη συνάρτησή σας. Θα βρείτε τη βασική πληροφορία που χρειάζεστε για να ξεκινήσετε την υλοποίηση της ιθαγενούς μεθόδου σας στο αρχείο NativeHello.h. Για κάθε συνάρτηση που δηλώνεται στο αρχείο επικεφαλίδας πρέπει εσείς να παρέχετε το σώμα. Το αρχείο NativeHello.h που παράγεται από τη javah είναι το επόμενο: /* DO NOT EDIT THIS FILE it is machine generated */ #include <native.h> /* Header for class NativeHello */ #ifndef _Included_NativeHello #define _Included_NativeHello typedef struct ClassNativeHello { char PAD; /* ANSI C requires structures to have at least one member */ ClassNativeHello; HandleTo(NativeHello); 3
extern void NativeHello_nativeHelloWorld(struct HNativeHello *); #endif Χρησιμοποιείστε τη δήλωση στο NativeHello.h για να παρέχετε έναν ορισμό για τη μέθοδο NativeHello_nativeHelloWorld(). Το αρχείο C θα πρέπει συνεπώς να είναι κάπως έτσι: /* * NWH.c * A native method HelloWorld implementation in C */ #inlcude <stdio.h> #inlude NativeHello.h #include <StubPreamble.h> void NativeHello_nativeHelloWorld(struct HNativeHello *) { printf( Hello Native World\n ); B.5 Κάνοντας το συνδυασμό Τώρα που έχετε μαζέψει όλα τα κομμάτια πρέπει να πείτε στο σύστημά σας πώς να τα συνδυάσει. Πρώτα μεταγλωττίστε το πρόγραμμα C. Θυμηθείτε να συμπεριλάβετε και το αυτόματα παραγόμενο stub αρχείο. Επίσης μπορεί να χρειαστεί να καθορίσετε τη θέση των αρχείων include. Στη συνέχεια δίνουμε ένα παράδειγμα χρήσης του μεταγλωττιστή της C για σύστημα Solaris: % cc I$JAVA_HOME/include I$JAVA_HOME/include/solaris NHW.c G NativeHello.c o libnativetest.so Οι δύο κατάλογοι include στους οποίους πρέπει να έχετε πρόσβαση για να μεταγλωττίσετε το πρόγραμμα είναι οι $JAVA_HOME/include και $JAVA_HOME/include/solaris. Μπορείτε να τους καθορίζεται στη γραμμή εντολής (command line) ή να τροποποιήσετε τη μεταβλητή περιβάλλοντος INCLUDE. Στη συνέχεια δίνεται ένα παράδειγμα χρήσης του μεταγλωττιστή Microsoft C για πλατφόρμα Win95 ή NT. C:\> c1 NativeHello.c NHW.c FenativeTest.dll MD LD javai.lib Από τη στιγμή που έχετε δημιουργήσει το αρχείο βιβλιοθήκης μπορείτε να εκτελέσετε το δοκιμαστικό πρόγραμμα των ιθαγενών μεθόδων % java UseNative Hello Native World! ή C:\> java UseNative Hello Native World! 4
Αν λάβετε ένα java.lang.unsatisfiedlinkerror, μπορεί να χρειαστεί να ενημερώσετε τη μεταβλητή συστήματος LD_LIBRARY_PATH ώστε να περιλαμβάνει και τον τρέχοντα κατάλογο. B.6 Μεταβίβαση πληροφορίας σε μία ιθαγενή μέθοδο Το δείγμα ιθαγενούς μεθόδου που είδαμε δεν αντιμετώπιζε προσπέλαση σε πληροφορία από την ορίζουσα κλάση ούτε δεχόταν ορίσματα. Και τα δύο φαινόμενα εμφανίζονται συχνά στον προγραμματισμό. Ορίσματα σε μεθόδους Προσπέλαση σε μέλη δεδομένων αντικειμένου B.6.1. Μεταβίβαση ενός ορίσματος Όταν ορίζετε μία ιθαγενή μέθοδο στη Java μπορείτε να παρέχετε ένα όρισμα όπως ακριβώς το κάνετε και όταν ορίζετε άλλες μεθόδους. public native void nativehelloworld2(int count); Η δήλωση θα παράγει την πιο κάτω καταχώρηση στο αρχείο επικεφαλίδας NativeHello2.h: extern void NativeHello2_nativeHelloWorld2( struct HNativeHello2 *, long); Τώρα μπορείτε να ξαναγράψετε τη μέθοδο C ώστε να κάνει μία επανάληψη στο παρεχόμενο πλήθος «φορών». #include <stdio.h> #include <StubPreamble.h> #include NativeHello2.h void NativeHello2_nativeHelloWorld2(struct HNativeHello2 *h, long count) { for (; count > 0; count--) printf( Hello Native World!\n ); B.6.2. Προσπέλαση σε μέλη δεδομένων αντικειμένου Το αρχείο StubPreamble.h (στον κατάλογο $JAVA_HOME/include) περιέχει αρκετές μακροεντολές για χρήση με αντικείμενα Java μέσα στα προγράμματα C. Η πιο συνηθισμένη απαίτηση σε μία ιθαγενή μέθοδο είναι να προσπελαύνει τα μέλη δεδομένων μίας κλάσης Java. Αυτή την προσπέλαση την παρέχει η μακροεντολή unhand(). Για παράδειγμα, θεωρείστε ότι γράφετε μία κλάση η οποία αποθηκεύει το μετρητή επαναλήψεων ως πληροφορία κατάστασης. class NativeHello3 { public int count; public NativeHello3(int i) { count = i; 5
public native void nativehelloworld3(); static { System.loadLibrary( nativetest ); Μέσα στο πρόγραμμα C μπορείτε πλέον να προσπελαύνετε τη μεταβλητή count χρησιμοποιώντας την unhand(). void NativeHello3_nativeHelloWorld3(struct HNativeHello3 *this) { printf( Hello Native World!\n ); printf( Java class member count = %d.\n, unhand(this)->count); B.6.3. Προσπέλαση συμβολοσειρών Όπως μπορεί να θυμάστε οι συμβολοσειρές στη Java αποτελούνται από χαρακτήρες Unicode των 16 bits, ενώ στη C αποτελούνται από χαρακτήρες ASCII των 8 bits. Καθώς οι συμβολοσειρές είναι συνήθη αντικείμενα για μεταβίβαση ανάμεσα σε κώδικα Java και ιθαγενή κώδικα, έχουν οριστεί αρκετές μέθοδοι για να βοηθήσουν να γίνει η διαχείριση συμβολοσειρών πιο απλή. Για συμβολοσειρές Java στον κώδικα C ο τύπος μεταβλητής είναι (Hjava_lang_String *). Οι βοηθητικές συναρτήσεις βρίσκονται στο αρχείο javastring.h (στον κατάλογο $JAVA_HOME/include). Αυτό το αρχείο περιλαμβάνει συναρτήσεις όπως: void javastringprint(hjava_lang_string *) η οποία τυπώνει ένα αντικείμενο Java τύπου String. int javastringlength(hjava_lang_string *) η οποία επιστρέφει το μήκος ενός Java String unicode *javastring2unicode(hjava_lang_string *s, unicode *buf, int len) μετατρέπει len χαρακτήρες ενός αντικειμένου Java τύπου String σε πίνακα από χαρακτήρες unicode. char *javastring2cstring(hjava_lang_string *s, char * buf, int len) Μετατρέπει ένα αντικείμενο Java τύπου String σε μία συνήθη C συμβολοσειρά (ένα πίνακα από C char). Υπάρχουν δύο ακόμα συναρτήσεις για μετατροπή συμβολοσειρών της Java σε συμβολοσειρές στη C. Αμφότερες οι συναρτήσεις μετατροπής δεσμεύουν μνήμη για τη συμβολοσειρά που θα μετατραπεί αυτόματα. Όμως, οι δύο αυτές συναρτήσεις αντιμετωπίζουν διαφορετικά τη μνήμη που δεσμεύουν. char *makecstring(hjava_lang_string *s) Η συνάρτηση επιστρέφει ένα προσωρινό χειριστήριο μνήμης για τη συμβολοσειρά και θα απελευθερώσει τη μνήμη μόλις όλες οι αναφορές στη συμβολοσειρά βγουν εκτός πεδίου εμβέλειας. 6
char *alloccstring(hjava_lang_string *s) Η συνάρτηση αυτή μετατρέπει τη συμβολοσειρά Java και επιστρέφει ένα malloc() δείκτη στον πίνακα χαρακτήρων. Υπεύθυνοι για την απελευθέρωση της μνήμης είστε εσείς. Στη συνέχεια ακολουθεί ένα παράδειγμα της μεθόδου HelloNativeWolrd4() που λαμβάνει δύο ορίσματα ένα μήνυμα προς εκτύπωση και το πλήθος των φορών που θα το τυπώσει. Η Κλάση βιβλιοθήκης Java class NativeHello4 { // Ορισμός της κλήσης ιθαγενούς μεθόδου public native void nativehelloworld4(string message, int count); // Φορτώνετε τη βιβλιοθήκη που περιέχει την υλοποίηση // της ιθαγενούς μεθόδου static { System.loadLibrary( nativetest ); // Η κλάση Java που χρησιμοποιεί το NativeWorld4 class UseNative4 { public static void main(string args[]) { // Δημιουργείτε ένα νέο αντικείμενο που περιέχει την // ιθαγενή μέθοδο NativeHello4 nh4 = new NativeHello(); // Καλέστε την nh4.nativehelloworld4( You did it!!, 4); Το αρχείο επικεφαλίδας από javah /* DO NOT EDIT THIS FILE it is machine generated */ #include <native.h> /* Header for class NativeHello4 */ #ifndef _Included_NativeHello4 #define _Included_NativeHello4 typedef struct ClassNativeHello4 { char PAD; /* ANSI C requires structures to have at least one member */ ClassNativeHello4; HandleTo(NativeHello4); 7
extern void NativeHello4_nativeHelloWorld4(struct HNativeHello4 *, struct Hjava_lang_String *, long); #endif Το αρχείο C που περιέχει την ιθαγενή υλοποίηση /* * NHW.c * A native method HelloWorld implementation in C */ #include <stdio.h> #include <StubPreamble.h> #include <javastring.h> #include NativeHello4.h> void NativeHello4_nativeHelloWorld4(struct HNativeHello4 * this, struct Hjava_lang_String *message, long cout) { char *C_message; C_message = makecstring(message); for (; count > 0; count--) printf( %s\n, C_message); B.7 Περίληψη Στη συνέχεια ακολουθεί μία σύντομη επανάληψη της ακριβούς διαδικασίας που αποτελείται για την ενσωμάτωση των ιθαγενών μεθόδων αγνοώντας προφανώς τη διαδικασία πληκτρολόγησης των προγραμμάτων: 1. Δημιουργείτε το πρόγραμμα που περιέχει τις δηλώσεις των ιθαγενών μεθόδων και το στατικό κώδικα που φορτώνει τη δυναμική βιβλιοθήκη. 2. Δημιουργείτε το πρόγραμμα που περιέχει κλήσεις στις ιθαγενείς μεθόδους 3. Μεταγλωττίζετε τα αρχεία.java 4. Δημιουργείτε το αρχείο επικεφαλίδας C 5. Δημιουργείτε το αρχείο stub 6. Δημιουργείτε το πρόγραμμα C που υλοποιεί τις ιθαγενείς μεθόδους 7. Μεταγλωττίζετε τη δυναμική βιβλιοθήκη 8. Ενημερώνετε τη μεταβλητή LD_LIBRARY_PATH 9. Εκτελείτε το πρόγραμμα. 8