Progmar Marcin Załęczny

3. Podstawy JNI

JNI definuje następujące typy danych w środowisku natywnym, które odpowiadają typom Javy:

  • Typy podstawowe: jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jboolean, które odpowiadają następującym typom Javy: int, byte, short, long, float, double, char and boolean.
  • Typy referencyjne: jobject dla Javowego java.lang.Object, jclass dla java.lang.Class, jstring dla java.lang.String, jthrowable dla java.lang.Throwable oraz jarray dla Javowego array - []. Ponieważ tablica Javowa może przechowywać osiem różnych typów podstawowych (wymienionych w poprzednim punkcie) oraz jeden typ referencyjny - java.lang.Object, więc mamy w JNI następujące typy tablicowe: jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray i jobjectArray.
Wszystkie funkcje natywne pobierają jako argumenty elementy dowolnego z powyższych typów i zwracają wartość, która też jest jednym z powyższych typów (np. jstring, jintArray). Poza tym w funkcjach natywnych możemy tworzyć zmienne dowolnego typu zdefiniowanego w języku natywnym (np. łańcuchy znaków: char* s, czy tablice liczb całkowitych: int a[10], itp.). Ponieważ mamy tu zarówno zmapowane typy Javowe jak i typy natywne, zachodzi więc konieczność konwertowania jednych z tych typów na drugie i odwrotnie.

Poniżej znajduje się schemat działania funkcji natywnej:

  1. Funkcja otrzymuje parametry JNI przekazane przez program Javowy.
  2. Konwertuje albo kopiuje typy referencyjne do lokalnych zmiennych natywnych (np. jstring do char*, jintArray do int[] itd.). Typy proste jak jint czy jdouble nie muszą być konwertowane - można na nich operować tak jak na typach natywnych.
  3. Wykonuje swoje zadanie operując na typach natywnych i/lub na podstawowych typach JNI.
  4. Na koniec tworzy obiekt typu JNI i kopiuje do niego zwracaną wartość.
  5. Zwraca rezultat i kończy działanie.

Najbardziej wyzywającym zadaniem w programowaniu funkcji JNI jest konwersja pomiędzy typami natywnymi a typami referencyjnymi JNI. Do tego celu służy wiele funkcji interfejsu środowiska JNI. JNI jest interfejsem języka C, który nie jest językiem zorientowanym obiektowo i w rzeczywistości nie pozwala na przekazywanie obiektów.

4. Przekazywanie argumentów i zwracanej wartości pomiędzy Javą i programem natywnym

4.1 Przekazywanie typów podstawowych

Przekazywanie typów podstawowych jest oczywiste. W natywnym środowisku zdefiniowany jest typ jxxx, np. jint, jbyte, jshort, jlong, jfloat, jdouble, jchar i jboolean dla każdego typu podstawowego Javy, czyli int, byte, short, long, float, double, char i boolean odpowiednio.

Przykładowy program - TestJNIPrimitive.java:

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

   // Deklaracja natywnej metody average(), która pobiera dwa parametry typu podstawowego ints i zwraca
   // ich średnią w postaci typu podstawowego double
   private native double average(int n1, int n2);

   // Test metody natywnej
   public static void main(String args[]) {
      System.out.println("W Javie, średnia wynosi " + new TestJNIPrimitive().average(3, 2));
   }
}

Powyższy program wywołujący metodę JNI ładuje bibliotekę dynamiczną libmyjni.so. Deklaruje on natywną metodę average, która przyjmuje dwa parametry typu int i zwraca rezultat w postaci typu double. Zwracana wartość jest średnią przekazanych parametrów. Funkcja main tworzy nowy obiekt klasy TestJNIPrimitive, na którym wywołuje metodę natywną average i wypisuje wynik na standardowym wyjściu.

Skompiluj program do pliku TestJNIPrimitive.class i wygeneruj plik nagłówkowy TestJNIPrimitive.h poleceniami: javac TestJNIPrimitive.java
javah TestJNIPrimitive

