Java Native Interface łączenie Javy i C C

background image

28

Programowanie

Java

www.sdjournal.org

Software Developer’s Journal 8/2006

Java Native Interface

– łączenie Javy i C/C++

J

NI (ang. Java Native Interface) jest technologią

pozwalającą na wywoływanie z poziomu Javy

funkcji zaimplementowanych w C/C++. Imple-

mentacje tych funkcji muszą znajdować się w biblio-

tekach dynamicznych ładowanych na żądanie przez

maszynę wirtualną Javy. Z poziomu takich funkcji

– nazywanych funkcjami rodzimymi albo natywny-

mi (ang. native method) – mamy dostęp do elemen-

tów środowiska Javy: możemy wywoływać metody,

odwoływać się do atrybutów, tworzyć obiekty. Sa-

ma procedura zastosowania funkcji rodzimej i wywoły-

wania jej w programie jest bardzo prosta i opiszę ją za

chwilę na przykładzie. Pewnej wiedzy wymaga jedynie

odwoływanie się z poziomu takich funkcji do składo-

wych obiektów czy przetwarzanie tablic Javy. Szczegó-

ły zostaną wyjaśnione w dalszej części artykułu.

Szybki start

Napiszemy wersję JNI nieśmiertelnego programu

HelloWorld. Wykorzystamy funkcję rodzimą do wy-

świetlenia komunikatu.

Przygotowanie kodu źródłowego

Program składa się z klasy

HelloWorld

zawierającej

metodę

sayHello()

. Jak widać na Listingu 1, nie posia-

da ona ciała a jej deklaracja jest poprzedzona słowem

kluczowym

native

. Oznacza ono, że JVM będzie po-

szukiwać implementacji tej metody w załadowanych

bibliotekach dynamicznych. Takie metody nazywane

są metodami rodzimymi.

Musimy zatem taką bibliotekę załadować. Wykorzy-

stamy statyczną metodę

System.loadLibrary(String)

.

Jako argument pobiera ona nazwę biblioteki. Biblioteka

dynamiczna musi zostać załadowana przed pierwszym

odwołaniem do metody rodzimej. W związku z tym,

wywołanie metody

loadLibrary()

najlepiej jest umie-

ścić na początku metody

main()

, albo w bloku statycz-

nym – będzie on wykonany podczas ładowania klasy.

Kod klasy

HelloWorld

prezentuje Listing 1.

Kompilacja

Plik HelloWorld.java zawierający klasę

HelloWorld

kom-

pilujemy normalnie: javac HelloWorld.java Jednak nie

możemy jeszcze wykonać naszego programu. Próba

uruchomienia da rezultat widoczny na Rysunku 1. Wy-

jątek

UnsatisfiedLinkError

jest spowodowany brakiem

biblioteki dynamicznej o nazwie

HelloWorld

(nie jest to

nazwa pliku z biblioteką – patrz dalej). Podobny komu-

nikat (Rysunek 2) zobaczymy, jeśli przed odwołaniem

do metody rodzimej w programie nie nastąpi wywoła-

nie metody

loadLibrary()

ładującej podaną bibliotekę

(np. bo zapomnieliśmy je umieścić w kodzie). Musimy

w takim razie napisać funkcję rodzimą i utworzyć bi-

bliotekę dynamiczną zawierającą jej implementację.

Prototyp funkcji implementującej (w szczególności jej

nazwa) nie może być przypadkowy. Aby się dowie-

dzieć pod jaką nazwą JVM szuka implementacji na-

szej metody rodzimej musimy wygenerować plik na-

główkowy, zawierający jej deklarację.

Generowanie pliku nagłówkowego

Do generowania plików nagłówkowych służy program

javah, dostarczany wraz z pakietem SDK Javy. Znajduje

się on w podkatalogu bin pakietu, tam gdzie kompilator

i interpreter. Zakładając, że w katalogu bieżącym znaj-

duje się plik HelloWorld.class, polecenie javah -jni Hello-

World wygeneruje plik HelloWorld.h, zawierający proto-

typy funkcji implementujących metody rodzime zadekla-

rowane w klasie

HelloWorld

. Jego zawartość przedsta-

wia Listing 2. Mimo, iż metoda

sayHello()

jest bezpara-

metrowa, to jej rodzima implementacja posiada dwa pa-

rametry. Są to standardowe argumenty przekazywane

wszystkim funkcjom implementującym metody rodzime.

Ich znaczenie zostanie wyjaśnione dalej. Makra

JNIE-

XPORT

i

JNICALL

są potrzebne (na platformie Win32) do

wyeksportowania funkcji z biblioteki dynamicznej i za-

pewnienia odpowiedniego protokołu wywołania. Są one

zdefiniowane w pliku nagłówkowym jni_md.h.

Implementacja metody rodzimej

Zatem nasza funkcja nazywa się

Java _ HelloWorld _

sayHello

i pobiera dwa argumenty (

JNIEnv *, jobject

),

Bartłomiej Starosta

Bartłomiej Starosta jest wykładowcą w Polsko-Japoń-
skiej Wyższej Szkole Technik Komputerowych.
Jest autorem kilku książek poświęconych Javie.
Kontakt: barstar@pjwstk.edu.pl.

Rysunek 1.

Próba uruchomienia programu bez

biblioteki dynamicznej

Rysunek 2.

Próba uruchomienia programu bez

załadowania biblioteki dynamicznej

background image

Java Native Interface – łączenie Javy i C/C++

29

www.sdjournal.org

Software Developer’s Journal 8/2006

które na razie ignorujemy. Przedrostek

Java

występujący w na-

zwie zawsze towarzyszy funkcjom implementującym metody ro-

dzime. Dalej następuje nazwa klasy, podkreślenie i nazwa me-

tody. Tak wygląda budowa nazwy funkcji rodzimej w najprost-

szym przypadku. Jeśli klasa znajduje się w pakiecie nazwanym

i/lub zawiera przeciążone metody rodzime, to ich nazewnictwo

się komplikuje. Implementację funkcji

Java _ HelloWorld _ sayHel-

lo

umieścimy w pliku HelloWorld.cpp (nazwa jest dowolna). Jego

zawartość prezentuje Listing 3. Należy pamiętać o włączeniu pli-

ku nagłówkowego HelloWorld.h na początku pliku z implementa-

cją. Dołącza on plik nagłówkowy jni.h, który zawiera deklaracje

funkcji, typów i makr niezbędnych do pracy z JNI.

Utworzenie biblioteki dynamicznej

Kolejnym krokiem jest skompilowanie kodu C++ do posta-

ci biblioteki dynamicznej. Niezależnie od stosowanej platfor-

my musimy poinformować kompilator, gdzie znajdują się pliki

nagłówkowe jni.h i jni_md.h (włączany przez jni.h). Wchodzą

one w skład SDK Javy i znajdują się w podkatalogach główne-

go katalogu dystrybucji: include (jni.h) oraz include\win32 lub
include/linux (jni_md.h) – zależnie od używanego systemu.

Listing 4 przedstawia konkretne wywołania dla popular-

