JVMがOSのファイルシステム参照しとる...のは置くとしてJNI(Java Native Interface)でハマる

f:id:ts0818:20201209201607j:plain

の枝を曲げた輪に糸を張って蜘蛛の巣に見立て、悪夢を捕らえ防いでくれるよう願った。 オジブウェー語ローマ字ではasabikeshiinhと記され、クモまたはクモの巣を意味する

ドリームキャッチャー (装飾品) - Wikipedia

テリ・J.アンドリューズ(Terri J. Andrews)によると、『悪夢は網目に引っかかったまま夜明けと共に消え去り、良い夢だけが網目から羽を伝わって降りてきて眠っている人のもとに入る』とされる。 また、『良い夢は網目の中央にある穴を通って眠っている人に運ばれてくるが、悪夢は網目に引っかかったまま夜明けと共に消え去る』とも言う

ドリームキャッチャー (装飾品) - Wikipedia

⇧ 良い夢を見ることのないまま、時の流れを感じ続ける、どうもボクです。

ドリームキャッチャー」ってそんな意味があったんですね、夢をフィルターしてくれていたとは目から鱗です! 

そんなネイティブの叡智を知ったところで、今回もJavaについてです。

 

ちなみに、

www.orangeitems.com

⇧ まさかの「CentOS」系に衝撃のニュースが!

 

激震が走っておりますが...

レッツトライ~。 

 

JVMJava Virtual Machine)はOS(Operation System)のファイルシステムを参照してると

事の発端は、

github.com

⇧ を実施していて、「gcc」でコンパイルした「DLL(Dynamic Link Library)」の場所が見つからんって言われたんですよね。

ちなみに、

stackoverflow.com

stackoverflow.com

JDK 10 からは「javah」が削除されて、「javac」の「h」オプションを使って「DLL(Dynamic Link Library)」を作る感じになった模様、時の流れ~。

 

で、ネットで調べてたところ、

blogs.osdn.jp

Java では System.load() または System.loadLibrary() を使用してネイティブライブラリー DLL をロードすることができます。これらにはライブラリーのフルパスを指定するか ライブラリー名のみを指定するかという違いがあります。

実行時にjava.library.pathを変更する

ライブラリーのフルパスを指定する場合は System.load() を使用します。

実行時にjava.library.pathを変更する 

ライブラリー名を指定する場合は System.loadLibrary() を使用します。このとき java.library.path プロパティーで設定されているパスからライブラリーが検索されることになります。

実行時にjava.library.pathを変更する

⇧ 上記サイト様によりますと、「ライブラリーのフルパスを指定」ができるってあったんですよ。

な~に~!やっちまったな!(クールポコ先輩~) 

というのも、「パス」ってのは、「OS(Operation System)」の「ファイルシステム」によって差異があるんですと。

 

Linux環境にJDKをインストールして発覚

何で気付いたかと言うと、Linux環境にはWindowsのようにドライブって概念がないんですよ。

つまり、「System.load()」を使う場合は、

OS(Operation System)がWindowsの場合は、

System.load("C:/mylibs/foobar.dll");

OS(Operation System)がLinuxの場合は、

System.load("/home/foobar.so");

みたいな感じで、OSによって変わってきますと。

そう!、「System.load()」は、OSに依存してしまうってわけなんですよ。

 

ちなみに、「System.loadLibrary()」なら大丈夫かと言うと、さにあらず。

java.library.path」ってのに影響されますと。

なので、「java.library.path」の中身を確認してみました。

 

Linux環境

CentOS 8(64bit。「Docker Hub」の公式の「centos」イメージ)

Java:OracleJDK 15のLinux版(RPMパッケージ)

f:id:ts0818:20201208194746p:plain

Windows環境

Windows 10 Home(64bit)

