[Android -- JNI と NDK] Java と C/C++ 間でパラメータを渡し、値を返す

ここに画像の説明を挿入

この記事では主に、JNI のデータ転送、つまり Java がオブジェクトを C++ に転送する方法と、C++ がデータを Java に必要なオブジェクトにカプセル化する方法を紹介します。

1. 基本的なデータ型

Java の基本型を転送するのは非常に簡単かつ直接的で、たとえば、 Java の基本型jint, jbyte, jshort, jlong, jfloat, jdouble, jchar 和 jbooleanにそれぞれ対応するjxxx のような型がローカル システムに定義されています。int, byte, short, long, float, double, char 和 boolean

Java JNI プログラム: TestJNIPrimitive.java

public class TestJNIPrimitive {
    
    
   static {
    
    
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }

   // Declare a native method average() that receives two ints and return a double containing the average
   private native double average(int n1, int n2);

   // Test Driver
   public static void main(String args[]) {
    
    
      System.out.println("In Java, the average is " + new TestJNIPrimitive().average(3, 2));
   }
}

この JNI プログラムは、 myjni.dll(windows)1 つまたは libmyjni.so(类UNIX)複数のライブラリをロードします。そして、nativeメソッド。このメソッドは 2int種類のパラメータを受け取り、 doubletype。この値はint2 種類の数値の平均です。mianメソッドはaverage関数を。

次に、上記の Java コードを にコンパイルしてTestJNIPrimitive.class

C/C++ ヘッダー ファイル TestJNIPrimitive.h:

> javac TestJNIPrimitive.java
> javah TestJNIPrimitive       // Output is TestJNIPrimitive.h

C 実装: TestJNIPrimitive.c
ヘッダーTestJNIPrimitive.hファイルには関数宣言が含まれています。

JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average(JNIEnv *, jobject, jint, jint);

ここで と がそれぞれ javajintことがわかります(上記) ヘッダー ファイルには、これらのデータ型の定義と、もう 1 つの定義が含まれています。jdoubleintdouble
jni.hwindowswin32/jni_mh.hjsize

// In "win\jni_mh.h" - machine header which is machine dependent
typedef long            jint;
typedef __int64         jlong;
typedef signed char     jbyte;

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

興味深いのは、jintClongタイプ( int16 ビット以上) ではなく、C タイプ (32 ビット以上) に対応していることです。したがって、 C コードのjint代わりに を使用することintが重要です。同時に、CygWin__int64 型はサポートされません。
TestJNIPrimitive.c の実装は次のとおりです

#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("In C, the numbers are %d and %d\n", n1, n2);
  result = ((jdouble)n1 + n2) / 2.0;
  // jint is mapped to int, jdouble is mapped to double
  return result;
}

次に、コードを共有ライブラリにコンパイルします。

// MinGW GCC under Windows
> set JAVA_HOME={jdk-installed-directory}
> gcc -Wl,--add-stdcall-alias -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -shared -o myjni.dll TestJNIPrimitive.c

最後に、次の Java コードを実行します。

java TestJNIPrimitive

C++ は、次のように TestJNIPrimitive.cppコードを実装します。

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

JNIEXPORT jdouble JNICALL Java_TestJNIPrimitive_average
          (JNIEnv *env, jobject obj, jint n1, jint n2) {
    
    
   jdouble result;
   cout << "In C++, the numbers are " << n1 << " and " << n2 << endl;
   result = ((jdouble)n1 + n2) / 2.0;
   // jint is mapped to int, jdouble is mapped to double
   return result;
}

g++ を使用して上記のコードをコンパイルします。

// MinGW GCC under Windows
> g++ -Wl,--add-stdcall-alias -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -shared -o myjni.dll TestJNIPrimitive.cpp

2.文字列を渡します

Java JNI プログラム:TestJNIString.java

public class TestJNIString {
    
    
   static {
    
    
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }
   // Native method that receives a Java String and return a Java String
   private native String sayHello(String msg);

   public static void main(String args[]) {
    
    
      String result = new TestJNIString().sayHello("Hello from Java");
      System.out.println("In Java, the returned string is: " + result);
   }
}

