特集:組み込みにも役立つJavaとネイティブコードの橋渡し

JNIより簡単にJavaとC/C++をつなぐ「JNA」とは


富士ソフト株式会社
高見 誠
2009/12/14


JNAを利用するための基本的な流れ

 まず、Windows APIのprintfを呼び出すサンプルソースコードを見てみましょう。

  リスト1 Windows APIのprintfを呼び出すJavaのサンプルソースコード
 1 import com.sun.jna.Library;
 2 import com.sun.jna.Native;
 3
 4 public class HelloWorld {
 5     public interface CLibrary extends Library {
 6         CLibrary INSTANCE = (CLibrary)
 7                              Native.loadLibrary("msvcrt" , CLibrary.class);
 8    
 9          void printf(String format, Object... args);
10     }
11
12     public static void main(String[] args) {
13         CLibrary.INSTANCE.printf("Hello, World\n");
14     }
15 }

 JNAでは、ネイティブライブラリをJavaのインターフェイスでマッピングします。5行目では、ネイティブライブラリにマッピングするインターフェイスCLibraryを宣言しています。インターフェイスCLibraryは、JNA提供のインターフェイスLibraryを継承します。

 6・7行目では、ターゲットのネイティブライブラリをロードします。ここで、APIのprintfはWindowsのmsvcrt.dllに実装しているので、msvcrt.dllを指定しますが、自己開発のネイティブライブラリにアクセスする場合、そのネイティブライブラリ名を指定します。同時に、ネイティブライブラリにマッピングするインターフェイスも指定して、ネイティブライブラリとJavaインターフェイスとの関連を付けます。

 ライブラリマッピングの指定後、9行目では、メソッドのマッピングで、呼び出すネイティブAPIのJavaメソッドを宣言します。Javaのメソッド名とシグネチャは、ネイティブAPIと一致しなければなりません。

 13行目からは、普通のJavaメソッドを呼び出すように、ネイティブライブラリをアクセスしています。

C/C++コードを1行も書かず、ネイティブAPIを呼び出し!

 ご覧のとおり、C/C++コードを1行も書かず、ネイティブAPIを呼び出せました。実は、プリミティブデータ型しか使わないAPIだけなら、JNA 3.2.0から、5行目のインターフェイスも書かず、もっと簡単に呼び出せます。詳細はJNAサイトを参照してください。

 本文では、自定義データ型の呼び出し方法も説明する都合上、サンプルソースの方法で記述します。

同様の機能をJNIで実現すると……

 比較のために、Windows環境で同様の機能をJNIで実現する手順とソースコードを見てみましょう。

 呼び出す側のJavaクラスファイルを作成します。この手順は、JNAと同じです。

  JNIの場合(Java側)
 1 public class HelloWorld {
 2     public native void printf(String message);
 3     static {
 4         System.loadLibrary(“MyNativeLib”);
 5     }
 6
 7     public static void main(String[] args) {
 8         HelloWorld obj = new HelloWorld();
 9         obj. printf(“Hello, World\n”);
10     }
11 }

 作成したJavaクラスファイルをコンパイルします。専用コマンドで、JavaクラスファイルのCのヘッダーファイルを生成します。

javah -jni -classpath .\classes HelloWorld

 以下のHelloWorld.hが生成されます。

  HelloWorld.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
 
/* Header for class HelloWorld */
#ifndef _Included_HelloWorld
#define _Included_HelloWorld
#ifdef __cplusplus
 
extern "C" {
    #endif
    /*
     * Class:     HelloWorld
     * Method:    printf
     * Signature: (Ljava/lang/String;)V
     */
    JNIEXPORT void JNICALL Java_HelloWorld_printf(JNIEnv *, jobject, jstring);
 
    #ifdef __cplusplus
}
#endif
#endif

 Cプログラムで、引数のデータ変換、ネイティブAPIの呼び出し、戻り値のデータ変換を実装して、HelloWorld.cppファイルとして保存します。

  HelloWorld.cpp
 1 #include "HelloWorld.h"
 2 
 3 JNIEXPORT void JNICALL Java_HelloWorld_printf(JNIEnv *env, jobject o, jstring s) { 
 4     const char *pMessage = env->GetStringUTFChars(s, 0);
 5     ::printf(“%s”,pMessage);
 6     env->ReleaseStringUTFChars(s, pMessage);
 7 }

 コンパイルして、Javaプログラムからアクセスする中間のネイティブライブラリMyNativeLib.dllを作成します。

 決まりの手順でも難しくはありませんが、手間が掛かるのに加えて、データ変換のために、なじみのないCプログラムを実装しなければならないと考えると、JNAを利用するメリットは、一目瞭然でしょう。

