JNI
2003.04.10 update.

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」を呼び出して対応した覚えがあるのですが、 ソースが見つからない(^_^;;