上記のコードは、Java を受け入れてJava を返すnative関数を宣言しておりメソッドはその関数。sayHelloStringstringmainsayHello

次に、上記のコードをコンパイルし、C/C++ ヘッダー ファイルを生成します。

> javac TestJNIString.java
> javah TestJNIString

C コードの実装:
TestJNIString.c 上のヘッダー ファイルは、次のような関数TestJNIString.hを宣言します。

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

JNI は、Java の型に対応するjstringString上記のステートメントの最後のパラメータjstringStringJava コードからのパラメータであり、戻り値もjstringtype。

StringJava はオブジェクトであるのに対し、C は最後にstring配列であるため、文字列を渡すことはプリミティブ型渡すことよりも複雑です。したがって、Javaオブジェクトを C 文字列表現に変換する必要がありますNULLcharStringchar *

前に述べたように、JNI 環境ポインターは、JNIEnv *データ変換を処理するための非常に豊富なインターフェイス関数を定義しています。

  1. const char* GetStringUTFChars(JNIEnv*, jstring, jboolean*)JNI をjstringC に変換する呼び出しchar *
  2. jstring NewStringUTF(JNIEnv*, char*)C をchar *JNI に変換する呼び出しjstring

したがって、C プログラムの基本的なプロセスは次のとおりです。

  1. GetStringUTFChars()関数jstringを使用して変換しますchar *
  2. その後、必要なデータ処理を実行します
  3. NewStringUTF()関数を使用してchar *に変換しjstring、返します
#include <jni.h>
#include <stdio.h>
#include "TestJNIString.h"

JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *env, jobject thisObj, jstring inJNIStr) {
    
    
   // Step 1: Convert the JNI String (jstring) into C-String (char*)
   const char *inCStr = (*env)->GetStringUTFChars(env, inJNIStr, NULL);
   if (NULL == inCSt) return NULL;

   // Step 2: Perform its intended operations
   printf("In C, the received string is: %s\n", inCStr);
   (*env)->ReleaseStringUTFChars(env, inJNIStr, inCStr);  // release resources

   // Prompt user for a C-string
   char outCStr[128];
   printf("Enter a String: ");
   scanf("%s", outCStr);    // not more than 127 characters

   // Step 3: Convert the C-string (char*) into JNI String (jstring) and return
   return (*env)->NewStringUTF(env, outCStr);
}

上記のコードを共有ライブラリにコンパイルします。

// MinGW GCC under Windows
> gcc -Wl,--add-stdcall-alias -I"<JAVA_HOME>\include" -I"<JAVA_HOME>\include\win32" -shared -o myjni.dll TestJNIString.c

最後に、コードを実行します。

> java TestJNIString
In C, the received string is: Hello from Java
Enter a String: test
In Java, the returned string is: test

JNI の文字列変換関数
上記では 2 つの関数を紹介しましたが、ここで JNI が提供する関数を包括的に整理します。JNI は、Unicode (16 ビット文字) と UTF-8 (1 ~ 3 バイトのエンコードを使用) の変換をサポートします。一般に、C/C++ では UTF-8 エンコーディングを使用する必要があります。
JNI システムは、文字列処理用に次の関数を提供します (UTF8 と Unicode の合計 2 つのグループ)。

// UTF-8 String (encoded to 1-3 byte, backward compatible with 7-bit ASCII)
// Can be mapped to null-terminated char-array C-string
const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
   // Returns a pointer to an array of bytes representing the string in modified UTF-8 encoding.
void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf);
   // Informs the VM that the native code no longer needs access to utf.
jstring NewStringUTF(JNIEnv *env, const char *bytes);
   // Constructs a new java.lang.String object from an array of characters in modified UTF-8 encoding.
jsize GetStringUTFLength(JNIEnv *env, jstring string);
   // Returns the length in bytes of the modified UTF-8 representation of a string.
void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize length, char *buf);
   // Translates len number of Unicode characters beginning at offset start into modified UTF-8 encoding 
   // and place the result in the given buffer buf.

// Unicode Strings (16-bit character)
const jchar * GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy);
   // Returns a pointer to the array of Unicode characters
void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *chars);
   // Informs the VM that the native code no longer needs access to chars.
