Java Language
Javaネイティブインタフェース
サーチ…
パラメーター
パラメータ | 詳細 |
---|---|
JNIEnv | JNI環境へのポインタ |
仕事 | 非static native メソッドを呼び出したオブジェクト |
jclass | static native メソッドを呼び出したクラス |
備考
JNIを設定するには、Javaコンパイラとネイティブコンパイラの両方が必要です。 IDEとOSに応じて、いくつかの設定が必要です。 Eclipseのためのガイドはここにあります 。完全なチュートリアルはここで見つけることができます 。
これらは、Windows上でJava-C ++リンケージを設定する手順です。
-
javac
を使用してJavaソースファイル(.java
)をクラス(.class
)にコンパイルします。 -
javah
を使用してnative
メソッドを含むJavaクラスからヘッダー(.h
)ファイルを作成します。これらのファイルは、ネイティブコードをどのメソッドに実装するかを「指示」します。 -
native
メソッドを実装するC ++ソースファイル(.cpp
)にヘッダファイル(#include
)をインクルードします。 - C ++ソースファイルをコンパイルし、ライブラリ(
.dll
)を作成します。このライブラリにはネイティブコードの実装が含まれています。 - ライブラリパス(
-Djava.library.path
)を指定し、Javaソースファイル(System.loadLibrary(...)
)にロードします。
コールバック(ネイティブコードからJavaメソッドを呼び出す)では、メソッド記述子を指定する必要があります。記述子が間違っていると、実行時エラーが発生します。このため、 javap -s
使って記述子を作成すると便利です。
JavaからC ++メソッドを呼び出す
Javaの静的メソッドとメンバーメソッドをネイティブとしてマークして、その実装が共有ライブラリファイル内にあることを示すことができます。ネイティブメソッドを実行すると、JVMはロードされたライブラリ( ネイティブライブラリの読み込みを参照)で対応する関数を探し、単純な名前のマングリングスキームを使用して引数の変換とスタックの設定を行い、ネイティブコードに制御を渡します。
Javaコード
/*** com/example/jni/JNIJava.java **/
package com.example.jni;
public class JNIJava {
static {
System.loadLibrary("libJNI_CPP");
}
// Obviously, native methods may not have a body defined in Java
public native void printString(String name);
public static native double average(int[] nums);
public static void main(final String[] args) {
JNIJava jniJava = new JNIJava();
jniJava.printString("Invoked C++ 'printString' from Java");
double d = average(new int[]{1, 2, 3, 4, 7});
System.out.println("Got result from C++ 'average': " + d);
}
}
C ++コード
ネイティブ関数宣言を含むヘッダーファイルは、対象クラスのjavah
ツールを使用して生成する必要があります。ビルドディレクトリで次のコマンドを実行します。
javah -o com_example_jni_JNIJava.hpp com.example.jni.JNIJava
...以下のヘッダーファイルを生成します( 簡潔にするためにコメントは削除されています)。
// com_example_jni_JNIJava.hpp
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h> // The JNI API declarations
#ifndef _Included_com_example_jni_JNIJava
#define _Included_com_example_jni_JNIJava
#ifdef __cplusplus
extern "C" { // This is absolutely required if using a C++ compiler
#endif
JNIEXPORT void JNICALL Java_com_example_jni_JNIJava_printString
(JNIEnv *, jobject, jstring);
JNIEXPORT jdouble JNICALL Java_com_example_jni_JNIJava_average
(JNIEnv *, jclass, jintArray);
#ifdef __cplusplus
}
#endif
#endif
次に実装例を示します。
// com_example_jni_JNIJava.cpp
#include <iostream>
#include "com_example_jni_JNIJava.hpp"
using namespace std;
JNIEXPORT void JNICALL Java_com_example_jni_JNIJava_printString(JNIEnv *env, jobject jthis, jstring string) {
const char *stringInC = env->GetStringUTFChars(string, NULL);
if (NULL == stringInC)
return;
cout << stringInC << endl;
env->ReleaseStringUTFChars(string, stringInC);
}
JNIEXPORT jdouble JNICALL Java_com_example_jni_JNIJava_average(JNIEnv *env, jclass jthis, jintArray intArray) {
jint *intArrayInC = env->GetIntArrayElements(intArray, NULL);
if (NULL == intArrayInC)
return -1;
jsize length = env->GetArrayLength(intArray);
int sum = 0;
for (int i = 0; i < length; i++) {
sum += intArrayInC[i];
}
env->ReleaseIntArrayElements(intArray, intArrayInC, 0);
return (double) sum / length;
}
出力
上の例のクラスを実行すると、次のような出力が得られます。
Javaから呼び出されたC ++ 'printString'
C ++ 'average'の結果が得られました:3.4
C ++(コールバック)からJavaメソッドを呼び出す
ネイティブコードからJavaメソッドを呼び出すことは、2つのステップからなるプロセスです。
Javaコード
/*** com.example.jni.JNIJavaCallback.java ***/
package com.example.jni;
public class JNIJavaCallback {
static {
System.loadLibrary("libJNI_CPP");
}
public static void main(String[] args) {
new JNIJavaCallback().callback();
}
public native void callback();
public static void printNum(int i) {
System.out.println("Got int from C++: " + i);
}
public void printFloat(float i) {
System.out.println("Got float from C++: " + i);
}
}
C ++コード
// com_example_jni_JNICppCallback.cpp
#include <iostream>
#include "com_example_jni_JNIJavaCallback.h"
using namespace std;
JNIEXPORT void JNICALL Java_com_example_jni_JNIJavaCallback_callback(JNIEnv *env, jobject jthis) {
jclass thisClass = env->GetObjectClass(jthis);
jmethodID printFloat = env->GetMethodID(thisClass, "printFloat", "(F)V");
if (NULL == printFloat)
return;
env->CallVoidMethod(jthis, printFloat, 5.221);
jmethodID staticPrintInt = env->GetStaticMethodID(thisClass, "printNum", "(I)V");
if (NULL == staticPrintInt)
return;
env->CallVoidMethod(jthis, staticPrintInt, 17);
}
出力
C ++からの浮動小数点数:5.221
C ++からintを得ました:17
ディスクリプタの取得
記述子(または内部型シグネチャ )は、コンパイルされた.class
ファイルのjavapプログラムを使用して取得されます。 javap -p -s com.example.jni.JNIJavaCallback
出力を次に示します。
Compiled from "JNIJavaCallback.java"
public class com.example.jni.JNIJavaCallback {
static {};
descriptor: ()V
public com.example.jni.JNIJavaCallback();
descriptor: ()V
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
public native void callback();
descriptor: ()V
public static void printNum(int);
descriptor: (I)V // <---- Needed
public void printFloat(float);
descriptor: (F)V // <---- Needed
}
ネイティブライブラリをロードする
共有ライブラリファイルをJavaでロードするための一般的なイディオムは、次のとおりです。
public class ClassWithNativeMethods {
static {
System.loadLibrary("Example");
}
public native void someNativeMethod(String arg);
...
System.loadLibrary
へのSystem.loadLibrary
は、ほとんどの場合、クラスのロード中に発生するように静的であり、共有ライブラリがロードされる前にネイティブメソッドが実行されないようにします。しかし、次のことが可能です:
public class ClassWithNativeMethods {
// Call this before using any native method
public static void prepareNativeMethods() {
System.loadLibrary("Example");
}
...
これにより、必要になるまで共有ライブラリの読み込みを遅らせることができますが、 java.lang.UnsatisfiedLinkError
を避けるためには特別な注意が必要です。
ターゲットファイルの検索
共有ライブラリファイルは、 java.library.path
システムプロパティで定義されたパスで検索されます。実行時に-Djava.library.path=
JVM引数を使用して上書きすることができます。
java -Djava.library.path=path/to/lib/:path/to/other/lib MainClassWithNativeMethods
システムのパス区切りに注意してください;
代わりに:
。
System.loadLibrary
は、プラットフォーム依存の方法でライブラリファイル名を解決することに注意してください。上のコードスニペットは、LinuxではlibExample.so
、WindowsではExample.dll
という名前のファイルがlibExample.so
です。
System.loadLibrary
の代わりにSystem.loadLibrary
System.load(String)
があります。これは共有ライブラリファイルへの完全なパスをとり、 java.library.path
参照を迂回します:
public class ClassWithNativeMethods {
static {
System.load("/path/to/lib/libExample.so");
}
...