nych kompilatorów. Zakładamy, że pakiet SDK został zainsta-

lowany w katalogu /j2sdk dla Linuxa i C:\j2sdk dla Win32.

Nazwa pliku zawierającego bibliotekę jest nazwą biblioteki

z dodanym rozszerzeniem .dll dla Win32 lub .so dla Linuksa. Do-

datkowo, w drugim przypadku nazwa pliku musi zaczynać się od

prefiksu lib (nie wchodzi on jednak w skład nazwy biblioteki, jaką

podajemy do metody

loadLibrary()

). Jako wynik kompilacji po-

winniśmy otrzymać plik z biblioteką dynamiczną: HelloWorld.dll

na platformie Win32, lub libHelloWorld.so dla Linuksa.

Uruchomienie programu

Przyszedł wreszcie czas na uruchomienie programu. Aby za-

kończyło się ono sukcesem, musimy poinformować system

operacyjny gdzie należy szukać naszej biblioteki dynamicz-

nej. W tym celu nadajemy odpowiednią wartość zmiennej sys-

temowej określającej położenia bibliotek dynamicznych. Pod

Linuksem jest to

LD _ LIBRARY _ PATH

a pod Win32 po prostu

PATH

. W drugim przypadku nie trzeba nic robić, jeśli biblioteka

znajduje się w katalogu bieżącym. Przykładowe komendy za-

mieszczone są w ramce. Teraz można już uruchomić maszy-

nę wirtualną i jeśli wszystko zostało wykonane prawidłowo zo-

baczymy na konsoli znany komunikat (Rysunek 3).

Konwencje nazewnicze JNI

Wiemy już jak wykorzystać kod C/C++ w Javie, ale nie wiemy

jeszcze jak go napisać. Oczywiście nie chodzi nam tu o pisa-

nie zwykłego kodu w tych językach, lecz o dostęp do danych

i metod Javy, w celu ich wykorzystania bądź przetworzenia.

Aby móc w pełni zastosować mechanizmy, które to umożliwia-

ją, musimy zapoznać się z pewnymi konwencjami nazewni-

czymi, jak również poznać typy C/C++, które reprezentują ty-

py Javy na poziomie rodzimym.

Deskryptory

Deskryptor jest symbolicznym kodem typu. Deskryptory są po-

trzebne do kodowania sygnatur metod rodzimych, a także do

identyfikowania składowych klas Javy. Są trzy rodzaje deskryp-

torów: klas, pól i metod. Deskryptory pól i metod można poznać

przy pomocy programu javap, który służy do dekompilacji klas

Javy. Opcja

-s

zleca wypisanie deskryptorów pól, opcja

-p

powo-

Listing 1.

Klasa HelloWorld

public

class

HelloWorld

{


static

{

System

.

loadLibrary

(

"HelloWorld"

)

;

}

public

native

void

sayHello

()

;

public

static

void

main

(

String

[]

args

){

new

HelloWorld

()

.

sayHello

()

;

}

}

javah a pakiety nazwane

Jeśli klasa

Klasa

znajduje się w pakiecie nazwanym

foo.bar

(za-

tem jej kwalifikowaną nazwą jest

foo.bar.Klasa

), to programowi

javah przekazujemy pełną nazwę: javah -jni foo.bar.Klasa. Zakła-
da on – podobnie jak interpreter Javy – że skompilowany plik Kla-
sa.class
umieszczony jest w strukturze katalogowej odpowiadającej
strukturze pakietów. W tym przypadku jest to foo/bar/Klasa.class.
Natomiast wygenerowany plik nagłówkowy zostanie umieszczony
w katalogu bieżącym.

Funkcje rodzime a pakiety nazwane

Jeśli klasa zawierająca metodę rodzimą

fun()

znajduje się w pakie-

cie nazwanym, na przykład

foo.bar.Klasa

, to funkcja rodzima do-

starczająca implementacji nazywa się

Java _ foo _ bar _ Klasa _

fun()

. Jak widać, pomiędzy obowiązkowym prefiksem

Java

a na-

zwą klasy pojawiła się nazwa pakietu z tym, że zamiast kropek ma-
my podkreślenia.

Ustawienie zmiennej środowiskowej

wskazującej położenie biblioteki

dynamicznej

bash

,

sh

,

ksh

export LD _ LIBRARY _ PATH=lib

tcsh

,

csh

setenv LD _ LIBRARY _ PATH lib

ms-dos

,

cmd.exe

dla Win32

set PATH=%path%;lib

lib

jest katalogiem zawierającym bibliotekę – plik libHello-

World.so lub HelloWorld.dll.

Nazwy metod przeciążonych

Jeśli w klasie występują przeciążone metody rodzime, to do nazw
funkcji implementujących dokleja się sufiks złożony z dwóch zna-
ków podkreślenia

_ _

, po którym następują deskryptory para-

metrów. Jeśli jedna z metod jest bezargumentowa, to otrzymuje
tylko sufiks

_ _

.

Na przykład dla klasy

Klasa

zawierającej trzy przeciążone me-

tody:

public class Klasa { native float fun(); native long

fun(long l); native int fun(int i, int j); }

prototypy funk-

cji implementujących będą takie:

jfloat Java _ Klasa _ fun _ _

(JNIEnv *, jobject); jlong Java _ Klasa _ fun _ _ J(JNIEnv *,
jobject, jlong); jint Java _ Klasa _ fun _ _ II(JNIEnv *, jo-
bject, jint, jint);

Ich deskryptory to

()F

,

(J)J

i

(II)I

.

background image

30

Programowanie

Java

www.sdjournal.org

Software Developer’s Journal 8/2006

duje wypisanie informacji również dla składowych prywatnych.

Wynik polecenia

javap -s -p HelloWorld

przedstawia Rysunek 4.

Deskryptorem klasy jest po prostu jej pełna kwalifikowana

nazwa, w której kropki oddzielające składniki pakietu (o ile wy-

stępują) zostały zamienione na ukośniki "

/

". Na przykład de-

skryptorem klasy

java.lang.Class

jest

"java/lang/Class"

.

Deskryptor klasy reprezentującej tablicę (są to wewnętrz-

ne klasy tworzone przez JVM) jest napisem składającym się

z jednego lub więcej – ich liczba jest równa liczbie wymiarów

tablicy – znaków

"["

(nawias kwadratowy lewy), po którym na-

stępuje deskryptor pola elementów tablicy. Na przykład de-

skryptor klasy reprezentującej tablicę

int[]

to

"[I"

, a dla tabli-

cy

Object[][]

to

"[[Ljava/lang/Object;"

. Deskryptory pól typów

pierwotnych Javy pokazuje Tabela 1.

Deskryptor pola typu referencyjnego zaczyna się od litery

"

L

", za którą umieszcza się deskryptor klasy zakończony śred-

nikiem "

;

". Na przykład deskryptorem pola typu

java.lang.Class

jest

"Ljava/lang/Class;"

. Deskryptor metody składa się z de-

skryptorów pól parametrów umieszczonych w nawiasach

okrągłych, za czym występuje deskryptor pola wyniku. De-

skryptory parametrów nie są oddzielone spacjami ani żadny-

mi innymi znakami. Jeśli metoda jest bezargumentowa, to na-

wiasy okrągłe są puste. Jeśli typem zwracanym jest

void

, to

deskryptorem wyniku jest

V

. Dla konstruktorów w miejscu de-

skryptora wyniku podaje się również

V

. Oto kilka przykładów:

"()V"

jest deskryptorem metody

void f()

"(I)J"

jest deskryptorem metody

long f(int i)

"(SZ)Ljava/lang/String;"

jest deskryptorem metody

String

f(short s, boolean b)

"([BB)[[Ljava/lang/Class;"

jest deskryptorem metody

Class[][] f(byte[] t, byte b)

Mapowanie typów

Typy Javy są na poziomie C/C++ reprezentowane przez specy-

ficzne typy JNI zdefiniowane w plikach nagłówkowych jni.h i jni_

md.h. W przypadku typów pierwotnych nie są to nowe typy, lecz

nowe nazwy na istniejące typy wprowadzone przy pomocy

ty-

pedef

. Odwzorowanie typów pierwotnych pokazuje Tabela 2. Typ

jchar

jest 16 bitowy i służy do reprezentowania znaków Unico-

de na platformach, które mają wsparcie dla tego systemu kodo-

wania. Do posługiwania się wartościami typu

jboolean

zdefinio-

wano dwa makra:

JNI _ TRUE

i

JNI _ FALSE

. Obiekty Javy są prze-

kazywane na zmiennych typu

jobject

. Z punktu widzenia języ-

ków C/C++ są to wskaźniki, jednak nie wolno odwoływać się do

Listing 2.

Plik nagłówkowy wygenerowany dla klasy

HelloWorld

/* DO NOT EDIT THIS FILE - it is machine generated */

#

include

<

jni

.

h

>

/* Header for class HelloWorld */

#

ifndef

_Included_HelloWorld

#

define

_Included_HelloWorld

#

ifdef

__cplusplus

extern

"C"

{

#

endif

/*
* Class: HelloWorld
* Method: sayHello
* Signature: ()V
*/

JNIEXPORT

void

JNICALL

Java_HelloWorld_sayHello

(

JNIEnv

*

,

jobject

)

;

#

ifdef

__cplusplus

}

#

endif

#

endif

Kodowanie UTF-8

W systemie UTF-8 (w wersji używanej przez maszynę wirtualną
Javy), jeśli najstarszy 8 bit znaku jest równy 0, to 7 bitów młod-
szych jest traktowane jako kod znaku w systemie ASCII. Jeśli na-
tomiast najstarszy bit jest równy 1, to dany bajt jest częścią dwu-
lub trzybajtowej sekwencji kodującej 16-bitowy znak Unicode.
Napisy UTF-8 są zakończone znakiem

'\0'

, tak jak zwykłe łańcu-

chy C/C++.

Funkcje pobierające wskaźnik do pierwszego

elementu tablicy

jboolean* GetBooleanArrayElements(jbooleanArray a,
jboolean *c)

jbyte* GetByteArrayElements(jbyteArray a, jboolean
*c)

jchar* GetCharArrayElements(jcharArray a, jboolean
*c)

jshort* GetShortArrayElements(jshortArray a, jboolean
*c)

jint* GetIntArrayElements(jintArray a, jboolean *c)

jlong* GetLongArrayElements(jlongArray a, jboolean
*c)

jfloat* GetFloatArrayElements(jfloatArray a, jboolean
*c)

jdouble* GetDoubleArrayElements(jdoubleArray a, jbo-
olean *c)

Parametr

c

ma podobne znaczenie jak w przypadku łańcuchów

znakowych (metoda

GetStringUTFChars()

) -– zostanie na nim

przekazana informacja o tym, czy została utworzona lokalna ko-
pia tablicy (

*c == JNI_TRUE

), czy też wskaźnik odnosi się do tabli-

cy zawartej w obiekcie Javy (

*c == JNI_FALSE

) . Jeśli nie jesteśmy

tym zainteresowani to podajemy

NULL

.

Odniesienia do klas

Wskaźnik do obiektu typu

jclass

reprezentującego klasę lub in-

terfejs Javy można uzyskać funkcjami:

jclass GetObjectClass(jobject obj);
jclass FindClass(const char *name);

Pierwsza zwraca odniesienie do obiektu reprezentującego klasę ar-
gumentu

obj

(jest on obiektem klasy zwracanej jako wynik). Druga

zwraca odniesienie do klasy, której deskryptor został podany jako
argument

name

. Na przykład:

jclass string_cls = env->FindClass("java/lang/String");
jclass objArray_cls = env->FindClass("[Ljava/lang/Object;");

Pierwsze wywołanie zwraca odniesienie do klasy

ja-

va.lang.String

. Drugie zwraca odniesienie do klasy reprezentują-

cej tablice obiektów

java.lang.Object[]

.

background image

Java Native Interface – łączenie Javy i C/C++

31

www.sdjournal.org

Software Developer’s Journal 8/2006

nich bezpośrednio, lecz poprzez wywołania specjalnych funkcji

opisanych dalej. Zmienne tego rodzaju będziemy nazywać od-

niesieniami (ang. opaque reference). Najczęściej używane kla-

sy mają zdefiniowane swoje odpowiedniki, np.:

jstring

,

jclass

,

jarray

,

jthrowable

. Wszystkie one są podtypami

jobject

(jeśli im-

plementujemy w C++, w C są to te same typy strukturalne). Typ

jarray

służy do reprezentowania tablic Javy. Posiada on dodat-

kowe podtypy reprezentujące tablice różnych typów pierwotnych

i obiektowych. Rysunek 5 przedstawia hierarchię typów JNI.

Dostęp do Javy z poziomu C/C++

Jesteśmy już gotowi do zapoznania się z tajnikami JNI. Na po-

ziomie C/C++ mamy dostęp do obiektów Javy i ich zawartości

za pośrednictwem specjalnych funkcji bibliotecznych. Umożli-

wiają one również zgłaszanie wyjątków z poziomu rodzimego

i koordynację wątków. Możemy zatem przetwarzać argumenty

przekazywane do funkcji rodzimych z poziomu Javy jak również

utworzyć nowy obiekt i zwrócić go jako wynik metody rodzimej.

Pierwszym parametrem funkcji rodzimych jest zawsze wskaźnik

do środowiska

JNIEnv * env

. Jest to struktura, w której są zade-

klarowane wszystkie funkcje JNI. Jest ona zdefiniowana w pliku

nagłówkowym jni.h. Poprzez wskaźnik

env

do owej struktury na-

leży wywoływać wszystkie funkcje biblioteczne. Drugim parame-

trem zawsze jest:

• dla metod niestatycznych – odniesienie do obiektu, z któ-

rego wywołano metodę. Jest to odpowiednik

this

z Javy,

w przykładach nazywany

self,

• dla metod statycznych – odniesienie do klasy, z której wywo-

łano metodę, przekazywane na zmiennych typu