Implementacja w C - TestJNIPrimitive.c:

Plik nagłówkowy TestJNIPrimitive.h zawiera deklarację funkcji Java_TestJNIPrimitive_average, która przyjmuje jako parametry zmienne typu JNIEnv * (dający dostęp do środowiska interfejsu JNI), jobject (dający dostęp do obiektu this) oraz dwa jint (dwa parametry funkcji natywnej). Wartość zwracana przez funkcję jest typu jdouble: JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average(JNIEnv *, jobject, jint, jint) Typy JNI jint i jdouble odpowiadają typom Javowym int i double odpowiednio.
Definicje typedef określające mapowanie pomiędzy tymi typami znajdują się w plikach nagłówkowych jni.h i linux/jni_mh.h (który zależy od platformy). Plik jni.h zawiera dodatkowo definicję typu jsize.

// jni_md.h
typedef int jint;
#ifdef _LP64 /* 64-bit Solaris */
typedef long jlong;
#else
typedef long long jlong;
#endif

typedef signed char jbyte;


// jni.h
typedef unsigned char   jboolean;
typedef unsigned short  jchar;
typedef short           jshort;
typedef float           jfloat;
typedef double          jdouble;

typedef jint            jsize;

Implementacja pliku TestJNIPrimitive.c wygląda następująco:

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

JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average
          (JNIEnv *env, jobject thisObj, jint n1, jint n2) {
   jdouble result;
   printf("W C, przekazane liczby to: %d i %d\n", n1, n2);
   result = ((jdouble)n1 + n2) / 2.0;
   return result;
}

Skompiluj program w C przy pomocy następujących poleceń: JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
gcc -o libmyjni.so TestJNIPrimitive.c -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -fPIC

Alternatywna implementacja pliku TestJNIPrimitive.cpp w C++ wygląda następująco:

#include <jni.h>
#include <iostream>
#include "TestJNIPrimitive.h"
using namespace std;

JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average
          (JNIEnv *env, jobject thisObj, jint n1, jint n2) {
   jdouble result;
   cout << "W C++, przekazane liczby to: " << n1 << n2 << endl;
   result = ((jdouble)n1 + n2) / 2.0;
   return result;
}

Skompiluj program w C++ przy pomocy następujących poleceń: JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
g++ -o libmyjni.so TestJNIPrimitive.cpp -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -fPIC

4.2 Przekazywanie łańcuchów znaków

Program Javowy: TestJNIString.java

public class TestJNIString {
   static {
      System.loadLibrary("myjni"); // libmyjni.so (Linux)
   }
   // Metoda natywna, która jako paametr pobiera łańcuch znaków i zwraca również łańcuch znaków
   private native String sayHello(String msg);

   public static void main(String args[]) {
      String result = new TestJNIString().sayHello("Witaj świecie - łańcuch pochodzący z programu Java");
      System.out.println("W Javie, zwrócony łańcuch przez program JNI to: " + result);
   }
}

Powyższy program deklaruje metodę natywną sayHello, która pobiera łańcuch znaków i zwraca również łańcuch znaków. Metoda main testuje działanie tej metody natywnej.
Skompiluj program Javy i wygeneruj plik nagłówkowy C/C++ "TestJNIString.h": javac TestJNIString.java
javah TestJNIString

Implementacja w C - TestJNIString.c

Wygenerowany plik nagłówkowy TestJNIString.h zawiera następującą deklarację:

JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *, jobject, jstring);

Jak widzisz, typ Javowy String jest reprezentowany przez typ jstring w programie natywnym.
Przekazywanie łańcuchów znaków jest nieco bardziej skomplikowane niż przekazywanie podstawowych typów danych. Powodem jest to, że typ String w Javie jest obiektem (typem referencyjnym), natomiast łańcuch znaków w C jest tablicą znaków (pojedyńczych bajtów) zakończonych znakiem (bajtem) NULL. Konieczna jest więc konwersja między tymi dwoma typami danych.
Metody konwersji między tymi typami dostarcza środowisko JNIEnv *:

  1. Aby uzyskać łańcuch znaków C (char*) z typu łańcucha znaków JNI (jstring) - wywołujemy funkcję: const char* GetStringUTFChars(JNIEnv*, jstring, jboolean*)
  2. Aby uzyskać łańcuch znaków JNI (jstring) z typu łańcucha znaków C (char*) - wywołujemy funkcję: jstring NewStringUTF(JNIEnv*, char*)