jstring NewString(JNIEnv *env, const jchar *unicodeChars, jsize length);
   // Constructs a new java.lang.String object from an array of Unicode characters.
jsize GetStringLength(JNIEnv *env, jstring string);
   // Returns the length (the count of Unicode characters) of a Java string.
void GetStringRegion(JNIEnv *env, jstring str, jsize start, jsize length, jchar *buf);
   // Copies len number of Unicode characters beginning at offset start to the given buffer buf

GetStringUTFChars()この関数は にjstring変換、システムのコンテンツ割り当てが失敗した場合にchar *この関数が戻ります。NULLしたがって、この関数の戻り値が正しいかどうかを確認することをお勧めしますNULL3 番目のパラメーターはisCopy、このパラメーターがin-outパラメーターであり、渡されるのはポインターであり、関数の終了時にポインターの内容が変更されるということです。内容が「はい」の場合は、返されたデータがデータのコピーでJNI_TRUEあることを意味します。それ以外の場合、「はい」の場合、返された文字列がそのオブジェクト インスタンスを直接指していることを意味します。この場合、ネイティブ コードはコード内のコンテンツを任意に変更しないでください。変更すると Java のコードが変更されるためです。JNI システムは、戻り値が直接参照であることを確認しようとしますが、そうでない場合はコピーを返します。通常、これらの変更を気にすることはほとんどないので、通常はここでパラメータに渡しますjstringJNI_FALSEStringstringstringNULLisCopy

GetStringUTFChars返された文字列が必要なくなった場合は、ReleaseStringUTFChars()必ず関数を呼び出してメモリ リソースを解放する必要があることに注意してください。そうしないとメモリ リークが発生します。そして上位のJavaもGC出来ない!

また、中GetStringUTFCharsではReleaseStringUTFCharsブロックできません!
NewStringUTF()関数はchar *文字列から使用できますjstring

C++ 実装: TestJNIString.cpp

#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) {
    
    
   // Step 1: Convert the JNI String (jstring) into C-String (char*)
   const char *inCStr = env->GetStringUTFChars(inJNIStr, NULL);
   if (NULL == inCStr) return NULL;

   // Step 2: Perform its intended operations
   cout << "In C++, the received string is: " << inCStr << endl;
   env->ReleaseStringUTFChars(inJNIStr, inCStr);  // release resources

   // Prompt user for a C++ string
   string outCppStr;
   cout << "Enter a String: ";
   cin >> outCppStr;

   // Step 3: Convert the C++ string to C-string, then to JNI String (jstring) and return
   return env->NewStringUTF(outCppStr.c_str());
}

上記のコードを g++ でコンパイルします。

// MinGW GCC under Windows
> g++ -Wl,--add-stdcall-alias -I"<JAVA_HOME>\include" -I"<JAVA_HOME>\include\win32" -shared -o myjni.dll TestJNIString.cpp

stringC++ では、ローカル クラスの関数呼び出し構文が異なることに注意してください。C++ では、env->の代わりに を呼び出します(env*)->同時に、このパラメータは C++ 関数では必要ありませんJNIEnv*

3. プリミティブ型の配列を渡す

JNI コード: TestJNIPrimitiveArray.java

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

   // Declare a native method sumAndAverage() that receives an int[] and
   //  return a double[2] array with [0] as sum and [1] as average
   private native double[] sumAndAverage(int[] numbers);

   // Test Driver
   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]);
   }
}

C 言語の実装: TestJNIPrimitiveArray.c
ヘッダー ファイルには、次の関数宣言がTestJNIPrimitiveArray.h含まれています。

JNIEXPORT jdoubleArray JNICALL Java_TestJNIPrimitiveArray_average (JNIEnv *, jobject, jintArray);

Java では、arrayクラスと同様に型を指します。array9 種類の java 、8 種類の基本的な配列、およびオブジェクトの配列があります。JNI は java: の基本型に対応する配列を定義しておりjintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray、オブジェクト指向の配列もありますjobjectArray

