Progmar Marcin Załęczny

5. Dostęp do atrybutów obiektów i wywoływanie metod obiektów

5.1 Dostęp do atrybutów obiektów

Program JNI - TestJNIInstanceVariable.java:

public class TestJNIInstanceVariable {
   static {
      System.loadLibrary("myjni"); // libmyjni.so
   }

   // Atrybuty obiektu klasy
   private int number = 88;
   private String message = "Hello from Java";

   // Deklaracja metody natywnej, która modyfikuje atrybuty obiektu
   private native void modifyInstanceVariable();

   // Test działania metody natywnej
   public static void main(String args[]) {
      TestJNIInstanceVariable test = new TestJNIInstanceVariable();
      test.modifyInstanceVariable();
      System.out.println("In Java, int is " + test.number);
      System.out.println("In Java, String is " + test.message);
   }
}

Powyższa klasa posiada dwa atrybuty: number typu podstawowego int oraz message typu obiektowego String. Klasa ta deklaruje również metodę natywną modifyInstanceVariable(), która modyfikuje zawartość każdego z tych atrybutów.

Implementacja w C - TestJNIInstanceVariable.c:

#include <jni.h>
#include <stdio.h>
#include "TestJNIInstanceVariable.h"

JNIEXPORT void JNICALL Java_TestJNIInstanceVariable_modifyInstanceVariable
          (JNIEnv *env, jobject thisObj) {
   // Pobierz referencję do klasy obiektu thisObj
   jclass thisClass = (*env)->GetObjectClass(env, thisObj);

   // int
   // Pobierz identyfikator atrybutu "number" w klasie thisClass
   jfieldID fidNumber = (*env)->GetFieldID(env, thisClass, "number", "I");
   if (NULL == fidNumber) return;

   // Pobierz wartość atrybutu "number" w obiekcie thisObj
   jint number = (*env)->GetIntField(env, thisObj, fidNumber);
   printf("In C, the int is %d\n", number);

   // Zmień wartość atrybutu "number"
   number = 99;
   (*env)->SetIntField(env, thisObj, fidNumber, number);

   // Pobierz identyfikator atrybutu "message" w klasie thisClass
   jfieldID fidMessage = (*env)->GetFieldID(env, thisClass, "message", "Ljava/lang/String;");
   if (NULL == fidMessage) return;

   // String
   // Pobierz wartość atrybutu "message" w obiekcie thisObj
   jstring message = (*env)->GetObjectField(env, thisObj, fidMessage);

   // Utwórz łańcuch znaków języka C ze zmiennej  JNI typu String
   const char *cStr = (*env)->GetStringUTFChars(env, message, NULL);
   if (NULL == cStr) return;

   printf("In C, the string is %s\n", cStr);
   (*env)->ReleaseStringUTFChars(env, message, cStr);

   // Przypisz do zmiennej JNI typu String nową wartość określoną przez łańcuch znaków C
   message = (*env)->NewStringUTF(env, "Hello from C");
   if (NULL == message) return;

   // Zmień wartość atrybutu "message"
   (*env)->SetObjectField(env, thisObj, fidMessage, message);
}

Aby uzyskać dostęp do atrybutu obiektu należy:

  1. Pobrać referencję do klasy tego obiektu poprzez wywołanie metody GetObjectClass.
  2. Pobrać identyfikator atrybutu (Field ID) poprzez wywołanie metody GetFieldID referencji klasy. Do metody tej należy przekazać nazwę atrybutu oraz jego deskryptor (sygnaturę). Jeśli atrybut jest typu klasowego, to jako deskryptor podajemy łańcuch znaków "Lpełna/ścieżka/do/klasy" np. "Ljava/lang/String". Dla typów podstawowych stosujemy następujące deskryptory: "I" dla int, "B" dla byte, "S" dla short, "J" dla long, "F" dla float, "D" dla double, "C" dla char, "Z" dla boolean. Dla typów tablicowych stosujemy dodatkowo prefiks określający typ elementów tablicy, np. "[Ljava/lang/Object" dla tablicy obiektów oraz "[I" dla tablicy elementów typu int.
  3. Przekazać otrzymany identyfikator atrybutu do metody GetObjectField() lub Get<typ-podstawowy>Field() aby pobrać wartość tego atrybutu.
  4. Przekazać otrzymany identyfikator atrybutu wraz z nową wartością do metody SetObjectField() lub Set<typ-podstawowy>Field() aby zmienić wartość tego atrybutu.