Implementacja TestJNIString.c wygląda zatem następująco:

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

JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *env, jobject thisObj, jstring inJNIStr) {
   // krok 1: Konwersja z JNI String (jstring) do C-String (char*)
   const char *inCStr = (*env)->GetStringUTFChars(env, inJNIStr, NULL);
   if (NULL == inCStr) return NULL;

   // Krok 2: Wykonaj zamierzone operacje
   printf("W C, przekazany w parametrze łańcuch to: %s\n", inCStr);
   (*env)->ReleaseStringUTFChars(env, inJNIStr, inCStr);  // zwolnij zasoby

   // Poproś użytkownika o podanie łańcucha znaków C-string
   char outCStr[128];
   printf("Podaj łańcuch znaków: ");
   scanf("%s", outCStr);    // nie więcej niż 127 znaków

   // Krok 3: Skonwertuj C-string (char*) na JNI String (jstring) i zwróć rezultat
   return (*env)->NewStringUTF(env, outCStr);
}

Jak zaznaczono w komentarzach program wykonuje następujące operacje:

  1. Konwertuje przekazany łańcuch znaków jstring na łańcuch char* przy pomocy funkcji GetStringUTFChars().
  2. Wykonuje właściwe operacje: wyświetla otrzymany łańcuch znaków na standardowym wyjściu i pobiera ze standardowego wejścia dowolny tekst od użytkownika.
  3. Konwertuje podany łańcuch C-string na łańcuch typu jstring przy pomocy funkcji NewStringUTF() i zwraca go jako rezultat funkcji.

Skompiluj program w C przy pomocy następujących poleceń: JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
gcc -o libmyjni.so TestJNIString.c -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -fPIC
Teraz uruchom program poleceniem: > java TestJNIString
W C, przekazany w parametrze łańcuch to: Witaj świecie - łańcuch pochodzący z programu Java
Podaj łańcuch znaków: Witaj!
W Javie, zwrócony łańcuch przez program JNI to: Witaj!

Natywne funkcje JNI operujące na łańcuchach znaków

JNI umożliwia również konwersje dla kodowań łańcuchów Unicode (16-bitowe znaki) oraz UTF-8 (złożone z 1-3 bajtów). Łańcuchy Unicode i UTF-8 w C również są reprezentowane jako tablice znaków (char *), którymi można się posługiwać w C/C++.
Dostępne funkcje operujące na łańcuchach, to:

// Łańcuchy UTF-8 (poszczególne znaki reprezentowane są przez 1-3 bajty, kompatybilne wstecznie z 7-bitowym ASCII).
// Mogą być zmapowane do tablic znaków C-string (char *)

// Zwraca wskaźnik na tablicę znaków w kodowaniu UTF-8.
const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);

// Informuje VM (Java Virtual Machine), że natywny kod nie potrzebuje już łańcucha znaków char* utf.
void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);

// Tworzy nowy obiekt java.lang.String z tablicy znaków (char*) w kodowaniu UTF-8.
jstring NewStringUTF(JNIEnv *env, const char *bytes);

// Zwraca długość w bajtach łańcucha jstring kodowanego w UTF-8.
jsize GetStringUTFLength(JNIEnv *env, jstring string);

// Wypełnia podany bufor (char *buf) podciągiem znaków ciągu str w kodowaniu UTF-8 zaczynając
// od bajtu start mającego długość length bajtów. Poszczególne znaki 2-3 bajtowe
// nie są traktowane jako jeden znak tylko jako 2-3 niezależne bajty.
void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize length, char *buf);



