トップ 差分 一覧 ソース 検索 ヘルプ PDF RSS ログイン

JNI

JNI

Javaにおいて、C/C++のモジュールを呼び出すことができる、または、逆にC/C++からJavaの機能を呼び出すことができるインターフェースを「JNI」(Java Native Interface)と言います。JNIを使用することにより、JavaではタッチできなかったOSネイティブな処理や速度の必要な処理をC/C++言語で行ったりできるようになります。

以下に、その手順を説明します。ここでは、JavaからC言語の関数を呼び出す方法を記述していきます。

Javaソースを記述

呼び出す側のJavaのソース「JNITest.java」を以下のようにします。

[ JNITest.java ]

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

  public native String getText();
  public native int add(int a,int b);
	
  public static void main(String[] args) {
    JNITest jni = new JNITest();
    String str;
	
    //C言語に関連付けた関数「getText()」を呼び出し
    str = jni.getText();
    System.out.println(str);

    //C言語に関連付けた関数「add()」を呼び出し
    int a,b;
    a=51;
    b=23;
    System.out.println("加算 : "+ a + "+" + b + "=" + jni.add(a,b));
  }
}

この場合は、(Windowsの場合)「JNITest.dll」をライブラリとして読み込みます。これの作成については後述します。Linuxの場合は、拡張子が「so」になるでしょうか。そして、「native」指定で関数「String getText()」と「int add(int a,int b)」を指定しています。この「native」が記述された関数が、JavaからCを呼び出す関数の宣言となります。ここではC言語側から、「getText()」で"HelloWorld!!"の文字列を返し、「add(int a,int b)」でa+bの計算結果を返すものとします。

「JNITest jni = new JNITest();」でネイティブの関数呼び出しのオブジェクトを生成して、各関数を呼び出しています。

C言語用ヘッダの出力

そして、C言語部分のモジュールを作成するための準備をします。まずは、以下のように普通にJavaソースをコンパイルします。

javac JNITest.java 

次に、C言語で使用するヘッダファイルを生成します。

javah -jni -o JNITest.h JNITest 

「-o JNITest.h」は出力するC言語のヘッダ名を指定していますが、省略もできます。省略した場合は、その後の「クラス名.h」が出力されることになります。最後の「JNITest」が、「JNITest.java」をコンパイルした後に生成されるクラス名を指定しています。

これで、以下のような「JNITest.h」が自動生成されます。

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JNITest */

#ifndef _Included_JNITest
#define _Included_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     JNITest
 * Method:    getText
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_JNITest_getText
  (JNIEnv *, jobject);

/*
 * Class:     JNITest
 * Method:    add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_JNITest_add
  (JNIEnv *, jobject, jint, jint);

#ifdef __cplusplus
}
#endif
#endif

C言語側の準備

次は、C言語側の準備をします。C言語で提供するモジュールは、「共有ライブラリ(DLL)」である必要があります。VC++/BCCなどで、DLLを作成する設定を行ってください。そして、インクルードファイル検索パスに「Javaのインストールディレクトリ\include」と(Windowsの場合は)「Javaのインストールディレクトリ\win32」を加えます。ライブラリファイルの検索パスに「Javaのインストールディレクトリ\lib」を加え、「jvm.lib」をリンク時の構成に加えてください。

そして、先ほど自動生成した「JNITest.h」を、コンパイラが検索できるインクルードのパス位置にコピーしてきてください。

C言語側のソースの記述

C言語側のソースは、以下の感じになります。

[JNITest.cpp]

#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "JNITest.h"      //javahで生成したヘッダを指定

BOOL APIENTRY DllMain( HANDLE hModule, 
  DWORD  ul_reason_for_call, 
  LPVOID lpReserved)
{
  return TRUE;
}

JNIEXPORT jstring JNICALL Java_JNITest_getText(JNIEnv *env, jobject o)
{
  jstring str = env->NewStringUTF("HelloWorld!!");

  return str;
}

JNIEXPORT jint JNICALL Java_JNITest_add(JNIEnv *env, jobject o, jint a, jint b)
{
  jint sum;

  sum = a + b;
  return sum;
}

このときの関数名「Java_JNITest_getText」「Java_JNITest_add」や引数などの記述は、ヘッダ「JNITest.h」よりそのままコピーしてきています。「Java_JNITest_getText」は、"HelloWorld!!"の文字列を返し、「Java_JNITest_add」は、「引数の(a+b)」を結果として返しています。

ちなみに、Windows以外のOSでは、「DllMain」の関数記述はいらないです。これをコンパイルすると、無事「JNITest.dll」が生成されます。

JNIを使ったJavaプログラムの実行

生成した「JNITest.dll」を、コマンドラインでJavaを実行するときに参照できる位置(PATHの通った位置)、もしくはカレントディレクトリにコピーします。そして、一番始めに生成したJavaの「JNITest」を実行します。

javac JNITest 

如何でしょうか?

HelloWorld!!
加算 : 51+23=74

と表示されましたか?簡単な処理をJavaからC言語呼び出しで行ったのですが、もっと時間のかかるような処理をC言語側で実行することで、 Javaのネックとなる「速度」の問題を緩和することができるようになるかと思います。総合開発環境の「Eclipse」もJNI経由で「SWT」を呼び出しているようですね。なので、動作速度がSwingに比べて快適です。

日本語文字列を返す

以下はおまけですが、日本語文字列(SHIFT-JIS)を返す場合は、その変換処理をC言語側で行ってあげる必要があります。JavaはUTF-8で文字列を扱いますので、「WindowsのC言語→Java」へは、「SHIFT-JIS→UTF-8」の変換処理が必要です。変換方法は、以下が一番楽です。

例えば、先ほどの「getText」で「はろーわーるど」と返す場合は、以下でOKです。

JNIEXPORT jstring JNICALL Java_JNITest_getText(JNIEnv *env, jobject o)
{
  const char *pBuff = "はろーわーるど";
  int len = strlen(pBuff);

  //Unicode文字列の長さを取得
  int uLen = MultiByteToWideChar( CP_ACP, 0, pBuff,
      len , NULL , 0);

  WCHAR *retBuff = new WCHAR[uLen];

  //Unicode文字列に変換する
  MultiByteToWideChar( CP_ACP, 0, pBuff,
      len , retBuff , uLen);

  //Javaの文字列生成
  jstring ret = env->NewString((jchar *)retBuff , uLen);

  delete retBuff;
  return ret;
}

ただ、OS依存の「MultiByteToWideChar」はWindows以外では使えませんので、各OS対応の変換処理を行うか、Javaの「java.lang.String」をマッピングして変換してやるといいかもしれません。#昔、仕事で「java.lang.String」を呼び出して対応した覚えがあるのですが、ソースが見つからない(^_^;;

Future's Laboratory 技術格納庫 2004-2013 Yutaka Yoshisaka.