同様に、JNI 配列とネイティブ配列の間で変換する必要があります。JNI システムは、次のような一連のインターフェイス関数を提供します。

  1. 使用するとCに変換されjint* GetIntArrayElements(JNIEnv *env, jintArray a, jboolean *iscopy)ますjintarrayjint[]
  2. jintArray NewIntArray(JNIEnv *env, jsize len)関数を使用してlenバイト サイズの領域を割り当て、void SetIntArrayRegion(JNIEnv *env, jintArray a, jsize start, jsize len, const jint *buf)関数を使用してjint[]その領域にデータをコピーしますjintArray

Java の 8 つの基本データ型に対応して、上記と同様の関数のペアが合計 8 組あります。
したがって、ネイティブ プログラムは次のことを行う必要があります。

  1. JavaからJNI配列を受け入れ、ネイティブ配列に変換します
  2. 必要なデータ操作を実行する
  3. 返す必要のあるデータをjni配列に変換して返します。C
    コードの実装は次のとおりですTestJNIPrimitiveArray.c
#include <jni.h>
#include <stdio.h>
#include "TestJNIPrimitiveArray.h"

JNIEXPORT jdoubleArray JNICALL Java_TestJNIPrimitiveArray_sumAndAverage
          (JNIEnv *env, jobject thisObj, jintArray inJNIArray) {
    
    
   // Step 1: Convert the incoming JNI jintarray to C's jint[]
   jint *inCArray = (*env)->GetIntArrayElements(env, inJNIArray, NULL);
   if (NULL == inCArray) return NULL;
   jsize length = (*env)->GetArrayLength(env, inJNIArray);

   // Step 2: Perform its intended operations
   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); // release resources

   jdouble outCArray[] = {
    
    sum, average};

   // Step 3: Convert the C's Native jdouble[] to JNI jdoublearray, and return
   jdoubleArray outJNIArray = (*env)->NewDoubleArray(env, 2);  // allocate
   if (NULL == outJNIArray) return NULL;
   (*env)->SetDoubleArrayRegion(env, outJNIArray, 0 , 2, outCArray);  // copy
   return outJNIArray;
}

JNI 基本型の配列関数
JNI 基本型のarray( jintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray 和 jbooleanArray) 関数は次のとおりです。

// 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);

同様にgetrelease関数と関数の間に always ブロックを置くことはできません。

4. Java オブジェクト変数へのアクセスと Java メソッドのコールバック


Java オブジェクト インスタンスの変数 JNI プログラムにアクセスします。TestJNIInstanceVariable.java

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

   // Instance variables
   private int number = 88;
   private String message = "Hello from Java";

   // Declare a native method that modifies the instance variables
   private native void modifyInstanceVariable();

   // Test Driver   
   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);
   }
}

このクラスには、privateint オブジェクトと String オブジェクトの 2 つのインスタンス変数が含まれています。次に、main でローカル関数を呼び出して、modifyInstanceVariableこれら 2 つの変数を変更します。

C コードの実装:TestJNIInstanceVariable.c

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

JNIEXPORT void JNICALL Java_TestJNIInstanceVariable_modifyInstanceVariable
          (JNIEnv *env, jobject thisObj) {
    
    
   // Get a reference to this object's class
   jclass thisClass = (*env)->GetObjectClass(env, thisObj);

   // int
   // Get the Field ID of the instance variables "number"
   jfieldID fidNumber = (*env)->GetFieldID(env, thisClass, "number", "I");
   if (NULL == fidNumber) return;

   // Get the int given the Field ID
   jint number = (*env)->GetIntField(env, thisObj, fidNumber);
   printf("In C, the int is %d\n", number);

   // Change the variable
   number = 99;
   (*env)->SetIntField(env, thisObj, fidNumber, number);

   // Get the Field ID of the instance variables "message"
   jfieldID fidMessage = (*env)->GetFieldID(env, thisClass, "message", "Ljava/lang/String;");
   if (NULL == fidMessage) return;

   // String
   // Get the object given the Field ID
   jstring message = (*env)->GetObjectField(env, thisObj, fidMessage);

   // Create a C-string with the JNI 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);

   // Create a new C-string and assign to the JNI string
   message = (*env)->NewStringUTF(env, "Hello from C");
   if (NULL == message) return;

   // modify the instance variables
   (*env)->SetObjectField(env, thisObj, fidMessage, message);
}

