ネイティブからJavaを呼び出す

前章でやったのは、Javaプログラム内から、dll内の関数(ネイティブメソッド)を呼び出す方法となります。
これを利用する事で、OS依存のネイティブAPIをJavaから使用する事も可能になります。

今度は逆に、Windowsアプリケーションの中から、Javaのメソッドを呼び出すという方法を紹介します。
Javaのメソッドを呼び出すわけですが、正確に記述するとJavaVMを起動し、JavaVM上でJavaのメソッドを実行させることになります。

呼び出されるJavaソース

public class JNIHello2 {
    public static String getMessage(){
        return "Hello World";
    }
}

呼び出されるJavaソースファイルは、特に呼び出される事を意識する必要はなくて、普通に作成すればよいです。
今回は、ネイティブ側から単に関数を呼び出すだけなので、インスタンス生成を考えなくてよいように、staticメソッドで作成します。
(別にstaticメソッドでなければならないというわけではないです。)

C言語ソース

#include <jni.h>

#include <windows.h>

#include <stdio.h>

int main(){
    JNIEnv *env;
    JavaVM *vm;
    JavaVMInitArgs vm_args;
    JavaVMOption options[1];    //JVMオプション配列

    options[0].optionString = "-Djava.class.path=.\\classes"; /* クラスパス */

    vm_args.version = JNI_VERSION_1_2;
    vm_args.options = options;
    vm_args.nOptions = 1;    //JVMオプション個数
    vm_args.ignoreUnrecognized = TRUE;

    //JVMの作成
    int res = JNI_CreateJavaVM(&vm, (void **)&env, &vm_args);

    if (res < 0) {
        printf("Fail to create JVM.\n");
        exit(1);
    }
    //JNIHello2.classの取得(クラスパスより検索)
    jclass cls = env->FindClass("JNIHello2");
    if (cls == 0) {
        printf("Fail to find class JNIHello2\n");
        exit(1);
    }

    //取得したクラスからgetMessageというstaticメソッドを取得
    jmethodID mid = env->GetStaticMethodID(cls, "getMessage", "()Ljava/lang/String;");
    //メソッドを実行し、戻り値Stringを受け取る。
    jstring str = (jstring)env->CallStaticObjectMethod(cls, mid);
    //UTFのchar配列に変換後、コンソールに関数の戻り値を出力。
    printf("[%s]\n", env->GetStringUTFChars(str, NULL));

    vm->DestroyJavaVM();

    return 0;

}

C言語側は、Javaを呼び出すために色々なことを記述しています。詳しい事は、JNIのリファレンスを見てください。ポイントしては以下のようになります。

  • JVMを起動する(JVM起動時のオプションでクラスパスを指定する)
  • クラスをfindする
  • メソッドをGetする(型のシグニチャーを使う)
  • メソッドを実行する

型のシグニチャーは、JNIのリファレンスを参考にしてください(Java Native Interface 仕様 型のシグニチャー)。メソッドを特定するためには、パラメータの型まで特定する必要があるので、「これらのパラメータを持つ●●メソッド」という形で指定しています。(同一メソッド名でパラメータ違いのオーバーロードがあるから、メソッド名だけでは特定できません)
型のシグニチャーは呪文のような文字列なので、最初は難しいと思いましたが、慣れれば別になんともないです。

bcc32用インポートライブラリの作成

上記のC言語ソースをコンパイルするためには、jvm用のライブラリを使用する事をコンパイラに教えてやる必要があります。
VCでJNIを使う場合には、JNIリファレンスにしたがってjvm.dllをコンパイラに指示してやれば問題なくコンパイルができました。
しかし、bcc32を使う場合には、bcc32用にインポートライブラリを作成する必要があります。(私は最初これを知らなくてすごくハマった。)
bcc32からjvm.libを使用すると、「’JVM.LIB’ contains invalid OMF record, type 0x21 (possibly COFF)」といったエラーを出力してコンパイルできません。

要するに、bcc32の場合には、JDKにあらかじめ用意してあるjvm.libを使用せずに、bcc32に付属しているimplibを使用して、
jvm.dllからjvm_bcc32.lib(名前は適当)を作成しましょう。ということです。

VCを使用する場合→JDK付属にjvm.libを使用する。
bcc32を使用する場合→jvm.dllからimplibを使用して、jvm_bcc32.lib(名前は適当です)を作成しコンパイラに使用させる。

作成方法は簡単で、bcc32用のインポートライブラリ作成ツールが、bccに付属してくるので、それを使用します。
<JAVA_HOME>/jre/bin/clientで、コマンドプロンプトを開き、
> implib jvm_bcc32.lib jvm.dll
と実行するだけで、jvm_bcc32.libを作成できます。一度作成してしまえば以後はこの作成したlibファイルを使用すればOKです。

コンパイル

makeファイルはこんな感じ書きます。

JNIHello2.exe:    JNIHello2.cpp
    bcc32 -I$(JAVA_HOME)\&#105;&#110;&#099;&#108;&#117;&#100;&#101; -I$(JAVA_HOME)\&#105;&#110;&#099;&#108;&#117;&#100;&#101;\win32 -L jvm_bcc32.lib JNIHello2.cpp

コンパイルは、上記のようにインクルードするためのヘッダを指定しておきます。
あと、先ほど用意したインポートライブラリも参照します。

実行

このまますぐに実行できるわけではありません
残念ながら、Javaで書いたプログラムを、こんなに簡単にexe実行ファイルから使用できるわけではなくて、実行するためには条件があります。
その条件は、Java仮想マシンが起動できること。当然のことながら、JREがインストールされている環境でないと動かせないのです。
実行するためには、<JAVA_HOME>/jre/bin/clientに対してパスが通っている必要があります。これが通っていないとJVMを起動する事が出来ません。
このディレクトリにパスが通っている状態で、JNIHello2.exeを実行すると、 「JNIHello2.classのgetMessageメソッドを実行した結果」を出力することが確認できます。

ネイティブからJavaを呼び出す」への2件のフィードバック

  1. 初歩的な質問でも申し訳ないのですが、
    bcc32 -I$(JAVA_HOME)\include -I$(JAVA_HOME)\include\win32 -L jvm_bcc32.lib JNIHello2.cpp
    を実行すると21個のコンパイルエラーが出てしまいます。

    おそらくjvm_bcc32.libやJNIHello2.cppの置く場所が間違っているのだと思います。
    どこにおけばよいのでしょうか??

    ご教授下さい。よろしくお願いします。

    *エラーの一番最初はjni.hをオープンできないとなっています。

  2. 置く場所は、makefileを実行するカレントディレクトリでOKですが、
    ここで書いてある内容は、makefileに記述するものですが、それは問題ないですか?
    ($(JAVA_HOME)の記述などはmakefileで使用する表記です)

    bcc32でコンパイルする方法や、ここでは解説していませんが、bcc32のヘルプや解説サイトを読んでください。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください