11.2 nativeメソッド
11.2.1 システムを呼び出す
Java 仮想マシンがどんなに抽象化された技術であっても、バイトコードの行く先は、最終的にオペレーティング・システムのサービスとなります。画面に文字を描画したり、プリンタに出力したり、ディスクにデータを保存するには、オペレーティング・システムの許可が必要です。
本来、この作業のすべては Java が標準で装備しているライブラリの役割です。Java 標準クラスライブラリは、ただの便利なクラス群ではありません。実体システムの機能を抽象化する仲介役と考えることができます。これまで、多くの入出力ストリームを扱ってきましたが、これらのクラスは最終的にネイティブなシステムの機能を呼び出しています。
どこでも動くという Java の役割から考えれば、アプリケーション用のコードがシステムを呼び出すのは避けなければなりません。Windows API を呼び出す Java バイトコードは、確実に Windows に依存したソフトウェアとなってしまうからです。他のシステムで実行すれば、メソッドが見つからないことによる例外が発生してしまうことでしょう。
しかし、Java 標準ライブラリには実装されていない特殊な処理が必要な場合や、速度が要求されるソフトウェアの開発に Java 言語を使いたい場合などはネイティブ・コードを呼び出すのも手段でしょう。設計力が必要になりますが、標準C/C++ と Java の通信であれば、移植性もある程度の範囲で守ることができます。
ネイティブ・コードを呼び出す方法は、実装する仮想マシンとシステムの事情で異なりますが、すばらしいことに Java はネイティブ・コードと通信する包括的な手段 JNI (Java Native Interface) を定めています。JNI を用いることによって、C/C++ 言語で開発したライブラリの関数を呼び出したり、逆にネイティブ・コードから Java オブジェクトを参照することができるようになります。
JNI によって開発されたライブラリのコードを Java 言語から呼び出すには、ソースのどこかでメソッドを宣言しなければなりません。このメソッドの本体は外部に存在するため、抽象メソッドと同様に本体を持たないメソッド宣言となります。このようなメソッドを宣言するには native 修飾子 を指定しなければなりません。次の n メソッドは native 修飾子を指定した実体を持たないメソッドです。
private native void n();
abstract メソッド同様に、本体を持たないためセミコロン ; で終了しています。このメソッドはネイティブ・コードで実装しなければなりません。native 修飾子と abstract 修飾子を同時に指定することはできません。
native メソッドを正しく呼び出すには、宣言したメソッドのネイティブ・コードがどこに存在するかという問題が発生します。システムのライブラリを Java 仮想マシンに読み込ませるには System クラスの loadLibrary() メソッドを使います(10.1 標準入出力とストリーム.表1)。このメソッドの引数にライブラリの名前を指定すれば、仮想マシンがコードをメモリに読み込み、リンクしてくれます。ただし、ネイティブ・コードが JNI で定められている仕様にしたがっていなければ、メソッドの実装を発見することはできません。
loadLibrary() メソッドの引数に指定するライブラリの名前は、拡張子を除いたファイル名です。拡張子は仮想マシンの実行システムに応じて適切なものが与えられます。例えば、"Test" という名前のライブラリを指定した場合、Windows 環境であれば Test.dll という名前のファイルが検索され Linux 環境であれば libTest.so が検索されます。
loadLibrary() を使ってコードを読み込むことができるのは、動的リンクがサポートされているシステムのみです。動的リンクとは、プログラムの部品をいくつかのファイルに分割し、必要なものだけをメモリにロードして使用することができるソフトウェア技術です。基盤システムが動的リンクをサポートしていない場合、Java 仮想マシンと必要なネイティブコードをあらかじめリンクさせておかなければなりません。
class Test { static { System.loadLibrary("Test"); } public native void nativeMethod(); private final String name; public Test(String name) { this.name = name; } public static void main(String[] args) { new Test("Blue Blue Glass Moon,").nativeMethod(); new Test("Under The Crimson Air.").nativeMethod(); } public String toString() { return super.toString() + ",name=" + name; } }
コード1は、native 修飾子を指定した実装を持たないメソッド nativeMethod() を宣言しています。このメソッドは実装しなくてもコンパイルすることができますが、この状態で実行してもメソッドが見つからないため、実行時に仮想マシンがエラーを出すことでしょう。
プログラムでは、仮想マシンの起動時に Test クラスの静的初期化子によって Test ライブラリが読み込まれます。この Test ライブラリの中に nativeMethod() を実装するコードが記述されていなければなりません。main() メソッドでは Test クラスのインスタンスを生成し、このオブジェクトで nativeMethod() メソッドを呼び出しています。このメソッドの結果がどのようになるのかは、ネイティブ・コードによる実装次第です。
JNI の仕様は初心者には難しすぎるので理解する必要はないでしょう。また、ネイティブ・コードの呼び出しは Java の最大の魅力である「どこでも動く」を捨てることにもなります。このような機能が Java には存在するということを知っていれば十分です。
実際には JNI の仕様はかなりスマートで、複雑なことをしている割にそれほど難しいものでもありませんが、正しく理解するためには C/C++ 言語の知識経験が問われます。
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class Test */ #ifndef _Included_Test #define _Included_Test #ifdef __cplusplus extern "C" { #endif /* * Class: Test * Method: nativeMethod * Signature: ()V */ JNIEXPORT void JNICALL Java_Test_nativeMethod (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif
#include <stdio.h> #include "Test.h" JNIEXPORT void JNICALL Java_Test_nativeMethod(JNIEnv *env, jobject obj ) { jclass testClass = env->FindClass("Ljava/lang/Object;"); jmethodID mid = env->GetMethodID(testClass , "toString" , "()Ljava/lang/String;"); if (mid == NULL) { printf("Object.toString() メソッドが見つかりませんでした"); return; } jstring jstr = (jstring)env->CallObjectMethod(obj , mid); const char *nstr = env->GetStringUTFChars(jstr, 0); printf("ネイティブコードとして実行しています\njobject:%s\n" , nstr); env->ReleaseStringUTFChars(jstr , nstr); }
>java Test ネイティブコードとして実行しています jobject:Test@6b97fd,name=Blue Blue Glass Moon, ネイティブコードとして実行しています jobject:Test@c78e57,name=Under The Crimson Air.
コード2の Test.h と Test.cpp は JNI 1.1 仕様に従って C++ 言語で開発した から呼び出すことができるネイティブ・コードです。Test.java で宣言されている nativeMethod() を C++ 言語を用いて実装しています。驚くべきことに、単純に Java からネイティブ・コードを呼び出すだけではなく nativeMethod() メソッドを呼び出した Java オブジェクトの情報に、C++ 言語で書かれたネイティブ・コードからもアクセスしています。実行結果を見れば、Java オブジェクトの toString() メソッドが呼び出されたことを確認できます。JNI を用いれば、ネイティブ・コードと Java のデータを送受信する双方向な関係を築くことができるのです。
JNI の詳細については JDK に付属しているドキュメントの Java Native Interface を参照してください。