Movatterモバイル変換


[0]ホーム

URL:


S-JIS[2006-06-18/2024-09-23]変更履歴

JNI(Java Native Interface)

JavaのJNIのメモ。


概要

JNIは、JavaからC言語/C++の関数を呼ぶ・あるいは逆にC/C++からJavaのメソッドを呼ぶための仕組み。

C言語またはC++で作ったライブラリを、Windowsの場合はDLLファイル、UNIXの場合は共有オブジェクト(so)ファイルにしておき、Javaから呼び出すことが出来る。
また、C言語/C++で作ったソースの中からJavaオブジェクトのメソッドを呼び出すことが出来る。
ただし当然 呼び出し方はJavaとC/C++(ネイティブ)とで整合性をとる必要があり、それがJNIである。


Java22以降ではJNIより外部関数およびメモリーAPIを使う方が良いと思われる。[2024-09-23]


全体の手順

JNIでライブラリを作るには、以下のような手順で作業する。

  1. 呼び出す側のプログラム(とりあえずは宣言だけでよい)をJavaで書く。
  2. ひとまずJavaのソースをコンパイルしてclassファイルを生成する。
  3. javahを使ってclassファイルから C言語/C++のヘッダーファイルを生成する。
    build.xmlを書いておくと便利
  4. 生成したヘッダーファイルの実装をC言語/C++で書く。→実装方法
  5. C言語/C++のソースをコンパイルしてDLL(SO)ファイルを生成する。
    JNI用ヘッダーファイルを探しに行く場所の指定が必要。
    →VC++の場合、ビルドしたDLLをEclipseのワークスペースに自動コピーするよう設定すると便利
  6. JavaでDLL(SO)ファイルを呼び出して実行する。

ある程度の生成作業は、makefileにしたりbuild.xmlにしたりして自動化できる。


宣言の作成

まず、Javaでライブラリの関数を宣言する。
nativeというキーワードを付け、関数本体を書かない。(インターフェースでメソッドを宣言するようなもの)

JniJikken.java:

public class JniJikken {privatenativebyte[]copy(String src);}

このファイルをコンパイルし、JniJikken.classを生成しておく。

メソッド本体の例


ヘッダーファイルの作成

javahというコマンド(javacやjavaと同じディレクトリに入っている)を使って、C言語/C++のヘッダーファイルを生成する。
この生成には、nativeを宣言したJavaのクラスファイルを入力として使う。

>javah -classpathクラスのあるディレクトリ -d生成先ディレクトリクラス名>javah -classpathclasses -d%VCPP%\JniJikkenJniJikken

この例では、classesディレクトリ直下にあるJniJikken.classを元に、VC++のJniJikkenというディレクトリの下にJniJikken.hを生成する。

javahは優秀なので(笑)、何度実行しても 宣言が新しくなっていない限りヘッダーファイルを更新しない。
(-forceオプションを付ければ常に上書きする)

→Sunのjavah - Cヘッダーとスタブファイルジェネレータ

Eclipseを使っている場合は(そうでない場合でも)、Ant用のbuild.xmlバッチファイルを作っておけば便利だろう。


なお、クラスがパッケージ内に存在する場合は、以下の様にパッケージを指定する。

>javah -classpathclasses -d%VCPP%\JniJikkenjp.hishidama.jni.JniJikken

生成されるヘッダーファイルの名前はパッケージを含んだ形になる。
すなわち「jp_hishidama_jni_JniJikken.h」となる。ちょっとくどいよね…
(生成される場所はあくまで指定したディレクトリ直下であり、パッケージ構成のディレクトリが作られるわけではない。)


C言語/C++での実装

生成したヘッダーファイルは、以下の様になっている。

JniJikken.h

/* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h>/* Header for class JniJikken */#ifndef _Included_JniJikken#define _Included_JniJikken#ifdef __cplusplusextern "C" {#endif/* * Class:JniJikken * Method:copy * Signature: (Ljava/lang/String;)[B */JNIEXPORT jbyteArray JNICALLJava_JniJikken_copy  (JNIEnv *, jobject, jstring);#ifdef __cplusplus}#endif#endif

JniJikken.javaで「copy」という名前で宣言したメソッドが、「Java_JniJikken_copy」という関数になっている。
この関数をC言語あるいはC++で実装してやることになる。

Javaでのbyte[]やStringは、jbyteArray・jstringといった型(実体は構造体のポインター)で表現される。
これらはjni.h(及びその中でincludeしているjni_md.h)で定義されている。

JniJikken.c: JniJikken.cpp:

#include "JniJikken.h"JNIEXPORT jbyteArray JNICALL Java_JniJikken_copy  (JNIEnv *env, jobjectthisj, jstringsrcj){return NULL; /*ひとまずコンパイルを通す為、意味のあることは書かない*/}

関数定義はヘッダーファイルから持ってきて、変数を追加すればいいだろう。
最初の変数は皆envにしているようだ。