Podsumowując, metodami pozwalającymi na dostęp do atrybutów obiektów klas są:

// Zwraca klasę dla poddanego obiektu
jclass GetObjectClass(JNIEnv *env, jobject obj);

// Zwraca identyfikator (field ID) dla podanego atrybutu obiektu klasy
jfieldID GetFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig);

// Pobiera/Ustawia wartość atrybutu obiektu
// <typ> reprezentuje nazwę każdego z ośmiu typów podstawowych plus typ obiektowy
TypNatywny Get<typ>Field(JNIEnv *env, jobject obj, jfieldID fieldID);
void Set<typ>Field(JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value);

5.2 Dostęp do statycznych zmiennych klas

Uzyskiwanie dostępu do statycznych zmiennych klas jest podobne do uzyskiwania dostępu do atrybutów obiektów z tą różnicą, że tutaj wykorzystujemy GetStaticFieldID(), Get|SetStaticObjectField(), Get|SetStatic<Typ-Podstawowy>Field().

Program JNI - TestJNIStaticVariable.java:

public class TestJNIStaticVariable {
   static {
      System.loadLibrary("myjni"); // libmyjni.so
   }

   // Zmienna statyczna
   private static double number = 55.66;

   // Deklaracja metody natywnej, która modyfikuje zmienną statyczną klasy
   private native void modifyStaticVariable();

   // Test metody natywnej
   public static void main(String args[]) {
      TestJNIStaticVariable test = new TestJNIStaticVariable();
      test.modifyStaticVariable();
      System.out.println("W Javie, wartość typu double wynosi: " + number);
   }
}

Implementacja w C - TestJNIStaticVariable.c:

#include <jni.h>
#include <stdio.h>
#include "TestJNIStaticVariable.h"

JNIEXPORT void JNICALL Java_TestJNIStaticVariable_modifyStaticVariable
          (JNIEnv *env, jobject thisObj) {
   // Pobierz referencję do klasy obiektu thisObj
   jclass cls = (*env)->GetObjectClass(env, thisObj);

   // Pobierz identyfikator zmiennej statycznej number
   jfieldID fidNumber = (*env)->GetStaticFieldID(env, cls, "number", "D");
   if (NULL == fidNumber) return;
   // Odczytaj wartość zmiennej statycznej number
   jdouble number = (*env)->GetStaticDoubleField(env, cls, fidNumber);
   printf("W kodzie C, wartość zmiennej typu double wynosi %f\n", number);
   number = 77.88;
   // Ustaw nową wartość zmiennej statycznej number
   (*env)->SetStaticDoubleField(env, cls, fidNumber, number);
}

Metodami pozwalającymi na dostęp do zmiennych statycznych klas są:

// Zwraca identyfikator (field ID) zmiennej statycznej klasy.
jfieldID GetStaticFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig);

// Pobiera/Ustawia wartość zmiennej statycznej klasy
// <typ> reprezentuje nazwę każdego z ośmiu typów podstawowych plus typ obiektowy
NativeType GetStatic<typ>Field(JNIEnv *env, jclass cls, jfieldID fieldID);
void SetStatic<typ>Field(JNIEnv *env, jclass cls, jfieldID fieldID, NativeType value);

5.3 Wywoływanie metod obiektów i statycznych metod klas

Program JNI - TestJNICallBackMethod.java:

public class TestJNICallBackMethod {
   static {
      System.loadLibrary("myjni"); // libmyjni.so
   }

   // Deklaracja metody natywnej, która wywołuje poniższe metody Javy
   private native void nativeMethod();

   // Metody wywoływane przez kod natywny
   private void callback() {
      System.out.println("W Javie");
   }

   private void callback(String message) {
      System.out.println("W Javie z parametrem message: " + message);
   }

