タイトルJava Watch on the X》2 - 結果的に実用的じゃないJDirectのサンプル(2)カテゴリーJava, Java Watch on the X
作成日2001/11/23 15:52:6作成者新居雅行
【MDOnline読者様限定コンテンツ】
JDirectは、Javaのプログラム中から、C言語などで作成されたネイティブな実行オブジェクトの関数などを呼び出すことを可能にする。簡単に言えば、C言語で呼び出す形式のシステムコールなどを、Javaのメソッドとして呼び出すことができるわけだ。たとえば、AppleEventのシステムコールで、ディスクリプタを生成するAECreateDescという関数がある。必要な引数を指定してこの関数をコールすると、AppleEventでの汎用データ形式であるディスクリプタがメモリ内に作られるというわけである。C言語のインタフェースとしては次のように定義されている。引数に必要な変数を用意し、いくつかは値をあらかじめ与えて、AECreateDescを呼び出すというのが基本である。

OSErr AECreateDesc(DescType typeCode, const void * dataPtr, Size dataSize, AEDesc * result);

もちろん、Javaのライブラリには、AECreateDescというメソッドはどこにもない。たとえば、Javaで作っているアプリケーションで、どうしてもAppleEvent関連の処理を組み込みたいとなると、なんとかAECreateDescを呼び出したいということになるわけだ。(もちろん、まとまったプログラムをC言語で作ってライブラリとして作っておき、JDirectやJNIを使って呼び出すという方法もあるが、今回はシステムコールを直接呼び出す方法を中心に説明しよう。)

―――ネイティブメソッドの定義
Javaの言語で定義されている範囲として、外部のC言語などで作られたネイティブライブラリにある関数(あるいはエントリーポイント)を、あたかもメソッドの1つのように扱えるようにするnative宣言というものがある。

◇Java言語仕様:8.4.3.4 native Methods
 http://java.sun.com/docs/books/jls/second_edition/html/classes.doc.html#31125

つまり、nativeという修飾子を使ってメソッドを宣言することで、そのメソッドは、Javaで使えるメソッドとなる。具体的には、AECreateDescの場合は、クラスの中で、次のような宣言を行う。

public class AppleScriptRunner {
private native static short AECreateDesc(int typeCode, byte[] dataPtr, int dataSize, int[] desc);

}

基本的には、まず、ネイティブな関数、つまりAPIコールと同じ名前のメソッドを定義する。AppleEvent Managerで、AECreateDesc関数だから、メソッド名も同じAECreateDescとする。メソッド名の前にはnativeというキーワードをつけておく。そして、このメソッドの実体はJavaのプログラムではなく外部のライブラリにあるので、メソッドのプログラムは書かない。従って、メソッド宣言はいきなりセミコロンで終わるわけだ。C言語で言えば、プロトタイプ宣言のようなものである。
ここで、privateやstatic宣言があるが、これは必須ではない。ただ、このAECreateDescメソッドを使って処理を行うメソッドを、クラスメソッドにしているため、static宣言が必要になっている。また、他で使う予定がないので、とりあえずprivateにしているが、これらは必要に応じて設定をすることになる。

―――メソッド呼び出しのパラメータ設定
ここまではまだ簡単だ。次に行う必要があるのは、引数の整合性を取るという作業である。要は、どこかでC言語の関数コールがなされるが、スタックに定義した順番に規定のバイト数で区切って引数を渡し、一方で戻り値を決められたサイズのスタックで引き渡すことになる。そうした振る舞いも、このnativeメソッド宣言で記述しなければならない。この対応については、基本データ型と構造体でそれぞれ対応が違うのであるが、まずは基本データ型で攻めて行こう。バイト数やデータフォーマットという基準で考えれば、たとえばJavaのint型はCのshort型になるといった対応付けができる。まず、その対応を以下にまとめておく。(一般にはC言語ではsignedは省略可能だ。)

Javaの基本型 Cの基本型
――――――――――――
void void
boolean unsigned char
byte signed char
char unsigned short
short signed short
int signed long
long signed longlong
float float
double double
byte[] signed char*
char[] unsigned short*
short[] signed short*
int[] signed long*
long[] signed long long*
float[] float*
double[] double*

charやlongはCとJavaではバイト数が違うのでちょっとややこしいところである。
ここで、改めてAECreateDescのC言語のプロトタイプを見ると、次のようになっている。

OSErr AECreateDesc(DescType typeCode, const void * dataPtr, Size dataSize, AEDesc * result);

ToolboxのAPIコールでは、ある意味でパラメータの意味が分かりやすいように、いろいろな型を定義している。たとえば、OSErrは要はエラーの種類を整数で示すわけで、その実態はCのshort型である。また、Sizeはデータの大きさを示す数値なわけで、その意味ではCでのlong型である。だから、Javaでのnativeメソッド宣言では、OSErr→short⇒short、Size→long⇒intという変換をする必要がある。
なら、Cでのvoid *は、unsigned char *であるから、Javaではbyte[]となる。void *はつまりはポインタであるわけだが、これに対する配慮は後からも必要になるので、要注意なところだ。DescTypeはResTypeだからそのもとはFourCharCodeであるので、そもそもunsgined longであるということだ。これは、Javaではint型に対応する。
一方、Cでの構造体の扱いはさらに難しい。構造体をラップするクラスStructなどが定義はされていて、その派生クラスとして、いくつかのToolboxで定義されているクラスが用意されている。たとえば、FSSpecのクラスといったものもある。一般論などはMRJ 2.1のJDirectのドキュメントで書いてあるが、結果的にはそうした特定の構造体に対応したクラスが使えるかどうかといった個別の問題となる。メソッドの定義という場面においては、C言語の場合は多くの場合、構造体へのポインタを引数として渡すことがほとんどだろう。その場合は、ポインタとなるので、対応表からJavaでは配列として受け渡しを行う。このとき、単にAPIコールでポインタとしての処理を行うだけであるのなら、byte[]でかまわない。一方、AEDescのように、Cでのlong型のメンバーが2つあるようなものの場合、int[]で受け渡しをすると、後の処理がやりやすい、つまり、構造体のメンバーに直接アクセスすることがしやすくなるということもある。結果的に、AEDesc *は、Javaではint[]で受け渡しするということになる。
(続く)
関連リンク