読者です 読者をやめる 読者になる 読者になる

chikuchikugonzalezの雑記帳

趣味とか日記とかメモとか(∩゚д゚)

JNIを使ったJavaプログラムをCygwinのGCJでexe化した時のメモ

まぁ、まずはJNIから

JavaにはJNIっていう、メソッドをC/C++で書ける機能があります *1
例えば次のようにmainすらnative化出来たりしますね

public class HelloWorld {
    static {
        System.loadLibrary("hw");
    }

    public static native void main(String[] args);
}

これを実行できるようにするにはクラスファイルの作成とネイティブ部分のDLLを作るという二度手間になります。
まぁ、まずはネイティブ用のヘッダを吐き出して、

$ javac HelloWorld.java    # コンパイル
$ javah HelloWorld         # ネイティブ関数用ヘッダを作成

このヘッダを使ったプログラムを次のように実装しておきます。

#include <iostream>
#include "HelloWorld.h"

JNIEXPORT void JNICALL Java_HelloWorld_main(JNIEnv* env, jclass cls, jobjectArray args) {
	std::cout << "Hello, World!" << std::endl;
	jsize length = env->GetArrayLength(args);
	for (jsize i = 0; i < length; i++) {
		jstring arg = (jstring) env->GetObjectArrayElement(args, i);
		const char* buf = env->GetStringUTFChars(arg, NULL);
		std::cout << "Arg " << i << " = " << buf << std::endl;
		env->ReleaseStringUTFChars(arg, buf);
	}
}

で、これをDLLにします。まずはVC++の場合

$ cl /nologo /EHsc /I%JAVA_HOME%\include /I%JAVA_HOME%\include\win32 /Fehw HelloWorld.cpp

次にGCCの場合。あ、ココで使ったGCCはTDM-GCCの4.6.2です。

$ g++ -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 -c HelloWorld.cpp
$ g++ -shared -o hw.dll HelloWorld.o -Wl,--kill-at

でまぁ、これでhw.dllが出来たので *2 実行すれば「Hello, World!」に続いて与えた引数がだらだらと垂れ流されます。

で、GCJ

GCJってのはGNUGCC系列にあるJavaコンパイラです。Javaのソースもしくはクラスファイルから実行可能ファイルを作成できるネイティブコンパイラですね。
ただWindowsだとCygwinGCCでしか利用できないのがネックですが *3

ようやく本題

問題はここで、上記のWindows DLLを使ったJavaプログラムをCygwin GCJでネイティブ化した時にどうやってnativeメソッドを使えるようにするか、です

GCCで作ったDLLの場合

ビルド時に-Wl,--kill-atとか-sharedを付けた効果かはよくわかりませんが、hw.dllをlibhw.dllにするだけで動きます。というかコピーとかいらずにhw.dll→libhw.dllというシンボリックリンクでも無問題でした。さすがや(∩´∀`)∩ワーイ *4

VC++で作ったDLLの場合

GCC版で気を良くしたのでシンボリックリンクにしたらUnsatisfiedLinkErrorが出おった(´・ω・`)

なんでかというと、JNIEXPORTついてるけど__declspec(dllexport)になってないようでエクスポートされてなかった。仕方ないので.defを作る。

$ nm HelloWorld.obj | grep -e "T _"

ってするとそれっぽい関数の定義が出てくるので、最後の@以降を削った上で.defに書く。出来上がる.defは↓のようにEXPORTS行の下に関数名を並べるらしい。

EXPORTS
Java_HelloWorld_main

それで、DLL作成時にコレも渡してやる。

$ cl /nologo /EHsc /I%JAVA_HOME%\include /I%JAVA_HOME%\include\win32 /Fehw HelloWorld.cpp hw.def

こうして出来上がったDLLならlibhw.dllにシンボリックリンク貼るもしくはリネームで利用可能になりました

参考

余談

DXライブラリをJavaから呼び出す妄想しててふと思ったのが「exe化必要じゃね?」だったのが始まり。
exewrapでもよかったんだけどjre入れるのもなぁ('A`) とか無駄に考えた結果「GCJ使えんじゃね?」だった。
でもTDM-GCCには入ってないので悲しみ背負いかけた

*1:Javaの利点たるWrite once, run anywhereを完全に捨てることになりますが

*2:hw.dllの名前はSystem.loadLibraryで使っている名前です

*3:MinGW版をリンクしようとして-mno-cygwinをつけるとGCJが怒る(´・ω・`)

*4:コンパイラバージョン違うんですが、そんなのは関係無かったようです