   private double callbackAverage(int n1, int n2) {
      return ((double)n1 + n2) / 2.0;
   }

   // Statyczna metoda wywoływana przez kod natywny
   private static String callbackStatic() {
      return "Łańcuch znaków zwrócony przez metodę Javy";
   }

   // Test metody natywnej
   public static void main(String args[]) {
      new TestJNICallBackMethod().nativeMethod();
   }
}

Klasa ta deklaruje natywną metodę nativeMethod() i wywołuje ją w metodzie main. Z kolei metoda nativeMethod() wywołuje metody instancji: callback(), callback(String), callbackAverage(int,int) oraz metodę statyczną klasy: callbackStatic().

Implementacja w C - TestJNICallBackMethod.c:


#include <jni.h>
#include <stdio.h>
#include "TestJNICallBackMethod.h"

JNIEXPORT void JNICALL Java_TestJNICallBackMethod_nativeMethod
          (JNIEnv *env, jobject thisObj) {

   // Pobierz referencję do klasy obiektu thisObj
   jclass thisClass = (*env)->GetObjectClass(env, thisObj);

   // Pobierz identyfikator (Method ID) metody "callback", która nie pobiera żadnych argumentów i nic nie zwraca
   jmethodID midCallBack = (*env)->GetMethodID(env, thisClass, "callback", "()V");
   if (NULL == midCallBack) return;
   printf("W C, wywołanie Javowej metody callback()\n");
   // Wywołaj metodę "callback"
   (*env)->CallVoidMethod(env, thisObj, midCallBack);

   jmethodID midCallBackStr = (*env)->GetMethodID(env, thisClass, "callback", "(Ljava/lang/String;)V");
   if (NULL == midCallBackStr) return;
   printf("W C, wywołanie Javowej metody callback(String)\n");
   jstring message = (*env)->NewStringUTF(env, "Witaj w C");
   (*env)->CallVoidMethod(env, thisObj, midCallBackStr, message);

   jmethodID midCallBackAverage = (*env)->GetMethodID(env, thisClass, "callbackAverage", "(II)D");
   if (NULL == midCallBackAverage) return;
   jdouble average = (*env)->CallDoubleMethod(env, thisObj, midCallBackAverage, 2, 3);
   printf("W C, średnia wynosi %f\n", average);

   jmethodID midCallBackStatic = (*env)->GetStaticMethodID(env, thisClass, "callbackStatic", "()Ljava/lang/String;");
   if (NULL == midCallBackStatic) return;
   jstring resultJNIStr = (*env)->CallStaticObjectMethod(env, thisClass, midCallBackStatic);
   const char *resultCStr = (*env)->GetStringUTFChars(env, resultJNIStr, NULL);
   if (NULL == resultCStr) return;
   printf("W C, zwrócony łańcuch znaków to: %s\n", resultCStr);
   (*env)->ReleaseStringUTFChars(env, resultJNIStr, resultCStr);
}

Aby wywołać metodę instancji w kodzie natywnym, należy:

  1. Pobrać referencję do klasy obiektu poprzez wywołanie metody GetObjectClass.
  2. Pobrać identyfikator metody (Method ID) poprzez wywołanie GetMethodID(), przekazując mu nazwę metody oraz jej sygnaturę. Sygnatura jest postaci: "(parametry)zwracana-wartość". Aby wyświetlić sygnatury metod można użyć programu javap (disasembler plików *.class) z parametrami -s (wydrukuj sygnaturę) oraz -p (pokaż prywatne metody), np:

    javap -s -p TestJNICallBackMethod
      .......
      private void callback();
        Signature: ()V
    
      private void callback(java.lang.String);
        Signature: (Ljava/lang/String;)V
    
      private double callbackAverage(int, int);
        Signature: (II)D
    
      private static java.lang.String callbackStatic();
        Signature: ()Ljava/lang/String;
      .......
  3. Użyć jednej z funkcji Call<typ-podstawowy>Method(), CallVoidMethod(), CallObjectMethod() przekazując jej pobrany identyfikator metody oraz opcjonalne parametry. Funkcja ta wywoła naszą metodę i zwróci zwróconą przez nią wartość (jeśli nie jest to void).
    Aby wywołać metodę statyczną, używamy następujących funkcji: GetStaticMethodID() CallStatic<typ-podstawowy>Method(), CallStaticVoidMethod() lub CallStaticObjectMethod().