jclass

.

Pozostałe parametry odpowiadają parametrom metody rodzi-

mej, którą dana funkcja implementuje. Oczywiście, ich typy są

rodzimymi odpowiednikami typów Javy. Przyjrzyjmy się bliżej

poszczególnym aspektom dostępu do Javy. Najpierw opisze-

my przetwarzanie napisów i tablic, następnie dostęp do skła-

dowych i tworzenie obiektów.

Przetwarzanie napisów

Ze względu na częste stosowanie napisy Javy jako obiek-

ty klasy

String

są reprezentowane po stronie rodzimej przez

zmienne specjalnego typu

jstring

. Nie są to jednak łańcuchy

znakowe w sensie C/C++ (typu

char *

), lecz prawdziwe obiek-

ty Javy (klasy

String

). Aby możliwe było przetwarzanie napi-

su reprezentowanego w ten sposób, konieczne jest uzyskanie

– za pomocą specjalnej funkcji – wskaźnika typu

char *

repre-

zentującego zawartość tego obiektu jako tablicę znaków.

I tu powstaje problem, ponieważ znaki Unicode wchodzące

w skład napisów Javy są 2 bajtowe, podczas gdy typ

char

w C/

C++ jest jednobajtowy. Potrzebne są więc specjalne funkcje kon-

wertujące. Maszyna wirtualna używa kodowania UTF-8 do we-

wnętrznej reprezentacji napisów i w takiej postaci będzie napis

C/C++ uzyskany z obiektu klasy

String

. Do zamiany argumentu

typu

jstring

na rodzimy łańcuch znakowy

char*

służy funkcja

const char* GetStringUTFChars(jstring str, jboolean *isCopy);

Zwraca ona wskaźnik do znaku będący reprezentacją napisu

Javy w systemie UTF-8. Pierwszym parametrem jest łańcuch

przekazany jako argument do metody rodzimej. Jeśli drugi ar-

gument jest różny od

NULL

, to zostanie na nim przekazana in-

formacja o tym, czy została wykonana lokalna kopia napisu

(będzie miał wartość

JNI _ TRUE

), czy też nie – wtedy uzyska-

ny łańcuch jest oryginalnym łańcuchem zawartym w obiekcie

Javy. Z reguły przekazuje się

NULL

. Jeśli napis składa się wy-

łącznie z 7-bitowych znaków ASCII, to pozyskany w ten spo-

sób wskaźnik można już przetwarzać w sposób typowy dla C/

C++. W przeciwnym wypadku należy przekształcić łańcuch do

tablicy bajtów metodą

String.getBytes()

(wywołaną z pozio-

Tabela 2.

Mapowanie typów pierwotnych

Typ Javy

Typ C/C++

Rozmiar w bitach

boolean

jboolean

8, unsigned

byte

jbyte

8

char

jchar

16, unsigned

short

jshort

16

int

jint

32

long

jlong

64

float

jfloat

32

double

jdouble

64

void

void

Dokumentacja i literatura

The Java Native Interface Programmer's Guide and Specification

– Obszerny podręcznik użytkownika, wraz ze specyfikacją zawie-
rającą opis wszystkich funkcji. Jest dostępny w kilku formatach on-
line http://java.sun.com/docs/books/jni/html/jniTOC.html)

Java Native Interface Specification – dostępna on-line pod adre-

sem http://java.sun.com/j2se/1.5.0/docs/guide/jni. Wchodzi rów-
nież w skład pakietu dokumentacji Javy w katalogu docs/guide/jni.

• Książka „Nowe Metody Programowania, Tom 2”, wyd. PJWSTK

2006. Zawiera rozdział poświęcony JNI. Autorzy: Krzysztof Bar-
teczko, Wojciech Drabik, Bartłomiej Starosta.

Tabela 1.

Deskryptory pól typów pierwotnych

Typ Javy

Deskryptor pola

boolean

Z

byte

B

char

C

short

S

int

I

long

J

float

F

double

D

Rysunek 3.

Uruchomienie programu HelloWorld

background image

32

Programowanie

Java

www.sdjournal.org

Software Developer’s Journal 8/2006

mu C/C++) i utworzyć na jej podstawie rodzimą tablicę zna-

ków z tą samą zawartością, którą już można normalnie prze-

twarzać. Metoda

getBytes()

uwzględnia kodowanie obowiązu-

jące w danym systemie, zatem uzyskany w ten sposób łań-

cuch będzie zakodowany np. w iso-8859-2 czy cp-1250.

Niezmiernie ważna kwestią jest zwalnianie tak pozyska-

nego wskaźnika, kiedy nie jest on już potrzebny – najpóźniej

przed zakończeniem wywołania funkcji rodzimej, w której zo-

stał pobrany. Służy do tego funkcja

void ReleaseStringUTFChars(jstring str, const char* chars);

Jej pierwszym argumentem jest łańcuch znakowy, z które-

go pozyskany został napis UTF-8 przekazany jako drugi ar-

gument. Utworzyć łańcuch klasy

String

na podstawie napisu

UTF-8 można funkcją

jstring NewStringUTF(const char *utf);

Wynik jest obiektem Javy zakodowanym w Unicode, który

można zwrócić jako rezultat metody rodzimej. Nie trzeba go

zwalniać, albowiem zajmie się tym odśmiecacz.

Zademonstrujemy przekazywanie napisów z Javy do

C++ i z powrotem. Klasa

JNIString

widoczna na Listingu 5

zawiera metodę

main()

, w której najpierw ładujemy bibliote-

kę dynamiczną libstring.so zawierającą implementację sta-

tycznej metody

stringUse(String s)

, a następnie wywołuje-

my tę metodę przekazując jakiś napis Javy jako argument.

Działanie tej metody, widocznej na Listingu 6, polega na

wypisaniu przekazanego komunikatu, a następnie pobraniu

napisu od użytkownika i zwróceniu go do Javy jako wynik.

W funkcji rodzimej

Java _ JNIString _ stringUse()

po pozy-

skaniu łańcucha

cppString

ze źródłowego napisu

javaString

sprawdzamy, czy nie jest on

NULL

co się może zdarzyć je-

śli JVM nie jest w stanie zaalokować niezbędnej pamięci.

Po wypisaniu komunikatu poprzez

cout

zwalniamy wskaźnik

cppString

. Dalej pobieramy ze standardowego wejścia ciąg

znaków do tablicy i na tej podstawie tworzymy obiekt

ret-

chars

klasy

String

zwracany do środowiska Javy.

Aby uruchomić nasz program stosujemy wcześniej opisaną

procedurę. Najpierw kompilujemy JNIString.java, potem generu-

jemy plik nagłówkowy JNIString.h i kompilujemy JNIString.cpp do

biblioteki dynamicznej. Na koniec ustawiamy zmienne systemo-

we (jeśli trzeba) i uruchamiamy interpreter (Rysunek 6).

Przetwarzanie tablic

Po stronie rodzimej tablice Javy są reprezentowane przez

zmienne typu

jarray

bądź typów pochodnych, odpowiada-

jących tablicom konkretnych typów pierwotnych lub referen-

cyjnych Javy. Tabela 3 pokazuje mapowanie typów tablico-

wych. Tablice obiektów są zawsze odwzorowywane na typ

jobjectArray

(niezależnie od zawartości) i sposób ich prze-

twarzania jest inny, niż w przypadku tablic złożonych z ele-

mentów typów pierwotnych. W szczególności tablice wielo-

wymiarowe – ponieważ w Javie są to tablice obiektów - są

przedstawiane na zmiennych typu

jobjectArray

. Zasady do-

tyczące posługiwania się tablicami są podobne, jak w przy-

Rysunek 5.

Hierarchia typów JNI

�������

���������

�������������

������

���������������

�������

����������������

����������

�������������������

������

����������

����������

�����������

����������

������������

������������

�����������

Listing 3.

Implementacja metody sayHello z klasy

HelloWorld

#

include

<

jni

.

h

>

#

include

<

iostream

>

#

include

"HelloWorld.h"

using

namespace

std

;

JNIEXPORT

void

JNICALL

Java_HelloWorld_sayHello

(

JNIEnv

*

env

,

jobject

self

)

{

cout

<<

"Hello World!"

<<

endl

;

}

Rysunek 4.

Analiza klasy HelloWorld programem javap

background image

33

Java Native Interface – łączenie Javy i C/C++

www.sdjournal.org

Software Developer’s Journal 8/2006

padku napisów. Typowy schemat postępowania obejmuje

następujące kroki:

• pobranie długości tablicy,

• uzyskanie wskaźnika, poprzez który będzie odbywał się

dostęp do jej elementów,

• przetwarzanie elementów tablicy w sposób właściwy dla

języka C/C++, za pośrednictwem pobranego wskaźnika,

• zwolnienie pozyskanego wskaźnika.

Do pobrania liczby elementów tablicy służy funkcja

jsize

GetArrayLength(jarray array);

Za zmienną

array

możemy podsta-

wić dowolną tablicę jednego z typów wymienionych w Tabela 3.

Typ

jsize

to inna nazwa na

jint

czyli

int

. Do posługiwania się

tablicą w C/C++ niezbędny jest wskaźnik do pierwszego elemen-

tu. Dla tablic elementów typów pierwotnych pobieramy go jedną

z funkcji przedstawionych w Ramce. Podobnie jak w przypadku

napisów, wskaźnik reprezentujący tablicę trzeba zwolnić, kiedy

nie jest już potrzebny, aby zwrócić pamięć przez nią zajmowaną.

Po uwolnieniu wskaźnika nie można się do niego więcej odwoły-

wać. Do zwalniana wskaźników służy zestaw funkcji:

void ReleaseTypeArrayElements(jTypearray array,
jtype *elems,
jint mode);

Za

type

wstawiamy nazwę typu pierwotnego Javy, otrzymując

efekt podobny jak w Ramce „Funkcje pobierające wskaźnik do

pierwszego elementu w tablicy”. Parametr

array

jest tablicą,

z której pobrano wskaźnik

elems

. Parametr

mode

określa tryb

zwalniania i może przyjmować następujące wartości (z regu-

ły podaje się

0

):

0

– zapisz zmiany w oryginalnej tablicy i zwolnij wskaźnik,

JNI _ COMMIT

– zapisz zmiany, ale nie zwalniaj wskaźnika,

JNI _ ABORT

– zwolnij wskaźnik nie zapisując zmian.

Jeśli tablica jest duża, to pobieranie wskaźnika do całości

w powyższy sposób może okazać się nieefektywne (mo-

że wymagać kopiowania całej tablicy), zwłaszcza w sytuacji,

gdy chcemy operować tylko na niewielkim jej fragmencie. Na

szczęście mamy do dyspozycji dwa zestawy funkcji umożli-

wiające dostęp do określonych zakresów tablic. Funkcje po-

staci (za

Type

wstawiamy odpowiedni typ pierwotny Javy):

void GetTypeArrayRegion(jTypearray array,
jsize start,
jsize len,
jtype *buf);

kopiują obszar o początku

start

i długości

len

z tablicy

array

do bufora

buf

. Operację odwrotną wykonują funkcje:

void SetTypeArrayRegion(jTypearray array,
jsize start,
jsize len,
jtype *buf);

Kopiują one

len

elementów z bufora

buf

do tablicy

array

po-

cząwszy od jej indeksu

start

. Można w ten sposób skopiować

jeden element jak i całą tablicę.

Tablice obiektów muszą być traktowane w inny sposób,

ponieważ nie wiadomo jaki rozmiar mają obiekty w nich za-

warte (więc nie jest możliwa arytmetyka na wskaźnikach). Za-

tem nie możemy dostać całej tablicy od razu, w jednym wy-

wołaniu. Możemy jedynie dostać się do poszczególnych ko-

mórek. Mamy do dyspozycji dwie funkcje:

jobject GetObjectArrayElement(jobjectArray arr, jsize index)
void SetObjectArrayElement(jobjectArray array,
jsize index, jobject val)

Listing 4.

Utworzenie biblioteki dynamicznej

Linux/GNU gcc

c

++

-

I

/

j2sdk

/

include

-

I

/

j2sdk

/

include

/

linux

-

shared

-

o

libHelloWorld

.

so

HelloWorld

.

cpp

Win32/Borland bcc32

bcc32

-

IC

:

\

j2sdk

\

include

-

IC

:

\

j2sdk

\

include

\

win32

-

tWD

-

eHelloWorld

.

dll

HelloWorld

.

cpp

Win32/Microsoft VC++

cl

/

IC

:

\

j2sdk

\

include

/

IC

:

\

j2sdk

\

include

\

win32

/

GX

/

LD

HelloWorld

.

cpp

/

FeHelloWorld

.

dll

Listing 5.

Klasa JNIString

public

class

JNIString

{

native

static

String

stringUse

(

String

arg

)

;

public

static

void

main

(

String

[]

args

){

System

.

loadLibrary

(

"string"

)

;

String

nativeString

=

stringUse

(

"Podaj napis"

)

;

System

.

out

.

println

(

"Podano napis: "

+

nativeString

)

;

}

}

Tabela 3.

Mapowanie typów tablicowych

Typ JNI

Typ Javy

jbooleanArray

boolean[]

jbyteArray

byte[]

jcharArray

char[]

jshortArray

short[]

jintArray

int[]

jlongAraay

long[]

jfloatArray

float[]

jdoubleArray

double[]

jobjectArray

Object[]

background image

34

Programowanie

Java

www.sdjournal.org

Software Developer’s Journal 8/2006

Pierwsza z nich zwraca element z pozycji

index

na zmiennej typu

jobject

. Druga pozwala zapisać do tablicy

array

element

val

na

pozycji

index

. Tablice wielowymiarowe, które są tablicami obiek-

tów traktujemy podobnie. Najpierw pobieramy element z określo-

nej pozycji jako

jobject

a następnie rzutujemy go na odpowied-

ni typ tablicowy. Poniższa sekwencja pokazuje jak dostać się do

elementu

elem

o współrzędnych

[x][y]

z dwuwymiarowej tablicy

Object[][]

.

jobject array = env->GetObjectArrayElement(object2DArr, x);
jobjectArray row = (jobjectArray)array;
jobject elem = env->GetObjectArrayElement(row, y);

Argument

object2DArr

reprezentuje dwuwymiarową tablicę

obiektów na zmiennej typu

jobjectArray

. Nową tablicę – jako

obiekt Javy – możemy utworzyć jedną z funkcji:

jTypearray NewTypeArray(jsize len);
jobjectArray NewObjectArray(jsize len,
jclass clazz, jobject init);

Pierwsza tworzy tablicę elementów pierwotnych typu

ty-

pe

, druga – tablicę obiektów klasy

clazz

(sposób pozyska-

nia takiej zmiennej opisuje Ramka). Parametr

len

określa roz-

miar tablicy. Trzeci parametr

init

jest obiektem, którym zosta-

ną wypełnione wszystkie komórki tablicy. Przeważnie podaje

się

NULL

– wtedy tablica będzie pusta. Schemat posługiwania

się tablicami zaprezentujemy na przykładzie metody rodzimej,

która oblicza podłogę z pierwiastka dla każdego elementu ta-

blicy (typu

double[]

) przekazanej jako argument i zapisuje wy-

nik w nowej tablicy (typu

int[]

) zwracanej jako rezultat.

Kod klasy

JNIArray

widoczny jest na Listingu 7. W metodzie

main()

tworzymy obiekt tej klasy, wejściową tablicę liczb i prze-

kazujemy ją do metody rodzimej

arrayUse(double[] arg)

, która

zwraca jako wynik tablicę

int[]

. Listing 8 zawiera implementację

w postaci funkcji rodzimej

Java _ JNIArray _ arrayUse()

. Najpierw

pobieramy długość i zawartość tablicy wejściowej

input

. Następ-

nie tworzymy tablicę

retarr

przeznaczoną na wynik. Tablica tym-

czasowa

temparr

jest potrzebna ze względów wydajnościowych:

nie chcemy po każdym obliczeniu wywoływać funkcji

SetIntAr-

rayRegion()

aby umieścić pojedynczy wynik w tablicy

retarr

, lecz

przekopiować wszystkie jednym wywołaniem. Na końcu zwal-

niamy pobrany wskaźnik do tablicy wejściowej i oczywiście tabli-

cę tymczasową. Kompilację, uruchomienie i wynik działania pre-

zentuje Rysunek 7.

Dostęp do atrybutów

Z poziomu C/C++ możemy dostać się do atrybutów obiektów Ja-

vy. Dwa zestawy funkcji JNI pozwalają na operowanie na polach

statycznych i niestatycznych. Aby można było wykonać jakąkol-

wiek operację trzeba najpierw pobrać identyfikator atrybutu re-

prezentowany przez zmienną typu

jfieldID

. Dla atrybutów instan-

cyjnych robimy to funkcją:

jfieldID GetFieldID(jclass clazz,
const char *name,
const char *sig);

Listing 6.

Implementacja rodzimej metody

stringUse(String s)

#

include

<

jni

.

h

>

#

include

<

iostream

>

#

include

"JNIString.h"

using

namespace

std

;

JNIEXPORT

jstring

JNICALL

Java_JNIString_stringUse

(

JNIEnv

*

env

,

jclass

clazz

,

jstring

javaString

) {

const

char

*

cppString

=

env

->

GetStringUTFChars

(

javaString

, 0

)

;

if

(

cppString

==

NULL

)

return

NULL

;

cout

<<

cppString

<<

endl

;

env

->

ReleaseStringUTFChars

(

javaString

,

cppString

)

;

const

int

LEN

=

80

;

char

buf

[

LEN

]

;

cin

.

getline

(

buf

,

LEN

)

;

jstring

retchars

=

env

->

NewStringUTF

(

buf

)

;

return

retchars

;

}

Listing 7.

Klasa JNIArray

public

class

JNIArray

{

native

int

[]

arrayUse

(

double

[]

arg

)

;

static

{

System

.

loadLibrary

(

"array"

)

;

}

public

static

void

main

(

String

[]

args

){

JNIArray

arrDemo

=

new

JNIArray

()

;

double

[]

in

=

new

double

[]{

1, 4.5, 10.01

}

;

int

[]

out

=

arrDemo

.

arrayUse

(

in

)

;

for

(

int

i

=

0

;

i

<

in

.

length

;

i

++

)

System

.

out

.

println

(

"sqrt("

+

in

[

i

]

+

")="

+

out

[

i

])

;

}

}

Rysunek 7.

Uruchomienie programu JNIArray

Rysunek 6.

Uruchomienie programu JNIString

background image

Java Native Interface – łączenie Javy i C/C++

Software Developer’s Journal 8/2006

Natomiast identyfikatory atrybutów statycznych pobiera się

podobną funkcją:

jfieldID GetStaticFieldID(jclass clazz,
const char *name,
const char *sig);

W obu przypadkach znaczenie argumentów jest następujące:

jclass clazz

jest odniesieniem do klasy w której jest wi-

doczny atrybut,

const char * name

jest nazwą atrybutu, pod jaką jest on

zdefiniowany w klasie,

const char * sig

jest deskryptorem pola.

Zapis i odczyt atrybutów instancyjnych wykonują funkcje z rodziny:

jtyp GetTypField(jobject obj, jfieldID fieldID);
void SetTypField(jobject obj, jfieldID fieldID, jtyp val);

Za

Typ

podstawiamy nazwę typu pierwotnego Javy (pisa-

nego dużą literą) lub

Object

a za

jtyp

– jego odpowied-

nik JNI. Argument

jobject obj

jest odniesieniem do obiek-

tu zawierającego dany atrybut,

jfieldID

jest identyfikato-

rem tego pola, a

val

jest nową wartością, która zostanie

mu nadana. Dla atrybutów statycznych przewidziano po-

dobne funkcje:

jtyp GetStaticTypField(jclass clazz, jfieldID fieldID);
void SetStaticTypField(jclass clazz,
jfieldID fieldID,
jtyp value);

Znaczenie argumentów jest podobne z tym, że zamiast odnie-

sienia do obiektu przekazujemy odniesienie do klasy

jclass

clazz

, której dotyczy operacja. Dostęp do atrybutów zilustru-

Listing 8.

Implementacja metody rodzimej arrayUse()

z klasy JNIArray

#

include

<

jni

.

h

>

#

include

<

iostream

>

#

include

<

cmath

>

#

include

"JNIArray.h"

using

namespace

std

;

JNIEXPORT

jintArray

JNICALL

Java_JNIArray_arrayUse

