JNI(Java Native Interface)と呼ばれる技術を使うことにより、JavaとC++の2つのプログラミング言語を統合的に扱うことができます。このように2つのプログラム言語を統合的に扱い、1つのプログラムを作ることは色々なメリットがあります。そのメリットの1つは、1つのプログラミング言語だけでは享受することのないメリットを複数のプログラミング言語を用いることにより、享受できるようになることです。具体的には、GUIに優れたJavaと、実行速度の高いC/C++を組み合わせることにより、パフォーマンスに優れたプログラムをより短時間で作れるようになるでしょう。
JNIを使うための準備
環境は、UNIX/Linuxを対象としています。が、ウェブ上や書籍なんかを見る限り、WindowsでもJNIの利用は可能なようなので、Windows上で利用したい方は一度探してみてください。そのうち、自分もWindows用の利用方法も書くかもしれません。
まず、JNIを利用するための準備ですが、これは特にありません。まぁ当然ながら、JavaとC++を利用するということで、JavaとC++の実行環境はそれぞれ必要になります。これらは、お使いのディストリビューションによって異なることもありますので、各自準備しておいてください。
では、続きでは実際のサンプル用のソースコードを書いていきます。
サンプル用のソースコード
Java側
public class JniExample {
static {
System.loadLibrary( "JniExample" );
}
public int intField = 17;
public static String stringField = "Hello!";
private static native void Call( JniExample x );
public JniExample() { }
public static void main( String[] args ) {
JniExample my = new JniExample();
my.Call( my );
}
}
C++側
#include <jni.h>
#include "JniExample.h"
#include <iostream>
using namespace std;
JNIEXPORT void JNICALL Java_JniExample_Call (JNIEnv *env, jclass clazz, jobject obj) {
jfieldID intFieldId = env->GetFieldID(clazz, "intField", "I" );
if( intFieldId == 0 ) exit(1);
int intFieldVal = env->GetIntField( obj, intFieldId );
jfieldID strFieldId = env->GetStaticFieldID( clazz, "stringField", "Ljava/lang/String;" );
if( strFieldId == 0 ) exit(1);
jstring jstr = (jstring) env->GetStaticObjectField( clazz, strFieldId );
const char *str = (jstr == 0) ? 0 : env->GetStringUTFChars( jstr, 0 );
cout << intFieldVal << endl;
cout << str << endl;
env->ReleaseIntArrayElements( jarr, arr, 0 );
env->ReleaseStringUTFChars( jstr, str );
return;
}
JNIの全体像
ソースコードの詳しい説明を行う前に、JNIの全体像を簡単に書いてみようと思います。JNIでは、Java側のソースコードをコンパイルし、C/C++で読み込めるヘッダファイルを生成します。次に、C/C++側では先ほどの処理で生成されたヘッダファイルをプログラムの最初に読み込ませます。C/C++のコンパイル時は、実行コードは生成しないで、コンパイル処理のみをしておけば十分です。その後、オブジェクト・コードから、共有ライブラリを生成します。Java側のプログラムでは、この共有ライブラリを読み込む、という記述を書いておきます。これによって、Javaでプログラムを実行した際に、C/C++のソースコードを実行しようと思ったときに、共有ライブラリ上からC/C++のコードを実行することになります。
ソースコードの解説
Javaソースコード
まず、Java側では最初にSystem.loadLibraryと呼れる記述がありますこれは、共有ライブラリを読み込むための記述になります。このメソッドの引数に共有ライブラリ名を指定します。この共有ライブラリ名ですが記述が分かりにくいですが、まず、共有ライブラリのファイル名は、lib******.so(UNIX/Linuxの場合)としなければいけません。そして、メソッドで指定する共有ライブラリ命は、この******部分のみを記述することになります。例えば、共有ライブラリがlibMyApplication.soであれば、System.loadLibrary(“MyApplication”)となります。
次に、Javaではなく、C/C++で実装するメソッドには、メソッド宣言部分の戻り値の型の記述の前にnativeというキーワードを付け、そのメソッドの中身はなにも書きません。
C/C++ソースコード
C/C++ソースコードはいろいろなことを書かなければいけません。まず、インクルード・ファイルです。インクルード・ファイルは、jni.hと、Java側から吐き出されたヘッダファイル(上の例ならJniExample.h)です。jni.hには、JNIを使うための基本的な定義がいろいろと書かれています。JniExample.hには、自分の書いたJavaソースコードをC/C++で利用するために必要な定義がなされています。次に、C/C++の関数定義です。この関数定義には、Javaソースコードでnatimeキーワードをつけた関数を全て同様の形式で書かなければいけません。形式は、
JNIEXPORT {戻り値の型} JNICALL Java_{Javaの呼び出されるクラス名}_{メソッド名} ( 省略 )
です。また、引数は、上のサンプルのように記述したり、その引数の後ろに複数の引数を
追加することも可能です。ここで追加された複数の引数は、Javaの呼び出し部分でも同様に
する必要があります。
次は、Javaのメンバ変数をC/C++で読み込む作業です。これは少し、面倒な書き方をしている例ですが、このように書くことでC/C++でJavaの変数を読み込むことができます。coutの行では、読み込んだJavaの変数の値をC/C++で出力しています。正しく読み込めていますよね。最後は、C/C++で取得した変数を開放する作業です。
コンパイル方法
以上は、ソースコードの説明になりました。次は、ソースコードをどのようにコンパイルするかを具体的に見て見ましょう。コンパイルは正直いって面倒ですw。途中、Javaのバージョンによってコマンド(パス)が変わってしまうのですが、以下ではjdk1.6.0_16として話を進めます。ここの部分は自身の環境に合わせて適宜変更してください。
- 手順1:Javaプログラムのコンパイル($ javac JniExample.java)
- 手順2:Javaヘッダファイルの生成($ javah -jni JniExample)
- 手順3:C/C++プログラムのコンパイル
($ g++ -fPIC -g -c -I/usr/java/jdk1.6.0_16/include -I/usr/java/jdk1.6.0_16/include/linux JniExample.cpp - 手順4:共有ライブラリの生成($ g++ -shared -o libJniExample.so JniExample.o)
この一連でコンパイルが完了します。実行時には、共有ライブラリのPATHをカレントディレクトリに指定($ export LD_LIBRARY_PATH=***.soのパス:$LD_LIBRARY_PATH)し、Javaのプログラム実行をします($ java JniExample)。これでJavaVMが立ち上がり、JavaとC/C++を用いたプログラムが実行されます。
まとめ
以上、簡単にJavaとC/C++をコラボレーションさせたプログラムの作成方法とその実行方法を説明しました。また、今後この発展系を紹介するかもしれませんので、そのときはご参考にしてください。
参考サイト
- [PDF] JNI-C++ integration made easy
- Java Native Interface – Wikipedia
- JavaでHello World JNI編
- JNI:Java Native Interface
JNI:Java Native Interfaceプログラミング―C/C++コードを用いたJavaアプリケーション開発 (Java books) |
|
![]() |
Rob Gordon
おすすめ平均 |
関連記事