Poniżej umieszczone są wszystkie funkcje, których używamy do wywoływania metod instancyjnych oraz metod statycznych:

    // Zwraca identyfikator metody instancyjnej klasy lub interfejsu
    jmethodID GetMethodID(JNIEnv *env, jclass cls, const char *name, const char *sig);

    // Wywołuje metodę obiektu (instancji).
    // The <typ> to jeden z typów podstawowych lub typ obiektowy (Object).
    NativeType Call<typ>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...);
    NativeType Call<typ>MethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
    NativeType Call<typ>MethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args);

    // Zwraca identyfikator metody statycznej klasy lub interfejsu
    jmethodID GetStaticMethodID(JNIEnv *env, jclass cls, const char *name, const char *sig);

    // Wywołuje metodę statyczną klasy
    // <typ> to jeden z typów podstawowych lub typ obiektowy (Object).
    NativeType CallStatic<typ>Method(JNIEnv *env, jclass cls, jmethodID methodID, ...);
    NativeType CallStatic<typ>MethodA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args);
    NativeType CallStatic<typ>MethodV(JNIEnv *env, jclass cls, jmethodID methodID, va_list args);
    

5.4 Wywoływanie wirtualnych metod instancji zdefiniowanych w klasie, po której odziedziczono

JNI udostępnia zestaw funkcji CallNonvirtual<Typ>Method() służących do wywoływania metod wirtualnych zdefiniowanych w klasie z której odziedziczono (coś w stylu super.methodName()). Aby to zrobić, należy:

  1. Pobrać identyfikator metody wywołaniem GetMethodID().
  2. Wywołać jedną z funkcji CallNonvirtual<Typ>Method() przekazując jej pobrany identyfikator, obiekt, klasę nadrzędną (której metodę wirtualną chcemy wywołać) oraz opcjonalnie argumenty.

Zestaw funkcji Nonwirtual zaprezentowano poniżej:


NativeType CallNonvirtual<typ>Method(JNIEnv *env, jobject obj, jclass cls, jmethodID methodID, ...);
NativeType CallNonvirtual<typ>MethodA(JNIEnv *env, jobject obj, jclass cls, jmethodID methodID, const jvalue *args);
NativeType CallNonvirtual<typ>MethodV(JNIEnv *env, jobject obj, jclass cls, jmethodID methodID, va_list args);

6. Tworzenie obiektów i tablic obiektów

Możesz utworzyć nowe zmienne typów jobject i jobjectArray w kodzie natywnym stosując wywołania funkcji NewObject() i NewObjectArray(). Następnie utworzone tak zmienne możesz przekazać z powrotem do programu w Javie.

6.1 Wywołanie konstruktora w celu utworzenia obiektu w kodzie natywnym

Wywołanie konstruktora w kodzie natywnym jest podobne do wywołania dowolnej metody. Najpierw pobieramy identyfikator konstruktora przekazując przekazując łańcuch "<init>" jako nazwę metody oraz "V" jako zwracaną wartość. Następnie możesz wywołać jedną z metod postaci NewObject(), żeby wywołać konstruktor i utworzyć nowy obiekt Javy.

Program JNI - TestJavaConstructor.java:

public class TestJavaConstructor {
   static {
      System.loadLibrary("myjni"); // libmyjni.so
   }

   // Natywna metoda, która wywołuje konstrutor klasy Integer i zwraca utworzony obekt.
   // Zwraca utworzony obiekt typu Integer, który przechowuje podaną wartość number.
   private native Integer getIntegerObject(int number);

   public static void main(String args[]) {
      TestJavaConstructor test = new TestJavaConstructor();
      System.out.println("In Java, the number is :" + test.getIntegerObject(9999));
   }
}

Powyższa klasa deklaruje metodę natywną getIntegerObject(). Kod natywny tej metody tworzy na podstawie przekazanego argumentu number obiekt klasy Integer i zwraca go do programu w Javie.