(

JNIEnv

*

env

,

jobject

self

,

jdoubleArray

input

) {

jsize

tlen

=

env

->

GetArrayLength

(

input

)

;

jdouble

*

body

=

env

->

GetDoubleArrayElements

(

input

, 0

)

;

if

(

body

==

NULL

)

return

NULL

;

jintArray

retarr

=

env

->

NewIntArray

(

tlen

)

;

if

(

retarr

==

NULL

)

return

NULL

;

jint

*

temparr

=

new

jint

[

tlen

]

;

for

(

int

i

=

0

;

i

<

tlen

;

i

++

)

temparr

[

i

]

=

(

long

)

floor

(

sqrt

(

body

[

i

]))

;

env

->

SetIntArrayRegion

(

retarr

, 0,

tlen

,

temparr

)

;

env

->

ReleaseDoubleArrayElements

(

input

,

body

, 0

)

;

delete

[]

temparr

;

return

retarr

;

}

Listing 9.

Klasa JNIAttributes

public

class

JNIAttributes

{

boolean

instanceField

=

true

;

static

String

staticField

=

"Java"

;

native

void

fieldAccess

()

;

public

static

void

main

(

String

[]

args

)

{

System

.

loadLibrary

(

"attributes"

)

;

JNIAttributes

attr

=

new

JNIAttributes

()

;

System

.

out

.

println

(

"Przed fieldAccess():"

)

;

System

.

out

.

println

(

"

\t

instanceField = "

+

attr

.

instanceField

)

;

System

.

out

.

println

(

"

\t

staticField = "

+

attr

.

staticField

)

;

attr

.

fieldAccess

()

;

System

.

out

.

println

(

"Po fieldAccess():"

)

;

System

.

out

.

println

(

"

\t

instanceField = "

+

attr

.

instanceField

)

;

System

.

out

.

println

(

"

\t

staticField = "

+

attr

.

staticField

)

;

}

}

Rysunek 8.

Uruchomienie programu JNIAttributes

background image

36

Programowanie

Java

www.sdjournal.org

Software Developer’s Journal 8/2006

Listing 10.

Imeplementacja metody rodzimej fieldAccess() z klasy JNIAttributes

jemy przykładem zawartym w klasie

JNIAttributes

zapre-

zentowanej na Listingu 9. Zawiera ona dwa pola: instan-

cyjne typu

boolean

i statyczne typu

String

. Metoda rodzi-

ma

fieldAccess()

będzie je modyfikować. Implementację tej

metody widzimy na Listingu 10.

Funkcja rodzima

Java _ JNIAttributes _ fieldAccess()

naj-

pierw tworzy identyfikatory pól

instanceField

i

staticField

zadeklarowanych w klasie

JNIAttributes

. Następnie pobie-

ra ich wartości i wypisuje na standardowym wyjściu me-

chanizmami C++. Dalej tworzymy napis "

C++

" (obiekt klasy

String

), który będzie nową wartością atrybutu

staticField

.

Na koniec nadajemy nowe wartości atrybutom i wypisujemy

je. Wynik działania przedstawia Rysunek 8.

Wywoływanie metod i tworzenie obiektów

Wywoływanie metod Javy z poziomu C/C++ sprowadza się

do podobnego schematu, co w przypadku atrybutów. Naj-

pierw pozyskujemy identyfikator metody (typu

jmethodID

),

a następnie używamy odpowiedniej funkcji JNI do wykonania

właściwego wywołania. Ze względu na polimorfizm, wywo-

ływana metoda nie musi być zadeklarowana w klasie obiek-

tu, na rzecz którego jest wołana. Może ona być zadeklarowa-

na w jego nadklasie, a nawet w interfejsie implementowanym

przez jego klasę. Do pozyskania identyfikatora metody uży-

wamy jednej z funkcji:

GetMethodID(jclass clazz, const char *name, const char *sig)
GetStaticMethodID(jclass clazz,
const char *name,
const char *sig)

Obie zwracają wartość typu

jmethodID

. Argumenty mają zna-

czenie podobne jak w przypadku dostępu do składowych:

clazz

jest klasą lub interfejsem, w której metoda będzie

poszukiwana, najczęściej będzie to klasa obiektu, na

rzecz którego wołamy metodę,

name

jest nazwą metody,

sig

jest deskryptorem tej metody.

Wykonanie metody następuje na skutek wywołania jednej

z funkcji JNI:

jtyp CallTypMethod(jobject obj, jmethodID methodID, ...);
jtyp CallStaticTypMethod(jclass clazz, jmethodID methodID,
...);

Wielokropek na końcu listy parametrów oznacza, że funk-

cja pobiera nieokreśloną ich liczbę (podobnie jak

printf()

).

#

include

<

jni

.

h

>

#

include

<

iostream

>

#

include

"JNIAttributes.h"

using

namespace

std

;

JNIEXPORT

void

JNICALL

Java_JNIAttributes_fieldAccess

(

JNIEnv

*

env

,

jobject

self

)

{

// pobieramy odniesienie do klasy tego obiektu

jclass

jniAttrs

=

env

->

GetObjectClass

(

self

)

;

// pobieramy identyfikatory pól

jfieldID

fid

=

env

->

GetFieldID

(

jniAttrs

,

"instanceField"

,

"Z"

)

;

jfieldID

s_fid

=

env

->

GetStaticFieldID

(

jniAttrs

,

"staticField"

,

"Ljava/lang/String;"

)

;

// jeśli błąd to kończymy

if

(

fid

==

NULL

||

s_fid

==

NULL

)

return

;

// pobieramy wartości pól

jboolean

fld_val

=

env

->

GetBooleanField

(

self

,

fid

)

;

jstring

s_fld_val

=

(

jstring

)

env

->

GetStaticObjectField

(

jniAttrs

,

s_fid

)

;

// przekształcamy String na napis UTF-8

const

char

*

cstr

=

env

->

GetStringUTFChars

(

s_fld_val

,

NULL

)

;

if

(

cstr

==

NULL

)

return

;


cout

<<

"W trakcie Java_JNIAttributes_fieldAccess() "

<<

"- wartosci argumentow:

\n

"

<<

"

\t

instanceField = "

<<

(

fld_val

==

JNI_TRUE

?

"true"

:

"false"

)

<<

endl

<<

"

\t

staticField = "

<<

cstr

<<

endl

;

// zwalniamy napis UTF-8

env

->

ReleaseStringUTFChars

(

s_fld_val

,

cstr

)

;

cstr

=

"C++"

;

// tworzymy nowy String na podstawie napisu UTF-8

s_fld_val

=

env

->

NewStringUTF

(

cstr

)

;

if

(

s_fld_val

==

NULL

)

return

;

// nadajemy nowe wartości atrybutom

fld_val

=

JNI_FALSE

;

env

->

SetBooleanField

(

self

,

fid

,

fld_val

)

;

env

->

SetStaticObjectField

(

jniAttrs

,

s_fid

,

s_fld_val

)

;

cout

<<

"W trakcie Java_JNIAttributes_fieldAccess() "

<<

"- wartosci po modyfikacji:

\n

"

<<

"

\t

instanceField = "

<<

(

fld_val

==

JNI_TRUE

?

"true"

:

"false"

)

<<

endl

<<

"

\t

staticField = "

<<

cstr

<<

endl

;

}

Rysunek 9.

Uruchomienie programu JNIMetods

