以下のサイトが丁寧で素晴らしい。
http://d.hatena.ne.jp/leetmikeal/20130403/1364959490
加えて、以下もインストールした。
cmake ncurses-devel
2014年3月16日日曜日
2014年3月15日土曜日
JNI (Java Native Interface) (5) 変数の設定、メソッド呼び出し
JNIを使って Javaの変数フィールド を扱う方法を見ていきましょう。
javahで生成された関数プロトタイプは以下の様でした。
すべての関数プロトタイプは JNIEnvポインタ が最初の引数として宣言されます。
C++ネイティブコードは、JNIEnvポインタ で自分のメソッド(this)と同じように使うことができます。C++オブジェクトは、同様の方法で自分自身のメソッドへのポインタを保持しているので、このように使うことができます。
では、Javaのデータフィールドを操作してみましょう。
privateなint型の変数と、同じ型ですがstaticな変数を宣言しています。
それぞれの値をsetするnativeメソッドを作っておきましょう。
javahで生成した関数プロトタイプが以下です。
関数プロトタイプの実装は以下のようにできます。
これらからわかることを整理してみましょう。
次は、Javaオブジェクトのデータフィールドを取得してみましょう。
javahで生成された関数プロトタイプは以下の様でした。
JNIEXPORT void JNICALL
Java_jp_sprix_jni_ImageRecognize_queryImage(JNIEnv *env, jobject obj){
…
すべての関数プロトタイプは JNIEnvポインタ が最初の引数として宣言されます。
C++ネイティブコードは、JNIEnvポインタ で自分のメソッド(this)と同じように使うことができます。C++オブジェクトは、同様の方法で自分自身のメソッドへのポインタを保持しているので、このように使うことができます。
では、Javaのデータフィールドを操作してみましょう。
オブジェクトのデータフィールドにネイティブからアクセスする
以下のようなJavaコードがあったとしましょう。privateなint型の変数と、同じ型ですがstaticな変数を宣言しています。
それぞれの値をsetするnativeメソッドを作っておきましょう。
javahで生成した関数プロトタイプが以下です。
関数プロトタイプの実装は以下のようにできます。
これらからわかることを整理してみましょう。
- staticとそうでないデータフィールドにアクセスするには、それぞれ違い関数集合がある
- staticなデータフィールドを参照する場合、jclass への参照が必要
- 非静的なデータフィールドを参照する場合、jobject への参照が必要
- データフィールドを操作するJNI関数は、アクセスするデータ型を名前に含む
次は、Javaオブジェクトのデータフィールドを取得してみましょう。
JNI (Java Native Interface) (4) nativeメソッドのロードと呼び出すについて
nativeメソッドは、通常のJavaメソッドと同じように呼び出せます。
ただし、以下の点に注意しましょう。
java.lang.UnsatisfiedLinkError が投げられたとき、上記を見直してみるとよいでしょう。
次は、変数とメソッド呼び出し についてです。
ただし、以下の点に注意しましょう。
- System.loadLibrary を呼び出す。
- nativeメソッドを宣言しているクラスの中に、staticな初期化ブロックを置いて呼び出すのが一般的
- staticな初期化ブロックのコードは、クラスを初期化するときに実行される
- クラスは初めてアクティブに使われたとき (= クラスが定義したメソッドを呼び出す)に初期化される
- ロードしていなかったり、失敗している場合にはJVMが「java.lang.UnsatisfiedLnkError」を投げる(getMessageで見つからなかったnativeメソッド名を取得できる)
- 共有オブジェクトライブラリのファイルは、環境変数LD_LIBRARY_PATHに指定したディレクトリの中にあることが必要
- LD_LIBRARY_PATHは、「;」で区切って複数のディレクトリを指定可能。以下で確認する
% echo $LD_LIBRARY_PATH
- ネイティブライブラリのロードを頑丈にするには、以下のようにします
検索パスのミス
- ロードに失敗すると、最初にnativeメソッドを呼び出そうとしたとき、java.lang.UnsatisfiedLinkErrorが投げられます。
- mainでロードしている場合、ロードにトライしたとき、上記と同様なことが起こります。
ライブラリ名の間違い
- 上記と同じエラーが起きます。
- javaデバッガで実行するとき、Nativeライブラリの名前に_gを要求することがある。デバッグでのみ問題がおこるときは、[ライブラリ名]_g.so や [ライブラリ名]_g.dll を準備してみるとよいです。
シグネチャの不一致
- nativeメソッドの仮引数の個数と型が、関数プロトタイプと一致しない場合、java.lang.UnsatisfiedLinkErrorが投げられる。nativeメソッドを変更したら、必ずjavahでヘッダを再生成することを進めます。package変更によっても起こります。
java.lang.UnsatisfiedLinkError が投げられたとき、上記を見直してみるとよいでしょう。
次は、変数とメソッド呼び出し についてです。
2014年3月14日金曜日
JNI (Java Native Interface) (3) Servletから呼び出す
既にプラットフォーム上で動作できているならば、同じプラットフォーム上の tomcat servlet から呼び出すことは難しくありません。
ただし、Pure Java で 書いたモジュールのように、tomcatとJVMがあればどこでもというわけにはいきません。
同じプラットフォーム上に限定すれば、以下の注意をすれば javaで作成したWEBアプリケーションから nativeコード を呼び出すことが可能です。
後は、LD_LIBRARY_PATH に 使用する動的ライブラリを入れて、環境変数か実行時にオプションで指定すれば動作します。
ただし、Pure Java で 書いたモジュールのように、tomcatとJVMがあればどこでもというわけにはいきません。
同じプラットフォーム上に限定すれば、以下の注意をすれば javaで作成したWEBアプリケーションから nativeコード を呼び出すことが可能です。
- Servletクラス(もしくはその先でnativeライブラリを使用するクラス)では、nativeライブラリのロードを忘れないようにしましょう。
- jniライブラリは、jarファイルにして WEB-INFのlib配下に設置します。
後は、LD_LIBRARY_PATH に 使用する動的ライブラリを入れて、環境変数か実行時にオプションで指定すれば動作します。
JNI (Java Native Interface) (2) 最も簡単なコードを書いてみる
ここでは、最も簡単なコードを書いてみましょう。
簡単に以下の様にコマンドすれば出来ます。
native宣言の入ったすべてのクラスファイルで、javah を走らせます。
Javaクラスファイルの場所は、環境変数 CLASSPATH、javahのオプション -classpath で指定します。
生成されたヘッダファイルは、nativeメソッドを実装したソースファイルにインクルードが出来る。
ヘッダファイルの名前は、入力したクラスの完全修飾された名前で決まります。つまり、パッケージ名までを含んだ一意な名前になります。
例えば以下のようになります。
これはjp.sprix.jniパッケージの配下にjavaファイルを置いている場合です。
次に、nativeコードを記述します。
以下の3つの制限に従っていることに注意しましょう。
この構成に必要となる構造は以下です。
src - javaパッケージ、javaソースコード、ANSI 関数プロトタイプヘッダファイル格納
dist - 動的ライブラリの格納ディレクトリ
jnih - ANSI 関数プロトタイプヘッダファイル、native実装ファイル
build.xml
build.xmlファイルを読めば、大体のディレクトリ構造はわかると思います。
また、gccコンパイルに必要なインクルードディレクトリやリンカーの設定もしてあります。
これでbuildしたxmlファイルは、実行時に以下のjava オプションでnativeライブラリの場所を指定すます。
以上が、Hello World と ビルド方法についてです。
次は、起きやすい問題やその対処について書こうと思います。
世界が広がる「Hello World!」です。
ただ実行するだけでなく、出来るだけ簡単に build 出来る環境も作成しましょう。
流れ
Java と c や c++ の両方の経験があれば、難しい処理は出てきません。
Java だけしか経験が無い場合、少し戸惑うかもしれませんが、知らなかったり、わからない部分は c や c++ のコードを実行するのに必要な作業と思えばよいと思います。
まず、Javaのコードを書きます。
その際、メソッドに nativeキーワード を指定して、nativeメソッド宣言します。
Javaクラスにはnativeメソッドの本体は定義しないので注意してください。
javah 使って、nativeメソッド毎の関数プロトタイプが記述された ヘッダファイル を生成します。
その関数プロトタイプの記述に従って、c や cpp で本体の関数を記述します。
関数プロトタイプには、入力(仮引数) と 出力(戻り値) を定義するので、native関数はそれに従って記述する必要があります。
本体のnative関数は、プラットフォーム固有のコンパイラとリンカを使用してコンパイラします。ここは、プラットフォーム毎に手順が違うと思います。
JVMは、それぞれの関数プロトタイプに定義された呼び出しプロトコルを使って、nativeメソッドを実行します。
これが、一連の流れです。
ここでは、native メソッドのプログラムは C や C++ で考えることにします。
ネイティブコードへのインターフェース記述
Javaコード と native のコードを統合するときの課題は、Javaのnativeメソッドの仮引数を、既存のAPIの仮引数にマッピングすることです。
一般の解決法は以下の 2 つです。
- 従来のデータを Java で設計し、そのデータを扱えるうまいメソッドを定義する
- 既存のインターフェースを、自分の Java のnativeメソッド宣言に直接マップ
どちらも難しそうです。
後者は、Cの構造体をJavaのオブジェクトで置き換えるために適用できる方法ですが、後で詳しく見ていくことにします。
順番に見ていきましょう。
Javaコードの生成
Javaクラスに、nativeメソッドを定義します。
例えば以下の様な感じです。
javac でクラスファイルを生成
IDEを使用していれば、ここは意識しないところですね。簡単に以下の様にコマンドすれば出来ます。
% javac NativeSampleMethod.java
javahでインクルードするヘッダファイルを生成
nativeメソッドの宣言が入ったJavaクラスファイルを作成したら、対応する ANSI C関数プロトタイプを生成します。native宣言の入ったすべてのクラスファイルで、javah を走らせます。
Javaクラスファイルの場所は、環境変数 CLASSPATH、javahのオプション -classpath で指定します。
% javah -jni NativeSampleMethod
生成されたヘッダファイルは、nativeメソッドを実装したソースファイルにインクルードが出来る。
ヘッダファイルの名前は、入力したクラスの完全修飾された名前で決まります。つまり、パッケージ名までを含んだ一意な名前になります。
例えば以下のようになります。
これはjp.sprix.jniパッケージの配下にjavaファイルを置いている場合です。
次に、nativeコードを記述します。
以下の3つの制限に従っていることに注意しましょう。
- nativeメソッドの実装関数は、最初の引数が JNIEnvへのポインタ、2番目がオブジェクト参照
- 入力引数の型はJNIが定義する
- 戻り値の型はJNIが定義する
- 関数プロトタイプが含む ヘッダファイルをインクルードしなければならない
ライブラリのビルド
プラットフォーム固有になる部分です。
Linuxであれば .so 、Windowsであれば .dll をビルドします。
今回は、ant contrib を使用して、ビルドしましょう。
詳細な方法は、以前のブログに書いています。
http://masabloggers.blogspot.jp/2013/05/jni-javacpp.html
今回必要になるbuild.xmlは以下です。
Linuxであれば .so 、Windowsであれば .dll をビルドします。
- nativeコードをコンパイルし、jni.hをインクルードするためにふさわしい場所はすべて指定
- nativeライブラリをビルド
今回は、ant contrib を使用して、ビルドしましょう。
詳細な方法は、以前のブログに書いています。
http://masabloggers.blogspot.jp/2013/05/jni-javacpp.html
今回必要になるbuild.xmlは以下です。
この構成に必要となる構造は以下です。
src - javaパッケージ、javaソースコード、ANSI 関数プロトタイプヘッダファイル格納
dist - 動的ライブラリの格納ディレクトリ
jnih - ANSI 関数プロトタイプヘッダファイル、native実装ファイル
build.xml
build.xmlファイルを読めば、大体のディレクトリ構造はわかると思います。
また、gccコンパイルに必要なインクルードディレクトリやリンカーの設定もしてあります。
これでbuildしたxmlファイルは、実行時に以下のjava オプションでnativeライブラリの場所を指定すます。
LD_LIBRARY_PATH
以上が、Hello World と ビルド方法についてです。
次は、起きやすい問題やその対処について書こうと思います。
JNI経由でJavaからOpenCVを使ったCppを呼び出す
前回JNIについて書いたのは結構前でした。
JNI JavaからCppを実行する
http://masabloggers.blogspot.jp/search/label/JNI
※参考ページ
http://stbr.no-ip.org/daizu/2009/11/jni-ant.html
この時に作成したbuild.xmlを改良して、OpenCVを使ったCppのコードを
コンパイル出来るようにするぜ。
後、うまくOpenCVの実行ファイルを使えるようにするぜ。
というのが目標です。
まずは ANT build.xml でOpenCVを使ったコンパイルが出来るようにしましょう。
今回は、OpenCV2.4.6 を使います。
OpenCVダウンロード
http://masabloggers.blogspot.jp/search/label/JNI
以下に展開した前提で進めます。
OpenCVを使用したコードで必要なのは、主に以下だと思います。
以下のように書けます。
注意したいのは、ライブラリを指定する時に、先頭の lib と 拡張子の .so を除いて記述するところです。libsには私が必要だったライブラリしか指定していないので、必要に合わせて変更します。
OpenCVのインストール時、オプションを指定しないとすべて /usr/local/ に配置されるので、ディレクトリを指定したい場合は注意。
JNI JavaからCppを実行する
http://masabloggers.blogspot.jp/search/label/JNI
※参考ページ
http://stbr.no-ip.org/daizu/2009/11/jni-ant.html
この時に作成したbuild.xmlを改良して、OpenCVを使ったCppのコードを
コンパイル出来るようにするぜ。
後、うまくOpenCVの実行ファイルを使えるようにするぜ。
というのが目標です。
Cppコードのコンパイル
まずは ANT build.xml でOpenCVを使ったコンパイルが出来るようにしましょう。
今回は、OpenCV2.4.6 を使います。
OpenCVダウンロード
http://masabloggers.blogspot.jp/search/label/JNI
以下に展開した前提で進めます。
/opt/opencv-2.4.6gcc のコンパイルは、ノード cc で実行します。
OpenCVを使用したコードで必要なのは、主に以下だと思います。
- ヘッダファイルのincludeディレクトリ
- リンクするライブラリ
- includepath
- libset
以下のように書けます。
注意したいのは、ライブラリを指定する時に、先頭の lib と 拡張子の .so を除いて記述するところです。libsには私が必要だったライブラリしか指定していないので、必要に合わせて変更します。
OpenCVのインストール時、オプションを指定しないとすべて /usr/local/ に配置されるので、ディレクトリを指定したい場合は注意。
JNI (Java Native Interface) (1) JNIの概要
JNIについて、ネットを探してもあまり良い日本語の情報が見つからなかったので、
まとめてみようと思いました。
参考にしている書籍は以下の本なので、若干古い情報もあるかもしれないが、
コードは動作したもののみを載せます。
http://www.amazon.co.jp/dp/4894710803
・PureJavaでは実現できないパフォーマンスが必要なとき
・過去のアプリケーションライブラリを有効活用したいとき
パフォーマンスの問題は、JVMの成熟やJIT(ジャストインタイム)コンパイラによって
改善に向かっていますが、すべてを解決してくれるわけではありません。
JNI以外の手法(JRI、RNI)も存在するが、AndroidアプリケーションがNDK(JNI)をサポート
したためか、2014年現在、存在感を失っています。
ホストのハードウェア上で直接、ネイティブコードが走ります。つまり、
JavaコードはJavaバイトコード(classファイル)にコンパイルされ、JVMがバイトコードと
ホストハードウエアの間を媒介する形で実行しますが、ネイティブコードはJVMの管理外で
走るということです。
ネイティブコードはJVMが媒介しないで実行可能な、プラットフォーム固有のオブジェクトコードです。
ライブラリを作成する場合は、ホストマシンのネイティブコンパイラとリンカを
使用してライブラリをビルドします。
ネイティブコンパイラが作成するオブジェクトコードはプラットフォーム固有なので、
ライブラリも同様です。
JNIのAPI(アプリケーションプログラミングインターフェイス)の規則に従って作成された
バイナリオブジェクトがプラットフォームX上のライブラリに入っているならば、
ライブラリを再ビルドすることなく、JNIをサポートしたJVMを介して使用することが出来ます。
・Javaオブジェクトの中身が見えない方法で参照を提供する
・JVMとネイティブコードの間に関数テーブルを配置し、JVMのデータにアクセスする場合は必ずその関数を経由する
・ネイティブの型集合を定義し、Javaの型からプラットフォーム固有な型への一様なマッピングを提示する
・メモリの喪失への対応(ユーザが連続したメモリ領域を期待する場合のために、オブジェクトのメモリの扱い方についてJVMに柔軟性をもたせる。文字列や数値配列データを返すJNI関数は、ネイティブコードがメモリ領域を使用している間にメモリ管理システムがその領域を移動しないように、メモリ領域をロックする可能性がある)
コードが無いとイメージしづらい部分もあると思います。
それらは後で実際にコードを動作させて見てみようと思います。
さらに詳細な特徴もあります。メソッドの呼び出しやオブジェクトの生成、変数へのアクセス、例外処理、ネイティブメモリの管理モニタ、クラスのロード、JVMの生成 などにJNIの特徴・良い点があります。
次に続く。
まとめてみようと思いました。
参考にしている書籍は以下の本なので、若干古い情報もあるかもしれないが、
コードは動作したもののみを載せます。
http://www.amazon.co.jp/dp/4894710803
JNIを使う理由
・PureJavaでは実現できないパフォーマンスが必要なとき
・過去のアプリケーションライブラリを有効活用したいとき
パフォーマンスの問題は、JVMの成熟やJIT(ジャストインタイム)コンパイラによって
改善に向かっていますが、すべてを解決してくれるわけではありません。
JNI以外の手法(JRI、RNI)も存在するが、AndroidアプリケーションがNDK(JNI)をサポート
したためか、2014年現在、存在感を失っています。
Javaアプリケーションがネイティブメソッドを実行するということ
ホストのハードウェア上で直接、ネイティブコードが走ります。つまり、
JavaコードはJavaバイトコード(classファイル)にコンパイルされ、JVMがバイトコードと
ホストハードウエアの間を媒介する形で実行しますが、ネイティブコードはJVMの管理外で
走るということです。
ネイティブコードはJVMが媒介しないで実行可能な、プラットフォーム固有のオブジェクトコードです。
ネイティブコードとは
一般にダイナミック(動的)ロードライブラリの中に入れます。ライブラリを作成する場合は、ホストマシンのネイティブコンパイラとリンカを
使用してライブラリをビルドします。
ネイティブコンパイラが作成するオブジェクトコードはプラットフォーム固有なので、
ライブラリも同様です。
再ビルドは必要ない(バイナリ互換性がある)
JNIのAPI(アプリケーションプログラミングインターフェイス)の規則に従って作成された
バイナリオブジェクトがプラットフォームX上のライブラリに入っているならば、
ライブラリを再ビルドすることなく、JNIをサポートしたJVMを介して使用することが出来ます。
JNIの特徴
・Javaオブジェクトの中身が見えない方法で参照を提供する
・JVMとネイティブコードの間に関数テーブルを配置し、JVMのデータにアクセスする場合は必ずその関数を経由する
・ネイティブの型集合を定義し、Javaの型からプラットフォーム固有な型への一様なマッピングを提示する
・メモリの喪失への対応(ユーザが連続したメモリ領域を期待する場合のために、オブジェクトのメモリの扱い方についてJVMに柔軟性をもたせる。文字列や数値配列データを返すJNI関数は、ネイティブコードがメモリ領域を使用している間にメモリ管理システムがその領域を移動しないように、メモリ領域をロックする可能性がある)
コードが無いとイメージしづらい部分もあると思います。
それらは後で実際にコードを動作させて見てみようと思います。
さらに詳細な特徴もあります。メソッドの呼び出しやオブジェクトの生成、変数へのアクセス、例外処理、ネイティブメモリの管理モニタ、クラスのロード、JVMの生成 などにJNIの特徴・良い点があります。
次に続く。
登録:
投稿 (Atom)