オブジェクト内の変数にアクセスするには、次のことを行う必要があります。

  1. GetObjectClass()対象オブジェクトのクラス参照を取得する呼び出し
  2. 上記で取得したクラス参照から取得した変数にアクセスするField IDには、変数の名前と変数の記述子 (署名とも呼ばれます) を指定する必要があります。Java クラスの場合、記述子は次の形式になります: "L fully-qualified-name;" (末尾に英語の半角セミコロンがあることに注意してください)、パッケージ名のドットはスラッシュ (/) に置き換えられます。 javaのStirngなど クラス記述子は「Ljava/lang/String;」です。基本型の場合、I は int を表し、B は byte を表し、S は short を表し、J は long を表し、F は float を表し、D は double を表し、C は char を表し、Z は boolean を表します。配列の場合は、左角括弧「[」を使用して、「[Ljava/lang/Object;」はオブジェクト配列を意味し、「[I」は int 型配列を意味します。
  3. 上記で取得した に基づいてField IDGetObjectField()またはGet_primitive-type_Field()関数使用して必要なデータを解析します
  4. SetObjectField()またはSet_primitive-type_Field()関数を使用して変数を変更する

JNI でインスタンス変数にアクセスするために使用される関数は次のとおりです。

jclass GetObjectClass(JNIEnv *env, jobject obj);
   // Returns the class of an object.

jfieldID GetFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig);
  // Returns the field ID for an instance variable of a class.

NativeType Get<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID);
void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, NativeType value);
  // Get/Set the value of an instance variable of an object
  // <type> includes each of the eight primitive types plus Object.

クラス内の静的変数へのアクセス
クラス内の静的変数へのアクセスは、ここで使用する関数が 、 、 であることを除いて、上記の通常のインスタンス変数へのアクセスと似てGetStaticFieldID()Get|SetStaticObjectField()ますGet|SetStatic_Primitive-type_Field()
JNI プログラム:TestJNIStaticVariable.java

public class TestJNIStaticVariable {
    
    
   static {
    
    
      System.loadLibrary("myjni"); // nyjni.dll (Windows) or libmyjni.so (Unixes)
   }

   // Static variables
   private static double number = 55.66;

   // Declare a native method that modifies the static variable
   private native void modifyStaticVariable();

   // Test Driver
   public static void main(String args[]) {
    
    
      TestJNIStaticVariable test = new TestJNIStaticVariable();
      test.modifyStaticVariable();
      System.out.println("In Java, the double is " + number);
   }
}

C言語実装: C実装 -TestJNIStaticVariable.c

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

JNIEXPORT void JNICALL Java_TestJNIStaticVariable_modifyStaticVariable
          (JNIEnv *env, jobject thisObj) {
    
    
   // Get a reference to this object's class
   jclass cls = (*env)->GetObjectClass(env, thisObj);

   // Read the int static variable and modify its value
   jfieldID fidNumber = (*env)->GetStaticFieldID(env, cls, "number", "D");
   if (NULL == fidNumber) return;
   jdouble number = (*env)->GetStaticDoubleField(env, cls, fidNumber);
   printf("In C, the double is %f\n", number);
   number = 77.88;
   (*env)->SetStaticDoubleField(env, cls, fidNumber, number);
}

JNI でクラス内のstatic変数にアクセスするために使用される関数は次のとおりです。

jfieldID GetStaticFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig);
  // Returns the field ID for a static variable of a class.

NativeType GetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID);
void SetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID, NativeType value);
  // Get/Set the value of a static variable of a class.
  // <type> includes each of the eight primitive types plus Object.

インスタンスの通常メソッドおよび静的メソッドのコールバック コード内で Java の通常メソッドまたは静的メソッドをコールバック
できます。native以下は例です:
JNI プログラム:TestJNICallBackMethod.java

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

   // Declare a native method that calls back the Java methods below
   private native void nativeMethod();

   // To be called back by the native code
   private void callback() {
    
    
      System.out.println("In Java");
   }

   private void callback(String message) {
    
    
      System.out.println("In Java with " + message);
   }

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

   // Static method to be called back
   private static String callbackStatic() {
    
    
      return "From static Java method";
   }

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

