JNIを使用して、JavaからC言語の関数を呼び出す際には、次のような流れになります。
- Javaソースの作成(呼び出し側)
- Javaソースのコンパイル(classの作成)
- Java classからヘッダファイルの生成
- ヘッダファイルに合うインターフェースでネイティブライブラリの作成
クラスファイルから、ヘッダファイルを生成する必要があるので、先にネイティブライブラリを作ることはできません。
したがって既にネイティブライブラリが出来上がっていて、それをJavaから使用したいという場合には、それをブリッジして呼び出すためのライブラリを作る必要があります。
いきなりJavaからWin32 APIを呼べるというわけではなく、Win32 APIを呼び出す関数を作成して、それをJavaから呼び出す必要があります。
ネイティブメソッドを呼び出すJavaソース
サンプルとして、HelloWorldを出力するネイティブメソッドを使う例を書いてみました。
public class JNIHello{
static {
//JNIモジュールのロード
System.loadLibrary("Native");
}
//ネイティブメソッド
native public static void sayHello();
public static void main(String[] args){
sayHello();
}
}
staticブロックで、JNIモジュールのロードというのがあるが、これがDLLのロード処理dです。Native.dllをロードするために、”Native”と指定します。(拡張子dllはつけない。これがLinux環境だとdllではなくsoだったりするから。)
4.で作成するDLLのファイル名がNative.dllだからこのように指定します。
mainメソッドが呼び出されると、sayHelloメソッドをcallしています。
sayHelloメソッドは、実装はなにもされておらず宣言だけされています。キーワードとしてnativeという宣言が付いているのが特徴です。
中身はなにも記述せず、パラメータや戻り値のインターフェースだけは決めるためにこのように記述します。中身はどこにあるかというと、native.dllに書くからです。
JNIモジュールのロードという部分をstaticブロックに記述しています。
これはJNIHelloクラスがロードされたときに一度だけdllをロードすればよいためです。もしもコンストラクタに書いた場合には、newを使ってインスタンス生成したときに、dll がロードされるようになります。2回newを行い、2つのインスタンスを生成した場合には、2回目のnewですでにdllがロードされているにも関わらず、再びロードしようとしてしまいます。staticブロックに書けばこれはOKです。
これで1.の作業は完了。そしてこれをコンパイルすれば2.は完了。
ヘッダファイルの作成
ヘッダファイルの作成は簡単です。
1.でコンパイルしたclassファイルを使用して、javahというSDK付属のツールを使用すると、このclassに必要なヘッダファイルを自動生成してくれます。
コマンドプロンプトから、classファイルのあるディレクトリに移動後、以下のコマンドを実行します。
ただし、パッケージ名がある場合にはクラスパスのルートを指定する事に注意。javahの引数で-classpathを指定しても良いです。
javah JNIHello
できあがったヘッダファイルを見ると、sayHelloのインターフェースが定義されています。
JNIEXPORT void JNICALL Java_JNIHello_sayHello
(JNIEnv *, jclass);
これをC言語で実装したDLLを作ることになるわけです。これで3.の作業は終了。
ネイティブライブラリの作成
このインターフェースを実装するかたちでC言語でDLLを作成します。WindowsだからDLLになります。
#include <stdio.h>
#include "JNIHello.h"
JNIEXPORT void JNICALL Java_JNIHello_sayHello(JNIEnv *, jclass){
printf("Hello World\n");
}
JNIHello.hと同じディレクトリにNative.cppというファイルを作成しました。
コンパイルは、こんな感じで、JDKに付属のヘッダファイルをインクルードパスで指定する必要があります。
> bcc32 -WD -I<jdk_HOME>\include -I<jdk_HOME>\include\win32 Native.cpp
JDK_HOMEと書いたのはJDKのインストールディレクトリ。これでNative.dllというDLLが作成されます。これで全作業が完了しました。
実行
実行する際には、Native.dllが適切な場所にインストールされる必要があります。
Windows環境ではPATHの通っているディレクトリに配置します。Unix関係ではLD_LIBRARY_PATHに追加します。
もしくはjavaの起動時に、
-Djava.library.path=モジュールを置いてあるディレクトリ
というようにシステムプロパティで指定することも出来ます。他の環境を汚さないことを考えれば、後者の方が他に影響を与えないので良いと思います。
色々書きましたが、今回のテストプログラム程度なら、CLASSPATH設定をしていなければ、カレントディレクトリが使用されるし、Windows環境では元々カレントディレクトリにはPATHが通っているので、カレントディレクトリにNative.dllとJNIHello.classがあれば
そのまま
> java JNIHello
とするだけで動作が確認できます。環境変数CLASSPATHを設定している場合には、
> java -cp .\ JNIHello
としてみるとよいです。
\classes\helloworld>javah JNIHello
‘javah’ は、内部コマンドまたは外部コマンド、
操作可能なプログラムまたはバッチ ファイルとして認識されていません。