この記事では主に、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
種類のパラメータを受け取り、 double
type。この値はint
2 種類の数値の平均です。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);
ここで と がそれぞれ javaでjint
ことがわかります。(上記) ヘッダー ファイルには、これらのデータ型の定義と、もう 1 つの定義が含まれています。jdouble
int
double
jni.h
windows
win32/jni_mh.h
jsize
// 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;
興味深いのは、jint
Clong
タイプ( int
16 ビット以上) ではなく、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
関数を宣言しており、メソッドはその関数。sayHello
String
string
main
sayHello
次に、上記のコードをコンパイルし、C/C++ ヘッダー ファイルを生成します。
> javac TestJNIString.java
> javah TestJNIString
C コードの実装:
TestJNIString.c 上のヘッダー ファイルは、次のような関数TestJNIString.h
を宣言します。
JNIEXPORT jstring JNICALL Java_TestJNIString_sayHello(JNIEnv *, jobject, jstring);
JNI は、Java の型に対応するjstring
型String
。上記のステートメントの最後のパラメータjstring
はString
Java コードからのパラメータであり、戻り値もjstring
type。
String
Java はオブジェクトであるのに対し、C は最後にstring
配列であるため、文字列を渡すことはプリミティブ型を渡すことよりも複雑です。したがって、Javaオブジェクトを C 文字列表現に変換する必要があります。NULL
char
String
char *
前に述べたように、JNI 環境ポインターは、JNIEnv *
データ変換を処理するための非常に豊富なインターフェイス関数を定義しています。
const char* GetStringUTFChars(JNIEnv*, jstring, jboolean*)
JNI をjstring
C に変換する呼び出しchar *
jstring NewStringUTF(JNIEnv*, char*)
C をchar *
JNI に変換する呼び出しjstring
したがって、C プログラムの基本的なプロセスは次のとおりです。
GetStringUTFChars()
関数jstring
を使用して変換しますchar *
- その後、必要なデータ処理を実行します
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
したがって、この関数の戻り値が正しいかどうかを確認することをお勧めしますNULL
。3 番目のパラメーターはisCopy
、このパラメーターがin-out
パラメーターであり、渡されるのはポインターであり、関数の終了時にポインターの内容が変更されるということです。内容が「はい」の場合は、返されたデータがデータのコピーでJNI_TRUE
あることを意味します。それ以外の場合、「はい」の場合、返された文字列がそのオブジェクト インスタンスを直接指していることを意味します。この場合、ネイティブ コードはコード内のコンテンツを任意に変更しないでください。変更すると Java のコードが変更されるためです。JNI システムは、戻り値が直接参照であることを確認しようとしますが、そうでない場合はコピーを返します。通常、これらの変更を気にすることはほとんどないので、通常はここでパラメータに渡します。jstring
JNI_FALSE
String
string
string
NULL
isCopy
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
string
C++ では、ローカル クラスの関数呼び出し構文が異なることに注意してください。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
クラスと同様に型を指します。array
9 種類の java 、8 種類の基本的な配列、およびオブジェクトの配列があります。JNI は java: の基本型に対応する配列を定義しておりjintArray, jbyteArray, jshortArray, jlongArray, jfloatArray, jdoubleArray, jcharArray, jbooleanArray
、オブジェクト指向の配列もありますjobjectArray
。
同様に、JNI 配列とネイティブ配列の間で変換する必要があります。JNI システムは、次のような一連のインターフェイス関数を提供します。
- 使用するとCに変換され
jint* GetIntArrayElements(JNIEnv *env, jintArray a, jboolean *iscopy)
ますjintarray
jint[]
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 組あります。
したがって、ネイティブ プログラムは次のことを行う必要があります。
- JavaからJNI配列を受け入れ、ネイティブ配列に変換します
- 必要なデータ操作を実行する
- 返す必要のあるデータを
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);
同様にget
、release
関数と関数の間に 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);
}
}
このクラスには、private
int オブジェクトと 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);
}
オブジェクト内の変数にアクセスするには、次のことを行う必要があります。
GetObjectClass()
対象オブジェクトのクラス参照を取得する呼び出し- 上記で取得したクラス参照から取得した変数にアクセスする
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 型配列を意味します。 - 上記で取得した に基づいて
Field ID
、GetObjectField()
またはGet_primitive-type_Field()
関数使用して必要なデータを解析します 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);
}
インスタンスでメソッドをコールバックできるようにするには、次のものが必要です。
- 関数を通じて
GetObjectClass()
このインスタンスのクラス オブジェクトを取得します - 上記で取得したクラス オブジェクトから
GetMethodID()
関数を呼び出して、インスタンス内のメソッドの抽象化を表すメソッド ID を取得します。このメソッドの名前と署名情報を指定する必要があります。署名ルールは変数と似ています。署名の形式は次のとおりです(parameters)return-type
。jni の署名が覚えにくいと本当に感じる場合は、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;
.......
上記の出力から、クラス内の各メソッドのシグネチャが明確にわかります。
- 上記で取得したメソッド ID に基づいて、または を
_Primitive-type_Method()
呼び出してこのメソッドを呼び出すことができます。メソッドにパラメータが必要な場合は、パラメータに従ってください。CallVoidMethod()
CallObjectMethod()
- 静的メソッドを呼び出したい場合は、
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()
- まずメソッド ID を取得し、使用します
GetMethodID()
- 上記で取得したメソッド 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 コードに戻るように指示します。jobjectarray
NewObject()
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 つのタイプに分類します。
- ネイティブ参照はネイティブ コードで作成され、関数が終了するか戻るときに解放されます。有効なスコープはネイティブ関数内のみです。呼び出して
DeleteLocalRef()
ローカル参照を明示的に無効化し、ガベージ コレクション中に再利用できるようにすることもできます。ローカル関数にパラメータとして渡されるオブジェクト参照はローカル参照であり、JNI 関数から返されるすべての Java オブジェクト (ジョブジェクト) はローカル参照です。 - グローバル参照は、プログラマが
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
ため、グローバル参照を作成できないことに注意してください。!