Implementacja w C - TestJavaConstructor.c:

#include <jni.h>
#include <stdio.h>
#include "TestJavaConstructor.h"

JNIEXPORT jobject JNICALL Java_TestJavaConstructor_getIntegerObject
          (JNIEnv *env, jobject thisObj, jint number) {
   // Pobierz referencję do klasy java.lang.Integer
   jclass cls = (*env)->FindClass(env, "java/lang/Integer");

   // Pobierz identyfikator konstruktora w powyższej klasie, który przyjmuje wartość int
   jmethodID midInit = (*env)->GetMethodID(env, cls, "<init>", "(I)V");
   if (NULL == midInit) return NULL;
   // Wywołaj konstruktor, który utworzy instancję obiektu powyższej klasy
   jobject newObj = (*env)->NewObject(env, cls, midInit, number);

   // Pobierz identyfikator metody toString a następnie go wywołaj w celu przetestowania
   // poprawności utworzonego obiektu
   jmethodID midToString = (*env)->GetMethodID(env, cls, "toString", "()Ljava/lang/String;");
   if (NULL == midToString) return NULL;
   jstring resultStr = (*env)->CallObjectMethod(env, newObj, midToString);
   const char *resultCStr = (*env)->GetStringUTFChars(env, resultStr, NULL);
   printf("In C: the number is %s\n", resultCStr);

   return newObj;
}

Funkcje JNI wykorzystywane w procesie tworzenia obiektów Javy znajdują się na poniższej liście:

jclass FindClass(JNIEnv *env, const char *name);

// Tworzy nowy obiect Javy. Argument methodID określa, który konstruktor klasy cls wywołać
jobject NewObject(JNIEnv *env, jclass cls, jmethodID methodID, ...);
jobject NewObjectA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args);
jobject NewObjectV(JNIEnv *env, jclass cls, jmethodID methodID, va_list args);

// Alokuje pamięć na nowy obiekt Javy bez wywoływania żadnego konstruktora
jobject AllocObject(JNIEnv *env, jclass cls);

6.2 Tablice obiektów

Program JNI - TestJNIObjectArray.java:

public class TestJNIObjectArray {
   static {
      System.loadLibrary("myjni"); // libmyjni.so
   }
   // Metoda natywna, która pobiera tablicę Integer[] i zwraca
   // tablicę Double[2], której element [0] przechowuje sumę a [1] średnią podanych argumentów
   private native Double[] sumAndAverage(Integer[] numbers);

   public static void main(String args[]) {
      Integer[] numbers = {11, 22, 32};  // tablica elementów testowych
      Double[] results = new TestJNIObjectArray().sumAndAverage(numbers);
      System.out.println("In Java, the sum is " + results[0]);
      System.out.println("In Java, the average is " + results[1]);
   }
}

Powyższy kod deklaruje metodę natywną, która pobiera tablicę obiektów typu Integer, wylicza ich sumę i średnią a następnie zwraca wyliczone wartości w tablicy obiektów typu Double. Na uwagę zasługuje fakt, że metoda ta zarówno pobiera tablicę obiektów jak i zwraca tablicę obiektów.

Implementacja w C - TestJNIObjectArray.c:

#include <jni.h>
#include <stdio.h>
#include "TestJNIObjectArray.h"