2つ目の引数は、Javaから呼び出した際のクラスのインスタンス(を表すもの)が入ってくる。
したがって名前をthisとするのが雰囲気的にはいいのだが、C++ではthisがキーワードとして使われているので、別のものにしなければならない。
単純にobjとしている人も多いようだが、自分はthisjにしておく。VBでthisに相当するものがMeだからmeでいいかと思ったけど、統一の為)
なお、staticメソッドの場合は、jobjectでなくjclassになる。[2007-09-15]

3つ目以降の引数はJava側で定義したものなので、基本的にそちらの変数と同じ名前でいいだろう。
が、実際に使う場合にはC言語の変数を作ってやることが多いので、Javaの引数であるということを明示する為に、自分は末尾に「j」を付けている。(srcの場合はsrcj)(変数の先頭に付けないのは、そうするとJNIのクラス(jstringとか)と紛らわしいから)

実際の関数の中の書き方は後述


C言語/C++のコンパイル

出来上がったC言語/C++のソースをコンパイルして、DLL(Windowsの場合)やSO(UNIXの場合)を生成する。
(UNIXの共有オブジェクト(SO)のファイル名は、「lib」で始まらないといけないらしい)

その際、jni.h およびその中でincludeしているjni_md.hがある場所をコンパイラーに指示してやる必要がある。
たいていのコンパイラーは「-I」オプションでインクルードパスを指定できる。

ファイル概要場所
jni.hJNIの各構造体の定義javaをインストールしたディレクトリ/includeC:\j2sdk〜\includeWindows
$JAVA_HOME/includeUNIX
jni_md.h機種依存部分の定義
jni.hからincludeされている。
jni.hのあるディレクトリの下C:\j2sdk〜\include\win32Windows
/usr/java/include/solarisSolaris
$JAVA_HOME/include/linuxLinux

Solarisの例:

% cc-G-I /usr/java/include-I /usr/java/include/solaris JniJikken.c-olibJniJikken.so

Solarisでのmakefileの例:

all: libJniJikken.solib%.so: %.c %.hcc -G$< -I /usr/java/include -I /usr/java/include/solaris -o$@%.h: %.classjavah$*%.class: %.javajavac$<

↑この例の場合、javaのコンパイル・javahでの生成まで含んでいる。

VC++の例:

インクルードパスの追加方法参照。
↓JDK1.6の場合

C:\Program Files\Java\jdk1.6.0\include,C:\Program Files\Java\jdk1.6.0\include\win32

Javaから実行

DLL又はSOファイルを作ったら、Javaから呼び出してやる。

JniJikken.java:

public class JniJikken {private native byte[] copy(String src);public static void main(String[] args) {System.loadLibrary("JniJikken");String src = "testテスト";JniJikken me = new JniJikken();byte[] dst = me.copy(src);System.out.println(dst);}}

DLL又はSOファイルを実行時に読み込むために、「System.loadLibrary」を最初に呼んでやる必要がある。
この引数には、DLLやSOファイル名の中核部分を指定する。(JniJikken.dlllibJniJikken.so

最初に一度だけ確実に実行する仕組みとして、staticブロック(静的初期化子)で呼ぶ人も多い。

public class JniJikken {static {System.loadLibrary("JniJikken");}private native byte[] copy(String src);public static void main(String[] args) {〜}}

このloadLibraryを実行し忘れると、nativeメソッド(この例ではcopy)の実行時に以下のような例外が発生する。

java.lang.UnsatisfiedLinkError: copyat JniJikken.copy(Native Method)at JniJikken.main(JniJikken.java:11)Exception in thread "main"

そして、loadLibraryで指定されたDLLやSOファイルをjavaが実行時に探す為には、環境変数を設定しておく必要がある。

Windowsの場合、環境変数PATHにDLLファイルのパスが入っていなければならない。
Eclipseの場合はアプリケーション毎にPATHを指定できる。

UNIXの場合は、環境変数LD_LIBRARY_PATHにSOファイルのパスが入っていなければならない。

環境変数を設定しない場合は、実行時引数を以下の様に設定してやる。

> java -Djava.library.path=DLLやSOのディレクトリJniJikken

loadLibraryの実行時に 指定されたDLLやSOファイルが見つからないと、以下のような例外が発生する。

java.lang.UnsatisfiedLinkError: noJni_Jikken injava.library.pathat java.lang.ClassLoader.loadLibrary(Unknown Source)at java.lang.Runtime.loadLibrary0(Unknown Source)at java.lang.System.loadLibrary(Unknown Source)at JniJikken.main(JniJikken.java:8)Exception in thread "main"
System.loadLibrary("Jni_Jikken");

java.library.path」が、環境変数PATHLD_LIBRARY_PATHを意味している。
(実際、Windowsの場合、System.getProperty("java.library.path")で取って来られる内容は、環境変数PATHの内容だ。)
この例では、Windowsの場合DLLファイルの「Jni_Jikken.dll」が、UNIXの場合SOファイルの「libJni_Jikken.so」が見つからない。


参考


Javaへ戻る /技術メモへ戻る /C言語/C++の実装方法へ行く /JNIエラーへ行く
メールの送信先:ひしだま

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


[8]ページ先頭

©2009-2025 Movatter.jp