// Łańcuchy Unicode (16-bitowe znaki)

// Zwraca wskaźnik na tablicę znaków w kodowaniu Unicode.
const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy);

// Informuje VM (Java Virtual Machine), że natywny kod nie potrzebuje już łańcucha znaków char* unicode.
void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);

// Tworzy nowy obiekt java.lang.String z tablicy znaków (char*) w kodowaniu Unicode.
jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize length);

// Zwraca długość w bajtach łańcucha jstring kodowanego w Unicode.
jsize GetStringLength(JNIEnv *env, jstring string);

// Wypełnia podany bufor (char *buf) podciągiem znaków ciągu str w kodowaniu Unicode zaczynając
// od bajtu start mającego długość length bajtów. Poszczególne znaki 2-bajtowe
// nie są traktowane jako jeden znak tylko jako 2 niezależne bajty.
void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize length, jchar *buf);

Łańcuchy UTF-8 a łańcuchy C char*

Funkcja GetStringUTFChars() służy do tworzenia nowego łańcucha znaków języka C - char * z danego łańcuch znaków języka Java - jstring. Zwraca ona wartość NULL jeśli jest za mało pamięci, żeby taki łańcuch utworzyć. Dobrą praktyką jest zawsze sprawdzać czy utworzenie łańcucha się powiodło.

Trzeci parametr tej funkcji isCopy (typu jboolean *), który jest parametrem "in-out" (czyli podaje dane na wejściu do funkcji oraz zwraca dane przy wyjściu z funkcji), powinien być ustawiony na JNI_TRUE jeśli zwracany łańcuch ma być nową kopią oryginalnej instancji java.lang.String (jstring). Jeśli natomiast w parametrze tym przekażemy wartość JNI_FALSE, to zwrócony łańcuch znaków będzie bezpośrednim wskaźnikiem na łańcuch przechowywany w parametrze (jstring). W tym przypadku kod natywny nie powinien zmieniać zawartości zwróconego łańccha znaków. Domyślnie środowisko JNI próbuje zwrócić bezpośredni wskaźnik. Jeśli się to nie uda, to próbuje zwrócić kopię przekazanego łańcucha java.lang.String. Zazwyczaj rzadko jesteśmy zainteresowani modyfikowaniem zwróconego łańcucha znaków i dlatego kjako trzeci parametr przekazujemy wartość NULL.

Należy pamiętać, żeby zawsze wywołać funkcję ReleaseStringUTFChars(), gdy już nie potrzebujemy łańcucha zwróconego przez GetStringUTFChars(). Dzięki temu zwalniamy zaalokowaną pamięć lub referencję na oryginalny łańcuch znaków, pozwalając tym samym na usunięcie jej przez garbage-collectora.

Funkcja NewStringUTF() zwraca nowy łańuch JNI (jstring) z podanego łańcucha C-string (char *).

JDK 1.2 wprowadziło funkcję GetStringUTFRegion(), która kopiuje wartość jstring (lub jej fragment o długości length zaczynając od pozycji start) do zadeklarowanej wcześniej tablicy znaków char[]. Funkcja ta może być użyta w miejsce funkcji GetStringUTFChars(). W tym przypadku wartość isCopy nie jest potrzebna, ponieważ łańcuch jest kopiowany do tablicy, która została już wcześniej zaalokowana.

JDK 1.2 wprowadziło także parę funkcji GetStringCritical() oraz ReleaseStringCritical(). Podobnie jak funkcja GetStringUTFChars(), zwraca ona bezpośredni wskaźnik jeśli jest to możliwe lub kopię oryginalnego łańcucha jeśli nie. Natywna metoda nie powinna się blokować (np. w oczekiwaniu na operacje wejścia/wyjścia - I/O) pomiędzy wywołaniami tych dwóch funkcji.

Więcej szczegółów na temat powyższych funkcji można znaleźć w oficjalnej dokumentacji: http://docs.oracle.com/javase/7/docs/technotes/guides/jni/index.html.

Łańcuchy znaków Unicode