このクラスではネイティブ関数が宣言されておりnativeMethod()、この関数は main メソッドで呼び出されます。nativeMethod()この関数は、このクラスで定義されたさまざまなメソッドをコールバックします。
C言語の実装:TestJNICallBackMethod.c

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

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

   // Get a class reference for this object
   jclass thisClass = (*env)->GetObjectClass(env, thisObj);

   // Get the Method ID for method "callback", which takes no arg and return void
   jmethodID midCallBack = (*env)->GetMethodID(env, thisClass, "callback", "()V");
   if (NULL == midCallBack) return;
   printf("In C, call back Java's callback()\n");
   // Call back the method (which returns void), baed on the Method ID
   (*env)->CallVoidMethod(env, thisObj, midCallBack);

   jmethodID midCallBackStr = (*env)->GetMethodID(env, thisClass,
                               "callback", "(Ljava/lang/String;)V");
   if (NULL == midCallBackStr) return;
   printf("In C, call back Java's called(String)\n");
   jstring message = (*env)->NewStringUTF(env, "Hello from 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("In C, the average is %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("In C, the returned string is %s\n", resultCStr);
   (*env)->ReleaseStringUTFChars(env, resultJNIStr, resultCStr);
}

インスタンスでメソッドをコールバックできるようにするには、次のものが必要です。

  1. 関数を通じてGetObjectClass()このインスタンスのクラス オブジェクトを取得します
  2. 上記で取得したクラス オブジェクトからGetMethodID()関数を呼び出して、インスタンス内のメソッドの抽象化を表すメソッド ID を取得します。このメソッドの名前と署名情報を指定する必要があります。署名ルールは変数と似ています。署名の形式は次のとおりです(parameters)return-typejni の署名が覚えにくいと本当に感じる場合は、JDK が提供する javap ツールを使用して特定のクラスのすべてのメソッドの署名を取得し、-s オプションを使用して署名を出力し、-p オプションを使用して署名を出力できます。プライベートメンバーを表示します。
> javap --help
> 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;
  .......

上記の出力から、クラス内の各メソッドのシグネチャが明確にわかります。

  1. 上記で取得したメソッド ID に基づいて、または を_Primitive-type_Method()呼び出してこのメ​​ソッドを呼び出すことができます。メソッドにパラメータが必要な場合は、パラメータに従ってください。CallVoidMethod()CallObjectMethod()
  2. 静的メソッドを呼び出したい場合は、GetMethodID(), CallStatic_Primitive-type_Method(), CallStaticVoidMethod()または を使用しますCallStaticObjectMethod()
    JNI のインスタンスおよび静的メソッドをコールバックするために使用されるすべての関数 (共通と静的の 2 つのタイプ):
jmethodID GetMethodID(JNIEnv *env, jclass cls, const char *name, const char *sig);
   // Returns the method ID for an instance method of a class or interface.

NativeType Call<type>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...);
NativeType Call<type>MethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
NativeType Call<type>MethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args);
   // Invoke an instance method of the object.
   // The <type> includes each of the eight primitive and Object.

jmethodID GetStaticMethodID(JNIEnv *env, jclass cls, const char *name, const char *sig);
   // Returns the method ID for an instance method of a class or interface.

NativeType CallStatic<type>Method(JNIEnv *env, jclass clazz, jmethodID methodID, ...);
NativeType CallStatic<type>MethodA(JNIEnv *env, jclass clazz, jmethodID methodID, const jvalue *args);
NativeType CallStatic<type>MethodV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args);
   // Invoke an instance method of the object.
   // The <type> includes each of the eight primitive and Object.

コールバック JNI によってオーバーライドされる親クラス インスタンス メソッドは、親クラス インスタンスのメソッドを呼び出すなどの
一連の関数を提供します。CallNonvirtual_Type_Method()

  1. まずメソッド ID を取得し、使用しますGetMethodID()
  2. 上記で取得したメソッド ID に基づいて、CallNonvirtual_Type_Method()関数を、パラメータobject、親クラス、およびパラメータ リストで指定されます。

親クラスのメソッドにアクセスするために JNI で使用される関数:

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