Java:OracleJDK 15のWindows版(アーカイブ

f:id:ts0818:20201208195025p:plain

 

ってな感じで、 OSによってまるきり変わってくるらしいですと。

Linux環境に至っては、

f:id:ts0818:20201208201533p:plain
⇧ そもそも存在しないディレクトリも「java.library.path」に含まれてるっぽい...

そして「java.library.path」の追加を試みるも、

f:id:ts0818:20201208201942p:plain

⇧ 駄目っすな...

java.library.path」の1つである「/usr/java/packages/lib」ディレクトリを用意して、ライブラリを配置してみたけど、

f:id:ts0818:20201208203435p:plain

⇧ 見つからんって言われるし...

 

mo3789530.hatenablog.com

Javaからdllを使う場合に java.lang.UnsatisfiedLinkErrorが発生するので原因と回避方法を調べました。 OSが32ビットの時は問題なく動作していたが64ビットOSに変更した際動かくなったという情報だけはありました

javaからDLLをコールすると java.lang.UnsatisfiedLinkErrorが発生する - moのブログ

エラーの原因は単純で64ビットJavaから32ビットのdllを呼ぼうとしているのが原因でした。

javaからDLLをコールすると java.lang.UnsatisfiedLinkErrorが発生する - moのブログ

⇧上記サイト様を参考に確認してみる。

f:id:ts0818:20201208214813p:plain

⇧ 駄目でした...

ばっちり64bitで「DLL(Dynamic Link Library)」作成されてるっぽい...

念のため、JDKも確認してみるけど、

f:id:ts0818:20201208215704p:plain

⇧ ばっちり64bitでした...

あとは、「DLL(Dynamic Link Library)」の作り方かな。 

 

javac -h [C言語のヘッダファイルの出力先のディレクトリを指定] Answer019.java
gcc -shared -D__int64='long long' -I/usr/java/jdk-15.0.1/include -I/usr/java/jdk-15.0.1/include/linux -c Answer019.c -o libAnswer019.so

 

ちなみに、自分の環境では、以下がJNI(Java Native Interface)で必要なDLL(Dynamic Link Library)ライブラリを作成するために、使用するファイル群かと。

/usr/java/jdk-15.0.1/include
/usr/java/jdk-15.0.1/include/classfile_constants.h
/usr/java/jdk-15.0.1/include/jawt.h
/usr/java/jdk-15.0.1/include/jdwpTransport.h
/usr/java/jdk-15.0.1/include/jni.h
/usr/java/jdk-15.0.1/include/jvmti.h
/usr/java/jdk-15.0.1/include/jvmticmlr.h
/usr/java/jdk-15.0.1/include/linux
/usr/java/jdk-15.0.1/include/linux/jawt_md.h
/usr/java/jdk-15.0.1/include/linux/jni_md.h

 

そして、衝撃の結末...

oshiete.goo.ne.jp

ファイル名をlibで始まるように変更したところうまくいきました.
Javaで呼ぶ方では「lib」付けなくていいんですね.

JNIでロードするライブラリが見つからない -JNIを使ってライブラリを呼- Java | 教えて!goo

⇧ な、何やて~!

何なの、その特殊ルール、

 

docs.oracle.com

Java Native Interface 6.0の仕様では、SolarisLinux、およびWindowsプラットフォーム上でJNIメカニズムを使用してオブジェクトを表示するためにAWTパッケージがどのように設計されているかについて説明します。

https://docs.oracle.com/javase/jp/8/docs/technotes/guides/jni/index.html

docs.oracle.com

System.loadLibraryの引数は、プログラマによって任意に選択されたライブラリ名です。このシステムは、標準であってもプラットフォーム固有の方式に従ってライブラリ名をネイティブ・ライブラリ名に変換します。たとえば、Solarisシステムはpkg_Clsという名前をlibpkg_Cls.soに変換するのに対して、Win32システムは同じpkg_Clsという名前をpkg_Cls.dllに変換します。

https://docs.oracle.com/javase/jp/8/docs/technotes/guides/jni/spec/design.html

⇧ う~ん、「Linux」も「Solaris」と同様と考えて良いのであれば、

  • 「libpkg_Cls.so」 ってライブラリを作成
  • JavaファイルでSystem.loadLibrary()の引数には、「pkg_Cls」を設定

 ってことになるのか...

 

修正してみて実施したところ、

Java HotSpot(TM) 64-Bit Server VM warning: You have loaded library /usr/java/packages/lib/libAnswer019.so which might have disabled stack guard. The VM will try to fix the stack guard now.
It's highly recommended that you fix the library with 'execstack -c <libfile>', or link it with '-z noexecstack'.
Exception in thread "main" java.lang.UnsatisfiedLinkError: /usr/java/packages/lib/libAnswer019.so: /usr/java/packages/lib/libAnswer019.so: only ET_DYN and ET_EXEC can be loaded
...省略

どっちにしろエラーやんけ~!

ただ、エラー前の警告の内容を見てみると、「ライブラリ」を修正することを進めるってあるんで、 「gcc」でのコンパイルのオプションを追加すればいけそう?

関係ありませんでした...

 

ただ、Linux環境の場合は、 

kiririmode.hatenablog.jp

www.wagavulin.jp

alpha-netzilla.blogspot.com

⇧ 「java.library.path」の他に「LD_LIBRARY_PATH」ってやつを設定してあげる必要があるみたいね。

tldp.org

命名規則とかもあるようね。

 

そして、申し訳ない、「Answer019.c」でタイポ(打ち間違い)がありました。

■誤

#include <stdio.h>
#include <stdlib.h>
#include "Answer019.h"
// ランダムで返す整数の最大値
#define MAX 7

JNIEXPORT jint JNICALL java_Answer019_randomNum (JNIEnv *env, jobject obj) {
	return rand() % MAX +1;
}

■正

#include <stdio.h>
#include <stdlib.h>
#include "Answer019.h"
// ランダムで返す整数の最大値
#define MAX 7

JNIEXPORT jint JNICALL Java_Answer019_randomNum (JNIEnv *env, jobject obj) {
	return rand() % MAX +1;
}

おわかりいただけただろうか?

そう、7行目の「Java_Answer019_randomNum」の部分の先頭が小文字の「j」になってしまっていたのである...

自分が悪いんだけど、これに気付いた時には絶望しか感じませんでしたね...

修正後、「gcc」 でライブラリを作成して、

gcc -fPIC -I"/usr/java/jdk-15.0.1/include" -I"/usr/java/jdk-15.0.1/include/linux" Answer019.c -shared -o libAnswer019.so

Javaを実行(クラスファイルを既に作成済みです)

java Answer019

f:id:ts0818:20201209195557p:plain

⇧ 無事動きました!


というわけで、タイポ(打ち間違い)には気を付けましょう。

コピペできる状況の時は、コーディングしてくれた方に感謝の気持ちを抱きつつ積極的にコピペさせていただきましょう。

1人で生きてるんじゃないんだよ~!、みんなの支えがあってこその自分だよ~!

っていう気持ちになったところで、

今回はこのへんで。