Łańcuchy znaków Unicode przechowywane są w zmiennych typu jchar * zamiast char *.

Implementacja TestJNIString.cpp wygląda następująco:

#include <jni.h>
#include <iostream>
#include <string>
#include "TestJNIString.h"
using namespace std;

JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *env, jobject thisObj, jstring inJNIStr) {
   // krok 1: Konwersja z JNI String (jstring) do C-String (char*)
   const char *inCStr = env->GetStringUTFChars(inJNIStr, NULL);
   if (NULL == inCStr) return NULL;

   // Krok 2: Wykonaj zamierzone operacje
   cout << "W C++, przekazany w parametrze łańcuch to: " << inCStr << endl;
   env->ReleaseStringUTFChars(inJNIStr, inCStr);  // zwolnij zasoby

   // Poproś użytkownika o podanie łańcucha znaków C-string
   string outCppStr;
   cout << "Podaj łańcuch znaków: ";
   cin >> outCppStr;

   // Krok 3: Skonwertuj C++ string na C-string (char*) a następnie na JNI String (jstring) i zwróć rezultat
   return env->NewStringUTF(outCppStr.c_str());
}

Skompiluj program poleceniem: g++ -o libmyjni.so TestJNIString.cpp -I"/usr/lib/jvm/java-8-openjdk-amd64/include" -I"/usr/lib/jvm/java-8-openjdk-amd64/include/linux" -shared -fPIC

Zwróć uwagę na to, że funkcje natywne w C++ mają nieco inną składnię niż funkcje w C. W C++ można używać "env->" zamiast "(*env)->". Ponadto w C++ w wywołaniach funkcji nie ma potrzeby przekazywania argumentu JNIEnv* jako pierwszego argumentu tych funkcji.
Ponadto używamy tu klasy string zamiast typu C-string: char*.

4.3 Przekazywanie tablic typów podstawowych

JNI Program - TestJNIPrimitiveArray.java

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

   // Deklaracja natywnej metody sumAndAverage(), która jako argument pobiera tablicę int[] i
   // zwraca tablicę double[2], która w komórce [0] przekazuje sumę a w komórce [1] zwraca średnią
   // liczb przekazanych w parametrze
   private native double[] sumAndAverage(int[] numbers);

   // Test funkcji natywnej
   public static void main(String args[]) {
      int[] numbers = {22, 33, 33};
      double[] results = new TestJNIPrimitiveArray().sumAndAverage(numbers);
      System.out.println("In Java, the sum is " + results[0]);
      System.out.println("In Java, the average is " + results[1]);
   }
}

Implementaja w C - TestJNIPrimitiveArray.c

Plik nagłówkowy TestJNIPrimitiveArray.h zawiera następującą deklarację funkcji: JNIEXPORT jdoubleArray JNICALL Java_TestJNIPrimitiveArray_sumAndAverage(JNIEnv *, jobject, jintArray); W Javie typ tablicowy jest typem referencyjnym, podobnym do klasy. Istnieje w niej 9 typów tablicowych - 8 tablic typów podstawowych oraz jedna tablica typu java.lang.Object (każda stworzona przez nas klasa wywodzi się właśnie od tego typu). JNI definiuje dla każdej z wymienionych tablic oddzielny typ. Zatem mamy tu typy takie jak: jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray oraz jobjectArray (omówiona w dalszej części tego tutorialu).

Ponownie, żeby używać typów tablicowych przekazywanych do i z programu natywnego w C konieczna jest konwersja typów, np. jintArray na C jint[] i odwrotnie, czy jdoubleArray na C jdouble[] i odwrotnie. Środowisko JNI oczywiście zapewnia zestaw funkcji do wykonywania tych konwersji:

  1. Aby otrzymać typ natywny C jint[] z typu natywnego JNI jintArray wywołujemy funkcję jint* GetIntArrayElements(JNIEnv *env, jintArray a, jboolean *iscopy).
  2. Aby otrzymać typ natywny JNI jintArray z typu natywnego C jint[] najpierw wywołujemy funkcję jintArray NewIntArray(JNIEnv *env, jsize len), żeby utworzyć nowy obiekt jintArray a następnie wywołujemy funkcję void SetIntArrayRegion(JNIEnv *env, jintArray a, jsize start, jsize len, const jint *buf), żeby skopiwać elementy z tablicy jint[] do tablicy jintArray.