background image

Java Native Interface – łączenie Javy i C/C++

37

www.sdjournal.org

Software Developer’s Journal 8/2006

W to miejsce będą podstawiane argumenty dla wołanej

metody. Podobnie jak w przypadku dostępu do atrybutów,

oprócz identyfikatora metody

methodID

, przekazujemy tym

funkcjom odniesienie do obiektu (

obj

) lub klasy (

clazz

), na

rzecz której metoda będzie wołana. Obie funkcje zwracają

rezultat wywołanej metody. Jeśli jest ona typu

void

, to jako

typ wyniku

jtyp

podajemy

void

, a jako

Typ

(w nazwie funk-

cji) podajemy

Void

. W przeciwnym przypadku podajemy od-

powiednie typy.

Zaprezentujemy teraz rekurencyjną metodę rodzimą –

z poziomu implementacji w C++ będziemy wywoływać ją sa-

mą jako metodę Javy. Kod klasy

JNIMetods

prezentuje Listing

11. Metodę

callMethod(short)

wywołujemy tyle razy, ile wyno-

si argument plus 1.

Implementacja metody

callMethod()

jest zawarta na Li-

stingu 12. Operacja pobrania identyfikatora metody jest

dość kosztowna czasowo, dlatego pozyskany identyfikator

zapamiętujemy na statycznej zmiennej, aby odwoływać się

do niego w przyszłych wywołaniach bez potrzeby ponow-

nego pobierania go. Rysunek 9 prezentuje kompilację i wy-

konanie programu. Identyfikatory metod są potrzebne rów-

nież do tworzenia obiektów. Funkcja

jobject NewObject(jclass clazz, jmethodID methodID, ...);

zwraca nowy obiekt klasy

clazz

. Parametr

methodID

jest

identyfikatorem (metody) konstruktora, którego chcemy

użyć do utworzenia obiektu. Ewentualne argumenty kon-

struktora przekazujemy dalej. Pobierając identyfikator me-

tody dla konstruktora należy podać funkcji

GetMethodID()

ja-

ko nazwę metody napis

"<init>"

, oraz

"V"

jako nazwę typu

zwracanego w deskryptorze metody. Tak utworzone obiek-

ty można oczywiście przekazywać na poziom Javy jako re-

zultaty metod, przypisując do atrybutów obiektów czy też

jako elementy tablic.

C a C++

W zaprezentowanych przykładach używaliśmy języka C++.

Funkcje rodzime można również implementować w czystym

C. Kod napisany w C różni się od kodu C++ dwoma detala-

mi.

Po pierwsze, funkcje biblioteczne JNI w wersji C++ są

funkcjami składowymi struktury

JNIEnv

i są wywoływane po-

przez wskaźnik

env

do niej (przesyłany zawsze jako pierwszy

argument do funkcji rodzimych), na przykład:

env->IsInstanceOf(object, clazz);

W wersji C zmienna typu

JNIEnv

jest wskaźnikiem do innej

struktury zawierającej odpowiednie funkcje biblioteczne (ja-

ko wskaźniki). A więc pierwszy parametr funkcji rodzimych

(typu

JNIEnv*

) jest tu podwójnym wskaźnikiem. Te same

funkcje biblioteczne w C pobierają dodatkowo jako pierw-

szy argument wskaźnik typu

JNIEnv*

(w C++ tę rolę spełnia

this

). Zatem powyższe wywołanie w C ma postać:

(*env)->IsInstanceOf(env, object, clazz);

Druga różnica polega na tym, że w języku C nie ma rela-

cji dziedziczenia pomiędzy typami reprezentującymi obiek-

ty, takimi jak:

jobject

,

jclass

,

jarray

,

jdoubleArray

. W związ-

ku z tym nie jest konieczne rzutowanie zmiennych typu

jo-

bject

na jakiś jego podtyp (na przykład przy przetwarzaniu

tablic wielowymiarowych). Oczywiście, utrudnia to wykrycie

potencjalnych błędów.

Zastosowania

Po co łączyć Javę z C/C++? Oczywiście, efektywność ko-

du napisanego w Javie jest z reguły niższa od jego odpo-

wiednika w C/C++, więc jeśli zależy nam na czasie wyko-

nania, to można krytyczne fragmenty kodu zaimplemento-

wać w C/C++ (ewentualnie poddając optymalizacjom) i od-

woływać się do niego w Javie poprzez metody rodzime. Ale

nie tylko zwiększenie efektywności może być zachętą do

stosowania JNI. Jeśli mamy gotowe i sprawdzone bibliote-

ki funkcji C i nie chcemy przepisywać ich od nowa w Ja-

vie to bardzo dobrym rozwiązaniem jest wykorzystanie ich

za pomocą tej technologii. Kolejnym obszarem zastosowań

są sytuacje, kiedy niezbędnego fragmentu kodu nie da się

napisać w Javie. Mogą to być np. sterowniki urządzeń albo

funkcje specyficzne dla danej platformy. We wszystkich ta-

kich przypadkach piszemy funkcje rodzime odwołujące się

do istniejącego kodu, które następnie wywołujemy w Javie

jako metody rodzime. n

Listing 11.

Klasa JNIMetods

public

class

JNIMethods

{

native

void

callMethod

(

short

calls

)

;

public

static

void

main

(

String

[]

args

)

{

System

.

loadLibrary

(

"methods"

)

;

new

JNIMethods

()

.

callMethod

((

short

)

4

)

;

}

}

Listing 12.

Imeplementacja rodzimej metody callMethod()

z klasy JNIMetods

#

include

<

jni

.

h

>

#

include

<

iostream

>

#

include

"JNIMethods.h"

using

namespace

std

;

JNIEXPORT

void

JNICALL

Java_JNIMethods_callMethod

(

JNIEnv

*

env

,

jobject

self

,

jshort

calls

) {

static

jmethodID

this_method_id

=

NULL

;

jclass

this_cls

=

env

->

GetObjectClass

(

self

)

;

if

(

this_method_id

==

NULL

){

this_method_id

=

env

->

GetMethodID

(

this_cls

,

"callMethod"

,

"(S)V"

)

;

if

(

this_method_id

==

NULL

)

return

;

}

cout

<<

"wywołanie "

<<

calls

<<

endl

;

if

(

calls

>

0

)

env

->

CallVoidMethod

(

self

,

this_method_id

,

calls

-

1

)

;

}


Wyszukiwarka

Podobne podstrony:
Java Programowanie Sieciowe Podstawy Javy id 226331
java nativ interface specificat Nieznany
Java lang Interfaces
OMÓWIENIE INTERFEJSÓW I KLAS ABSTRAKCYJNYCH W JĘZYKU JAVA
07 Java klasy abstrakcyjne, interfejsy, polimorfizm 0
Java 06 Klasy Interfejsy
java 2 object classes interfaces packages annotations
08 Interfejsy Java
Java Media FreamWork
Ćwiczenia 3 Łączenie źródeł napięcia
java 2
7000DELUXE INTERFUNK
Interfejsy

więcej podobnych podstron