Object と Object 配列を作成するには
ネイティブ コードで関数呼び出して とjobjectを構築し、Java コードに戻るように指示します。jobjectarrayNewObject()newObjectArray()

Java コンストラクターをコールバックして新しい Java オブジェクトを作成する
コンストラクターをコールバックすることは、他のメソッドをコールバックすることと似ています。まず、それをメソッドinit名として使用し、戻り値として V を使用してメソッド ID を取得し、次にNewObject()関数を使用してビルドします。 Java クラス オブジェクト。

JNI プログラム:TestJavaConstructor.java

public class TestJNIConstructor {
    
    
   static {
    
    
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }

   // Native method that calls back the constructor and return the constructed object.
   // Return an Integer object with the given int.
   private native Integer getIntegerObject(int number);

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

このクラスはgetIntegerObject、int データを受け入れるネイティブ メソッドを宣言し、ネイティブ コードで Integer 型のオブジェクトを作成します。その値はこの値です。

C コードの実装:TestJavaConstructor.c

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

JNIEXPORT jobject JNICALL Java_TestJNIConstructor_getIntegerObject
          (JNIEnv *env, jobject thisObj, jint number) {
    
    
   // Get a class reference for java.lang.Integer
   jclass cls = (*env)->FindClass(env, "java/lang/Integer");

   // Get the Method ID of the constructor which takes an int
   jmethodID midInit = (*env)->GetMethodID(env, cls, "<init>", "(I)V");
   if (NULL == midInit) return NULL;
   // Call back constructor to allocate a new instance, with an int argument
   jobject newObj = (*env)->NewObject(env, cls, midInit, number);

   // Try runnning the toString() on this newly create object
   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;
}

JNI でオブジェクト (jobject) を作成するために使用される関数は次のとおりです。

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

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);
   // Constructs a new Java object. The method ID indicates which constructor method to invoke

jobject AllocObject(JNIEnv *env, jclass cls);
  // Allocates a new Java object without invoking any of the constructors for the object.

オブジェクト (オブジェクト) 配列
JNI プログラム:TestJNIObjectArray.java

import java.util.ArrayList;

public class TestJNIObjectArray {
    
    
   static {
    
    
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }
   // Native method that receives an Integer[] and
   //  returns a Double[2] with [0] as sum and [1] as average
   private native Double[] sumAndAverage(Integer[] numbers);

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

わかりやすくするために、このクラスは、Integer 型の配列を受け入れるネイティブ メソッドを宣言し、ネイティブ コードで配列内の数値の合計と平均を計算し、 の形式で 2 つの数値を返しますDouble array
C コードの実装:TestJNIObjectArray.c

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

JNIEXPORT jobjectArray JNICALL Java_TestJNIObjectArray_sumAndAverage
          (JNIEnv *env, jobject thisObj, jobjectArray inJNIArray) {
    
    
   // Get a class reference for java.lang.Integer
   jclass classInteger = (*env)->FindClass(env, "java/lang/Integer");
   // Use Integer.intValue() to retrieve the int
   jmethodID midIntValue = (*env)->GetMethodID(env, classInteger, "intValue", "()I");
   if (NULL == midIntValue) return NULL;

   // Get the value of each Integer object in the array
   jsize length = (*env)->GetArrayLength(env, inJNIArray);
   jint sum = 0;
   int i;
   for (i = 0; i < length; i++) {
    
    
      jobject objInteger = (*env)->GetObjectArrayElement(env, inJNIArray, i);
      if (NULL == objInteger) return NULL;
      jint value = (*env)->CallIntMethod(env, objInteger, midIntValue);
      sum += value;
   }
   double average = (double)sum / length;
   printf("In C, the sum is %d\n", sum);
   printf("In C, the average is %f\n", average);

   // Get a class reference for java.lang.Double
   jclass classDouble = (*env)->FindClass(env, "java/lang/Double");

   // Allocate a jobjectArray of 2 java.lang.Double
   jobjectArray outJNIArray = (*env)->NewObjectArray(env, 2, classDouble, NULL);

   // Construct 2 Double objects by calling the constructor
   jmethodID midDoubleInit = (*env)->GetMethodID(env, classDouble, "<init>", "(D)V");
   if (NULL == midDoubleInit) return NULL;
   jobject objSum = (*env)->NewObject(env, classDouble, midDoubleInit, (double)sum);
   jobject objAve = (*env)->NewObject(env, classDouble, midDoubleInit, average);
   // Set to the jobjectArray
   (*env)->SetObjectArrayElement(env, outJNIArray, 0, objSum);
   (*env)->SetObjectArrayElement(env, outJNIArray, 1, objAve);

   return outJNIArray;
}

プリミティブ データ型の配列とは異なり、Get|SetObjectArrayElement()関数を使用して各要素を処理する必要があります。
JNI は、array(jobjectArray)オブジェクトを作成するために次の関数を提供します。

jobjectArray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement);
   // Constructs a new array holding objects in class elementClass.
   // All elements are initially set to initialElement.

jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index);
   // Returns an element of an Object array.

void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value);
   // Sets an element of an Object array.

5. ローカル参照とグローバル参照

参照の管理は、効率的なプログラムを作成するための鍵です。たとえば、ローカル コードでは jclass、jmethodID、jfieldID をFindClass(), GetMethodID()よく使用しますGetFieldID()実際、これらの変数は毎回取得するのではなく、初回のみ取得し、その後は直接使用する必要があるため、プログラムの実行効率が向上します。
JNI は、ネイティブ コードのオブジェクト参照をローカル参照とグローバル参照の 2 つのタイプに分類します。

  1. ネイティブ参照はネイティブ コードで作成され、関数が終了するか戻るときに解放されます。有効なスコープはネイティブ関数内のみです。呼び出してDeleteLocalRef()ローカル参照を明示的に無効化し、ガベージ コレクション中に再利用できるようにすることもできます。ローカル関数にパラメータとして渡されるオブジェクト参照はローカル参照であり、JNI 関数から返されるすべての Java オブジェクト (ジョブジェクト) はローカル参照です。
  2. グローバル参照は、プログラマがDeleteGlobalRef()free を呼び出して手動で解放するまで残ります。NewGlobalRef()この関数を使用して、ローカル参照からグローバル参照を作成できます。
    以下に例を示します。
public class TestJNIReference {
    
    
   static {
    
    
      System.loadLibrary("myjni"); // myjni.dll (Windows) or libmyjni.so (Unixes)
   }

   // A native method that returns a java.lang.Integer with the given int.
   private native Integer getIntegerObject(int number);

   // Another native method that also returns a java.lang.Integer with the given 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));
   }
}

上記の JNI プログラムは 2 つのnative関数、どちらもjava.lang.Integerオブジェクトを作成して返します。C コードの実装では、クラス参照を取得し、そこからコンストラクターを見つけて、そのコンストラクターを呼び出すjava.lang.Integer必要があります。method IDただし、取得したclass参照Method IDをキャッシュして、次回使用するときに再度取得する必要がないようにしたいと考えています。
これが私たちの 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("Find java.lang.Integer\n");
      classInteger = (*env)->FindClass(env, "java/lang/Integer");
   }
   if (NULL == classInteger) return NULL;

   // Get the Method ID of the Integer's constructor if missing
   if (NULL == midIntegerInit) {
    
    
      printf("Get Method ID for java.lang.Integer's constructor\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("In C, constructed java.lang.Integer with number %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);
}

上記のプログラムでは、 を呼び出してクラス参照FindClass()を取得し、それをグローバル静的変数に保存します。java.lang.Integerただし、2 回目の呼び出しでは、参照は無効になります (無効ではありませんNULL)。これは、FindClass()ローカル クラス参照が返されるためであり、getInteger関数が返されるとローカル参照は無効になるためです。
これを修正するには、ローカル参照からグローバル参照を作成し、それをグローバルstatic変数に割り当てる必要があります。

// Get a class reference for java.lang.Integer if missing
if (NULL == classInteger) {
    
    
   printf("Find 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);
}

jmethodIDと はjfieldIDではないjobjectため、グローバル参照を作成できないことに注意してください。

おすすめ

転載: blog.csdn.net/duoduo_11011/article/details/131172581