JNI入門チュートリアル:最小環境HelloWorld実戦
JNIはAndroidアプリケーション開発ではあまり扱われない技術ですが、Framework層では広く使われています.Androidアプリケーション開発者として、JNIの知識を学ぶことは、システム全体の原理を理解するのに役立ちます.
JNIを学ぶには多くの方法があります.Frameworkソースを直接読むことができます.Frameworkコードを構成するコンパイル環境が複雑で、コンパイル後に直接テストを実行できないため、Rootシステムが必要 次にNDKをダウンロードしてインストールし、Android Studioで直接プロジェクトを開発することもできます.この方式は環境構成の作業も多く,操作が面倒である.
そこでこの記事で検討したテーマは,最小環境下でのJNIの学習と検証である.JNIはJava自体が持つ能力なので、最小環境はJavaが実行する環境であり、Android開発ツールやIDEは必要ありません.デバッグとコンパイルを容易にするために、ここではUbuntuシステムを使用しています.インストールする必要がある開発ツールは次のとおりです.openjdk:ここではopenjdk 11バージョンを採用 gcc:通過 いずれかのテキストエディタ JNI Hello World
このJNIの例では、主にこれらのことをしています.JavaでJNI関数を呼び出す JNIでJavaの変数を印刷 JNIでJava中変数を修正 JNIでJavaメソッドを呼び出す Javaコードの作成
Javaレイヤのコードは簡単で、nativeコードの読み書きのためにString変数を宣言します.変数を印刷する方法も提供します
ヘッダファイルの生成
次にJavaコンパイルツールを用いてJNIモジュールの
Cコードの作成
そして同じディレクトリの下にCソースファイルを作成する
Cコードのコンパイル
nativeレイヤロジックを作成すれば、Cコードをコンパイルできます.コンパイルには2つのヘッダファイルが必要
この2つのパスをgccコンパイルパラメータに追加する必要があります.また、ライブラリファイルをコンパイルしていますのでmain関数は含まれていませんので、そのまま使用します
/usr/lib/gcc/x86_64-linux-gnu/7/…/…/…/x86_64-linux-gnu/scrt 1.o:関数'start'中:(.text+0 x 20):mainに対して定義されていない参照collect 2:error:ld returned 1 exit status
追加する必要がある
コンパイルが完了すると、同じディレクトリの下に
JNI Libにロード
Javaコードを再度変更し、作成したライブラリファイルをロードします.デフォルトの環境ではjavaの
変更後再使用
テスト
Javaコマンドで直接実行:
出力が見える
JNI : Java_JniStudy_nativeChangeMsg Print Java String in JNI: Hello World from Java Hello World from JNI #################### JNI : Java_JniStudy_nativeCallPrintMsg JNI CallVoidMethod: printMsg Hello World from JNI
Java->Native,Native->Javaの2つのリンクが通じていることを証明します.
いくつかの問題
以下は私がデバッグで出会ったいくつかの問題です.上に述べたものもありますが、ここでもう一度まとめます.
1.ヒント
これはJDKバージョンと関係があるかもしれません.旧バージョンJDKでは
2.gccコンパイルエラー
/usr/lib/gcc/x86_64-linux-gnu/7/…/…/…/x86_64-linux-gnu/scrt 1.o:関数'start'中:(.text+0 x 20):mainに対して定義されていない参照collect 2:error:ld returned 1 exit status
JNIはライブラリファイルをロードしているため、mainメソッドがなく、コンパイル時に追加する必要がある
3.時報エラーFatel Errorを実行し、クラッシュ終了
エラーメッセージ
A fatal error has been detected by the Java Runtime Environment
nativeレイヤメソッド
4.nativeレイヤメソッドドキュメントはどこですか?
native層の法例としては
5.nativeのJavaタイプ署名の決定方法
公式サイトドキュメント:Chapter 3:JNI Type-and Data Structures
より簡単な方法は
の最後の部分
この小さなDemoによって、JNIの最も基本的な流れが開通しました.プロジェクト学習の過程で、この方法を使用して迅速に検証し、効率を高め、複雑なエンジニアリングや環境構成の問題を回避することができます.もちろんJNIについては、C++言語、Android、Linuxシステムの下位APIの使用など、多くの知識があり、FrameworkソースコードとNDKを組み合わせて学ぶ必要があります.
JNIを学ぶには多くの方法があります.
そこでこの記事で検討したテーマは,最小環境下でのJNIの学習と検証である.JNIはJava自体が持つ能力なので、最小環境はJavaが実行する環境であり、Android開発ツールやIDEは必要ありません.デバッグとコンパイルを容易にするために、ここではUbuntuシステムを使用しています.インストールする必要がある開発ツールは次のとおりです.
apt-get install build-essential
取付このJNIの例では、主にこれらのことをしています.
Javaレイヤのコードは簡単で、nativeコードの読み書きのためにString変数を宣言します.変数を印刷する方法も提供します
public class JniStudy {
private String msg = "Hello World from Java";
private native void nativeChangeMsg();
public native void nativeCallPrintMsg();
public void getNativeMsg() {
nativeChangeMsg();
}
public void printMsg() {
System.out.println(this.msg);
}
public static void main(String[] args) {
JniStudy test = new JniStudy();
test.getNativeMsg(); // native msg
test.printMsg();
System.out.println("####################");
test.nativeCallPrintMsg(); // native ,native printMsg
}
}
native
識別の2つの方法は、いずれもJNIの方法であり、後でコマンドにより生成する必要がある.h
ヘッダファイル.その他のコードはすべてベースのJavaコードです.ファイルを保存したら、まずjavac
コマンドで直接コンパイルし、コンパイルに成功するはずです.javac JniStudy.java
ヘッダファイルの生成
次にJavaコンパイルツールを用いてJNIモジュールの
.h
ヘッダファイルを生成する必要がある.旧バージョンJavaでは、使用する必要がありますjavah
、Java 11ではこのコマンドは削除されていますので、そのまま使用しますjavac
javac JniStudy.java -h .
-h
パラメータはヘッダファイルを生成するために使用され、.
現在のパスの下に生成されることを表す.生成されたヘッダファイルは次のとおりです./* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class JniStudy */
#ifndef _Included_JniStudy
#define _Included_JniStudy
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: JniStudy
* Method: nativeChangeMsg
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_JniStudy_nativeChangeMsg
(JNIEnv *, jobject);
/*
* Class: JniStudy
* Method: nativeCallPrintMsg
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_JniStudy_nativeCallPrintMsg
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
Cコードの作成
そして同じディレクトリの下にCソースファイルを作成する
jniStudy.c
nativeレイヤロジックを記述する#include
#include "JniStudy.h" // ,
void Java_JniStudy_nativeChangeMsg(JNIEnv *env, jobject obj) {
puts("JNI : Java_JniStudy_nativeChangeMsg");
// Java Class
jclass clazz = (*env)->FindClass(env, "JniStudy");
if (clazz == NULL) {
return;
}
// Java Class fieldID
jfieldID fieldId = (*env) -> GetFieldID(env, clazz, "msg", "Ljava/lang/String;");
if (fieldId == NULL) {
return;
}
// fieldID Java
jstring msg_org = (*env) -> GetObjectField(env, obj, fieldId);
if (msg_org == NULL) {
return;
}
// GetStringUTFChars C char
const char * msg_char = (*env)->GetStringUTFChars(env, msg_org, NULL);
puts("Print Java String in JNI:");
puts(msg_char);
// NewStringUTF JNI jstring
jstring msg = (*env) -> NewStringUTF(env, "Hello World from JNI");
// SetObjectField , jstring Java ( Java msg)
(*env)->SetObjectField(env, obj, fieldId, msg);
}
void Java_JniStudy_nativeCallPrintMsg(JNIEnv *env, jobject obj) {
puts("JNI : Java_JniStudy_nativeCallPrintMsg");
// Java Class
jclass clazz = (*env)->FindClass(env, "JniStudy");
if (clazz == NULL) {
return;
}
// Java Class methodID
jmethodID printMethodId = (*env) -> GetMethodID(env, clazz, "printMsg", "()V");
if (printMethodId == NULL) {
return;
}
puts("JNI CallVoidMethod: printMsg");
// CallVoidMethod Java void
(*env)->CallVoidMethod(env, obj, printMethodId);
}
Java_JniStudy_nativeChangeMsg
Javaオブジェクトのうちのmsg
を読み込み、印刷する.そしてJNIインタフェースでmsg
の内容を変更し、JavaレイヤでprintMsg
新しい内容をプリントアウトします.Java_JniStudy_nativeCallPrintMsg
JNIのインタフェースを介してJavaレイヤを直接呼び出すprintMsg
メソッド.詳細な手順は、コードコメントを参照してください.Cコードのコンパイル
nativeレイヤロジックを作成すれば、Cコードをコンパイルできます.コンパイルには2つのヘッダファイルが必要
jni.h
,jni_md.h
.ヘッダファイルJniStudy.h
では、自動生成コードが参照されていることがわかるjni.h
、jni_md.h
はjni.h
で参照されている.私たちのコードパスの下にはこの2つのファイルがないので、Ubuntuでは2つのヘッダファイルがそれぞれ/usr/lib/jvm/java-11-openjdk-amd64/include/
/usr/lib/jvm/java-11-openjdk-amd64/include/linux/
この2つのパスをgccコンパイルパラメータに追加する必要があります.また、ライブラリファイルをコンパイルしていますのでmain関数は含まれていませんので、そのまま使用します
gcc -o
エラーが発生します/usr/lib/gcc/x86_64-linux-gnu/7/…/…/…/x86_64-linux-gnu/scrt 1.o:関数'start'中:(.text+0 x 20):mainに対して定義されていない参照collect 2:error:ld returned 1 exit status
追加する必要がある
-shared
パラメータの最後の完全なコンパイルコマンドは以下の通りです.gcc -shared -o libjnistudy JniStudy.c \
-I /usr/lib/jvm/java-11-openjdk-amd64/include/ \
-I /usr/lib/jvm/java-11-openjdk-amd64/include/linux
コンパイルが完了すると、同じディレクトリの下に
libjnistudy
というライブラリファイルが生成されます.JNI Libにロード
Javaコードを再度変更し、作成したライブラリファイルをロードします.デフォルトの環境ではjavaの
java.library.path
現在のパスがないため、ここではSystem.load
メソッドを使用して、ライブラリファイルへの絶対パスpublic class JniStudy {
......
static {
System.load("/home/myname/spc-work/jni-test/libjnistudy");
}
......
}
変更後再使用
javac
コマンドコンパイル生成.class
ファイルテスト
Javaコマンドで直接実行:
java JniStudy
出力が見える
JNI : Java_JniStudy_nativeChangeMsg Print Java String in JNI: Hello World from Java Hello World from JNI #################### JNI : Java_JniStudy_nativeCallPrintMsg JNI CallVoidMethod: printMsg Hello World from JNI
Java->Native,Native->Javaの2つのリンクが通じていることを証明します.
いくつかの問題
以下は私がデバッグで出会ったいくつかの問題です.上に述べたものもありますが、ここでもう一度まとめます.
1.ヒント
javah
コマンドが見つからないこれはJDKバージョンと関係があるかもしれません.旧バージョンJDKでは
javah
コマンドを使用できます.新版(JDK 10以上かも)javac
コマンドにはヘッダファイル生成機能が統合されている.2.gccコンパイルエラー
/usr/lib/gcc/x86_64-linux-gnu/7/…/…/…/x86_64-linux-gnu/scrt 1.o:関数'start'中:(.text+0 x 20):mainに対して定義されていない参照collect 2:error:ld returned 1 exit status
JNIはライブラリファイルをロードしているため、mainメソッドがなく、コンパイル時に追加する必要がある
-shared
パラメータgcc -shared xxx.c -o xxx
3.時報エラーFatel Errorを実行し、クラッシュ終了
エラーメッセージ
A fatal error has been detected by the Java Runtime Environment
nativeレイヤメソッド
SetXXXField
,GetXXXField
などのパラメータを空にすることはできません.コード記述が間違っていると、入力パラメータが空になるとクラッシュします.情報をデバッグまたは印刷することで、パラメータのエラー位置を決定できます.4.nativeレイヤメソッドドキュメントはどこですか?
native層の法例としては
FindClass
・GetFieldID
・NewStringUTF
などがありますが、いずれも公式文書があります:JNI公式完全文書:Java Native Interface Specification Contents JNIメソッドリストおよび文書:Chapter 4:JNI Functions5.nativeのJavaタイプ署名の決定方法
公式サイトドキュメント:Chapter 3:JNI Type-and Data Structures
より簡単な方法は
javap
コマンドによる表示例えばJniStudy.class
ファイル通過javap -s
表示可能メソッドの署名Compiled from "JniStudy.java"
public class JniStudy {
public JniStudy();
descriptor: ()V
public native void nativeCallPrintMsg();
descriptor: ()V
public void getNativeMsg();
descriptor: ()V
public void printMsg();
descriptor: ()V
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
static {};
descriptor: ()V
}
javap -v
クラスファイルのコンパイル後のすべての情報を得ることもできますが、この内容は比較的多いです......
Compiled from "JniStudy.java"
public class JniStudy
minor version: 0
major version: 55
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #7 // JniStudy
super_class: #15 // java/lang/Object
interfaces: 0, fields: 1, methods: 7, attributes: 1
Constant pool:
#1 = Methodref #15.#31 // java/lang/Object."":()V
#2 = String #32 // Hello World from Java
#3 = Fieldref #7.#33 // JniStudy.msg:Ljava/lang/String;
#4 = Methodref #7.#34 // JniStudy.nativeChangeMsg:()V
#5 = Fieldref #35.#36 // java/lang/System.out:Ljava/io/PrintStream;
#6 = Methodref #37.#38 // java/io/PrintStream.println:(Ljava/lang/String;)V
#7 = Class #39 // JniStudy
#8 = Methodref #7.#31 // JniStudy."":()V
#9 = Methodref #7.#40 // JniStudy.getNativeMsg:()V
#10 = Methodref #7.#41 // JniStudy.printMsg:()V
#11 = String #42 // ####################
#12 = Methodref #7.#43 // JniStudy.nativeCallPrintMsg:()V
#13 = String #44 // /home/myname/spc-work/jni-test/libjnistudy
#14 = Methodref #35.#45 // java/lang/System.load:(Ljava/lang/String;)V
#15 = Class #46 // java/lang/Object
......
の最後の部分
この小さなDemoによって、JNIの最も基本的な流れが開通しました.プロジェクト学習の過程で、この方法を使用して迅速に検証し、効率を高め、複雑なエンジニアリングや環境構成の問題を回避することができます.もちろんJNIについては、C++言語、Android、Linuxシステムの下位APIの使用など、多くの知識があり、FrameworkソースコードとNDKを組み合わせて学ぶ必要があります.