JNIEXPORT jobjectArray JNICALL Java_TestJNIObjectArray_sumAndAverage
          (JNIEnv *env, jobject thisObj, jobjectArray inJNIArray) {
   // Pobierz referencję do klasy java.lang.Integer
   jclass classInteger = (*env)->FindClass(env, "java/lang/Integer");
   // Pobierz identyfikator metody Integer.intValue() zwracającej wartość int obiektu klasy Integer
   jmethodID midIntValue = (*env)->GetMethodID(env, classInteger, "intValue", "()I");
   if (NULL == midIntValue) return NULL;

   //Pobierz ilość elementów tablicy przekazanej w parametrze
   jsize length = (*env)->GetArrayLength(env, inJNIArray);
   jint sum = 0;
   int i;
   for (i = 0; i < length; i++) {
       // Pobierz i-ty obiekt z tablicy
       jobject objInteger = (*env)->GetObjectArrayElement(env, inJNIArray, i);
       if (NULL == objInteger) return NULL;
       // Wywołaj metodę intValue na pobranym obiekcie
       jint value = (*env)->CallIntMethod(env, objInteger, midIntValue);
       // zaktualizuj sumę
       sum += value;
   }
   // oblicz średnią
   double average = (double)sum / length;
   printf("In C, the sum is %d\n", sum);
   printf("In C, the average is %f\n", average);

   //Pobierz referencję na klasę java.lang.Double
   jclass classDouble = (*env)->FindClass(env, "java/lang/Double");

   // Utwórz tablicę 2 elementów klasy java.lang.Double
   jobjectArray outJNIArray = (*env)->NewObjectArray(env, 2, classDouble, NULL);

   // Pobierz identyfikator konstruktora klasy Double
   jmethodID midDoubleInit = (*env)->GetMethodID(env, classDouble, "<init>", "(D)V");
   if (NULL == midDoubleInit) return NULL;
   // Utwórz dwa obiekty klasy Double przechowujące sumę i średnią odpowiednio
   jobject objSum = (*env)->NewObject(env, classDouble, midDoubleInit, (double)sum);
   jobject objAve = (*env)->NewObject(env, classDouble, midDoubleInit, average);
   // Ustaw elementy tablicy
   (*env)->SetObjectArrayElement(env, outJNIArray, 0, objSum);
   (*env)->SetObjectArrayElement(env, outJNIArray, 1, objAve);

   // i zwróć tablicę do programu Javy
   return outJNIArray;
}

W przeciwieństwie do tablic typów podstawowych, które mogą być pobierane i/lub ustawiane bezpośrednio, dostęp do elementów tablic obiektowych wymaga użycia funkcji Get|SetObjectArrayElement().

Funkcje potrzebne do przetwarzania tablic obiektów wymieniono poniżej:

// Tworzy nową tablicę przechowującą length elementów klasy elementClass.
// Wszystkie elementy są zainicjalizowane domyślnie wartością initialElement.
jobjectArray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);

// Pobiera element klasy array znajdujący się na pozycji index
jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index);

// Przypisuje elementowi klasy array znajdującemu się na pozycji index
// wartość value
void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);

7. Lokalne i globalne referencje

Zarządzanie referencjami ma kluczowe znaczenie przy pisaniu wydajnego kodu natywnego. Na przykład często używamy funkcji FindClass(), GetMethodID(), GetFieldID(), aby uzyskać odpowiednio referencję do klasy jclass, identyfikator metody jmethodID oraz identyfiaktor atrybutu jfieldID w kodzie natywnym. Zamiast wywoływać powyższe metody wiele razy, za każdym razem kiedy są potrzebne, powinniśmy wywołać je raz i skeszować zwrócone przez nie wyniki w celu poprawienia wydajności programu.

JNI dzieli referencje na obiekty/klasy, wykorzystywane w kodzie natywnym, na referencje lokalne i globalne:

  1. Lokalna referencja jest utworzona wewnątrz metody natywnej i zwolniona przy wyjściu z tej metody. Możesz także bezpośredniop wywołać funkcję JNI DeleteLocalRef(), aby zwolnić podaną referencję i poinstruować garbage collector'a, że może zwolnić zajmowane przez nią zasoby. Obiekty są przekazywane do funkcji natywnych jako referencje lokalne. Wszystkie obiekty (jobject) zwracane przez funkcje natywne są także referencjami lokalnymi.
  2. Globalne referencje są dostępne w programie natywnym między jego kolejnymi wywołaniami i trwają do momentu, gdy programista jawnie wywoła funkcję DeleteGlobalRef() zwalniającą daną referencjęglobalną i instruującą garbage collector'a, że może zwolnić zajmowane przez nią zasoby. Globalną referencję można utworzyć z referencji lokalnej za pomocą wywołania funkcji NewGlobalRef().

Przykładowy program JNI - TestJNIReference.java:

public class TestJNIReference {
   static {
      System.loadLibrary("myjni"); // libmyjni.so
   }