Istnieje 8 odmian powyższych trzech funkcji, dla każdego z Javowych typów podstawowych.

Tak więc nasz program natywny w C powinien wykonać następujące czynności:

  1. Pobrać przekazaną tablicę jintArray i przekonwertować ją do typu jint[].
  2. Wykonać na tablicy jint[] oczekiwane operacje (wyliczyć sumę i średnią) i wyniki zapisać w tablicy jdouble[].
  3. Przekonwertować natywną tablicę C jdouble[] do typu tablicy JNI jdoubleArray i zwrócić ją jako rezultat funkcji.

Implementacja TestJNIPrimitiveArray.c wygląda następująco:

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

JNIEXPORT jdoubleArray JNICALL Java_TestJNIPrimitiveArray_sumAndAverage
          (JNIEnv *env, jobject thisObj, jintArray inJNIArray) {
   // Krok 1: Konwersja tablicy JNI jintarray do typu C jint[]
   jint *inCArray = (*env)->GetIntArrayElements(env, inJNIArray, NULL);
   if (NULL == inCArray) return NULL;
   jsize length = (*env)->GetArrayLength(env, inJNIArray);

   // Krok 2: wykonanie oczekiwanych operacji
   jint sum = 0;
   int i;
   for (i = 0; i < length; i++) {
      sum += inCArray[i];
   }
   jdouble average = (jdouble)sum / length;
   (*env)->ReleaseIntArrayElements(env, inJNIArray, inCArray, 0); // zwolnienie pamięci

   jdouble outCArray[] = {sum, average};

   // Krok 3: Konwersja tablicy C jdouble[] do typu JNI jdoubleArray
   jdoubleArray outJNIArray = (*env)->NewDoubleArray(env, 2);  // Przydziel pamięć
   if (NULL == outJNIArray) return NULL;
   (*env)->SetDoubleArrayRegion(env, outJNIArray, 0 , 2, outCArray);  // Skopiuj zawartość tablicy jint[]
   return outJNIArray;
}

Poniżej znajduje się zestaw funkcji operujcych na tablicach dla każdego typu podstawowego Javy:

// ArrayType: jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray
// PrimitiveType: int, byte, short, long, float, double, char, boolean
// NativeType: jint, jbyte, jshort, jlong, jfloat, jdouble, jchar, jboolean
NativeType * Get<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, jboolean *isCopy);
void Release<PrimitiveType>ArrayElements(JNIEnv *env, ArrayType array, NativeType *elems, jint mode);
void Get<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize length, NativeType *buffer);
void Set<PrimitiveType>ArrayRegion(JNIEnv *env, ArrayType array, jsize start, jsize length, const NativeType *buffer);
ArrayType New<PrimitiveType>Array(JNIEnv *env, jsize length);
void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy);
void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode);

Funkcje Get<PrimitiveType>ArrayElements i Release<PrimitiveType>ArrayElements służą odpowiednio do konwersji tablicy natywnej Javy na tablicę C oraz do zwolnienia pamięci zajmowanej przez tablicę C.
Funkcje Get<PrimitiveType>ArrayRegion i Set<PrimitiveType>ArrayRegion służą odpowiednio do skopiowania tablicy Javowej JNI do tablicy C oraz odwrotnie.
Funkcja New<PrimitiveType>Array alokuje pamięć dla tablicy Javowej o podanym rozmiarze.
Funkcje GetPrimitiveArrayCritical i ReleasePrimitiveArrayCritical nie pozwalają wywoływać funkcji blokujących (np. na operacje I/O) pomiędzy wywołaniem pierwszej a drugiej.