JavaとC/C++のマッピング方法、6パターン

 異なる言語間の呼び出しは、データのマーシャリングが一番のポイントです。

 JNAでは、多くのJavaのプリミティブデータ型に対してCのプリミティブデータ型を自動的にマッピングしてくれます。直接マッピングできない型は、JNA独自型で対応したり、配列型のデータには、豊富な補助ユーティリティの提供でサポートされています。

 以降は、マッピング方法について、紹介していきたいと思います。

  1. ライブラリのマッピング
  2. APIのマッピング
  3. プリミティブデータ型のマーシャリング
  4. 構造体型のマッピング
  5. 配列型のマッピング
  6. 入れ子構造体型のマッピング

【1】ライブラリのマッピング

 JNAを利用する流れでも説明しましたが、JNAでは、ネイティブライブラリをJavaのインターフェイスでマッピングします。そのインターフェイスは、JNA提供のLibraryインターフェイスを継承し、このインターフェイスの中にネイティブライブラリで公開している利用したいデータ構造体と呼び出したいAPIを宣言します。

 後は、JNAのNative.loadLibraryメソッドで、このインターフェイスとネイティブライブラリとを関連付けます。このインターフェイスは代わりに、ネイティブライブラリの機能を公開します。

Native.loadLibrary(String nativeLibName, Class clazz)

 第1引数は、ネイティブライブラリ名を指定します。Windows環境では、ネイティブライブラリ名の拡張子を取り除いて指定します。詳細および、ほかのプラットフォーム環境での場合は、JNAのドキュメントを参照してください。第2引数は、宣言した対応するJavaのインターフェイスを指定します。

図1 Javaインターフェイスとネイティブライブラリのマッピングイメージ
図1 Javaインターフェイスとネイティブライブラリのマッピングイメージ

【2】APIのマッピング

 名前とシグネチャで、APIをマッピングします。デフォルトでは、同名でマッピングしますが、JNAでは、改名のマッピング方法を提供しています。詳細は、JNAのドキュメントを参照してください。

図2 APIのマッピングイメージ
図2 APIのマッピングイメージ

【3】プリミティブデータ型のマーシャリング

 マッピングの基準は、データのサイズです。

表1 JNAのネイティブ層でのマッピング表
Javaの型 Cの型 ネイティブサイズ
boolean int 32-bit Integer
byte char 8-bit Integer
char wchar_t プラットフォーム依存
short short 16-bit Integer
int int 32-bit Integer
long long long
__int64
64-bit Integer
float float 32-bit floating
double double 64-bit floating ポインタ
<T>[]()プリミティブ配列 pointer 32/64-bit メモリポインタ

表2 JNA独自型のJava層でのマッピング表
Javaの型 Cの型 備考
String char* NULL終端の配列(Native encoding、または、Jna encoding)
WString wchar_t* NULL終端の配列(Unicode)
WStringは、JNAの独自型
String[] char** NULL終端の文字列配列
WString[] wchar_t** NULL終端の文字列配列r
Structure struct*
struct
structのポインタ
structの値
Union union Structureと同様
Structure[] struct[] structの配列。連続メモリ確保とする
Callback <T>(*fp)() 関数(メソッド)のポインタ
NativeLong long プラットフォーム依存(32-bit、または、64-bit Integer)
IntReference int* Cからint型の戻り値
PointerByReference void**

注意点

上記以外のデータ型はサポートされません。従って、ほかの型は、上記の基本型に一度に変換して、利用します。

また表1に示した自動変換は、JavaからC/C++に渡す引数に対するとき、表の通りに適用できますが、Cからの戻り値は特に配列の戻り値はそのまま受け取れません。これについては、後述の「配列型のマッピング」を参照してください。

1-2-3-4

 INDEX 特集「組み込みにも役立つJavaとネイティブコードの橋渡し」
  Page1
  C/Sでは、サーバサイドはC/C++がまだまだ主役
JNIを使いやすくした「JNA」とは
Page2
  JNAを利用するための基本的な流れ
JavaとC/C++のマッピング方法、6パターン
 【1】ライブラリのマッピング
 【2】APIのマッピング
 【3】プリミティブデータ型のマーシャリング
  Page3
   【4】構造体型のマッピング
 【5】配列型のマッピング
  Page4
   【6】入れ子構造体型のマッピング
JNAアプリケーションのアーキテクチャ
JNIよりも保守性を高めるJNA

Java Solution全記事一覧



Java Agile フォーラム 新着記事
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Java Agile 記事ランキング

本日 月間