   // Metoda natywna zwracająca obiekt java.lang.Integer przechowujący podaną wartość int
   private native Integer getIntegerObject(int number);

   // Inna metoda natywna zwracająca obiekt java.lang.Integer przechowujący podaną wartość int
   private native Integer anotherGetIntegerObject(int number);

   public static void main(String args[]) {
      TestJNIReference test = new TestJNIReference();
      System.out.println(test.getIntegerObject(1));
      System.out.println(test.getIntegerObject(2));
      System.out.println(test.anotherGetIntegerObject(11));
      System.out.println(test.anotherGetIntegerObject(12));
      System.out.println(test.getIntegerObject(3));
      System.out.println(test.anotherGetIntegerObject(13));
   }
}

Powyższy kod deklaruje dwie metody natywne, które pobierają wartość typu int i tworzą przechowujący ją obiekt typu java.lang.Integer.

W implementacji w C potrzebujemy pobrać referencję na klasę java.lang.Integer przy pomocy funkcji FindClass(). Następnie musimy pobrać identyfikator konstruktora tej klasy, żeby móc utworzyć odpowiednią zmienną obiektową. Zamiast robić to za każdym razem, gdy wywoływana jest dowolna z dwóch powyższych metod natywnych możemy to zrobić raz i przechować otrzymane wartości w referencjach globalnych. Następnie za każdym wywołaniem tych metod natywnych pobieramy odpowiednie wartości z tych globalnych referencji, przyspieszając działanie programu.

Implementacja w C - TestJNIReference.c:

#include <jni.h>
#include <stdio.h>
#include "TestJNIReference.h"

// Global Reference to the Java class "java.lang.Integer"
static jclass classInteger;
static jmethodID midIntegerInit;

jobject getInteger(JNIEnv *env, jobject thisObj, jint number) {

   // Get a class reference for java.lang.Integer if missing
   if (NULL == classInteger) {
       printf("Uzyskanie reerencji na klasę java.lang.Integer\n");
       // FindClass returns a local reference
       jclass classIntegerLocal = (*env)->FindClass(env, "java/lang/Integer");
       // Create a global reference from the local reference
       classInteger = (*env)->NewGlobalRef(env, classIntegerLocal);
       // No longer need the local reference, free it!
       (*env)->DeleteLocalRef(env, classIntegerLocal);
   }
   if (NULL == classInteger) return NULL;

   // Get the Method ID of the Integer's constructor if missing
   if (NULL == midIntegerInit) {
      printf("Pobierz identyfikator konstruktora klasy java.lang.Integer's\n");
      midIntegerInit = (*env)->GetMethodID(env, classInteger, "<init>", "(I)V");
   }
   if (NULL == midIntegerInit) return NULL;

   // Call back constructor to allocate a new instance, with an int argument
   jobject newObj = (*env)->NewObject(env, classInteger, midIntegerInit, number);
   printf("W C, utworzono obiekt klasy java.lang.Integer zawierający wartość %d\n", number);
   return newObj;
}

JNIEXPORT jobject JNICALL Java_TestJNIReference_getIntegerObject
          (JNIEnv *env, jobject thisObj, jint number) {
   return getInteger(env, thisObj, number);
}

JNIEXPORT jobject JNICALL Java_TestJNIReference_anotherGetIntegerObject
          (JNIEnv *env, jobject thisObj, jint number) {
   return getInteger(env, thisObj, number);
}

W powyższym programie wywołujemy funkcję FindClass(), żeby uzyskać lokalną referencję na klasę java.lang.Integer. Następnie tak otrzymaną wartość konwertujemy przy pomocy funkcji NewGlobalRef() na referencję globalną i zapisujemy zwróconą przez nią wartość w statycznej zmiennej globalnej. Po tej operacji możemy zwolnić referencję lokalną przy pomocy funkcji DeleteLocalRef, gdyż nie jest ona nam już potrzebna.

Należy tu zwrócić uwagę na to, że zmienne jmethodID i jfieldID nie są obiektami jobject, tylko zwykłymi strukturami języka C i nie można z nich tworzyć referencji globalnych. Można je bezpośrednio zachować w statycznych zmiennych globalnych.