タイトルJava Watch on the X》2 - 結果的に実用的じゃないJDirectのサンプル(3)カテゴリーJava, Java Watch on the X
作成日2001/11/23 15:52:52作成者新居雅行
【MDOnline読者様限定コンテンツ】
こうした一連の変換を行なった結果、nativeなメソッドは、以下のように宣言することで、引数の受け渡しが基本的にはうまくいくということになるわけだ。

native short AECreateDesc(int typeCode, byte[] dataPtr, int dataSize, int[] desc);

あとは変数に値を入れて…と言いたいところだが、Javaに慣れているとメモリ確保といった極めてネイティブな事情に疎くなっているかもしれない(それがJavaのメリットでもあるのだが)。しかしながら、ネイティブメソッドは、呼び出しはJavaから行われるけど、中の動きはまさにネイティブなのである。
たとえば、AECreateDescは、ディスクリプタを生成する。ディスクリプタは、AEDescレコードで管理されている。このレコードは最初の4バイトでデータの種類を記述し、次の4バイトで実際のデータのあるハンドルを記録する。従って、ディスクリプタ全体は、まずAEDescの8バイトのレコード、そのハンドルの先にあるあるサイズのメモリブロックという2つの領域がある。もちろん、ハンドルの先の実データは、AECreateDescで生成されるけど、AEDescレコードはあらかじめあるものとして処理される。C言語だったら、

AEDesc myDesc;
er = AECreateDesc( ... , &myDesc);

のようなやり方をするだろう。ここでAEDesc構造体を定義したときに、8バイトのメモリをスタックに確保している。
JavaでAECreateDescを利用するときにはこうした動作を記述しなければならない。そのポイントになる部分を書き出すと次の通りだ。ここでは、引数に指定したAppleScriptのスクリプトプログラムを実行するクラスメソッドdoScriptを定義しているが、そこで、スクリプトプログラムのテキストをディスクリプタとして用意しなければならないので、AECreateDescを利用している。

public class AppleScriptRunner

public static synchronized String doScript(String script) {

int[] sourceData = {0,0}; //これらはAEDesc構造体なのでCのlong
// が2つ分、その分のメモリを確保しないといけない

byte[] scriptBytes = script.getBytes();
short err = AECreateDesc(0x54455854, scriptBytes, scriptBytes.length, sourceData);

AECreateDescの4つ目の引数は、int[]であるが、そのint[]、つまりC言語側には結果的にint[]の変数sourceDataを作ったときのメモリが渡されるので、単に「int[] sourceData;」だけでint配列を作ってAECreateDescに引き渡すと、メモリが確保されていない状況となってしまうので、正しく動作しないことになる。ただ、この場合はsourceData変数を初期化しないとコンパイルエラーになる。初期化するときに、たとえば4バイトしか確保していないと、メモリ関連のエラーが出るので、たとえば右辺を{0}にして実行してみるとその様子が分かるだろう。そこで、前のプログラムのようにintの値の2つ分、つまり、AEDescレコードのメモリ領域を、Javaのプログラム中で確保して0で生めるということで、{0, 0}という初期化を行っているというわけである。
さらに、ここでは、文字列scriptから、byte型の配列に変更して、それを2つ目、そして3つ目の引数に利用している。getBytesはもちろんStringクラスに用意されたメソッドだ。
さて、AECreateDescの最初の引数は、C言語場合は既定義の定数を使えた。ここでは、テキストだから、C言語だとtypeCharと記述すればそれでOKだった。しかしながら、Javaの世界ではそうしたAPIコールで使える定数はほとんど利用できないと言っても過言ではない。ここでは、typeCharはオリジナルは‘TEXT’であり、これをintで展開しないといけないから、0x54455854、つまり、T→0x54、E→0x45
などと文字コードの16進表記をJavaのプログラムの上ではやらないといけないのである。
以上のように、APIコールをそのまま使うには、結果的にToolboxのプログラミングをC言語で行った経験や知識は必要である。それにも増して、Cでは簡単に利用できていた定義定数もないとか、typedefで定義された型もオリジナルにさかのぼらないといけないなど、なかなか面倒なのである。もっとも、こうした機械的な定義は、ヘッダファイルからのコンバータが1つあればOKという気もするし、Appleから定義部分だけをテキストとして供給してくれたらいいなとも思うけど、いずれにしても、定義だけではなく理解しないといけないポイントも多く、なかなか一筋縄では行かないところでもある。

―――ネイティブメソッドの存在するライブラリとのリンク
ここまでが長かったけど、まだ終わりではない。nativeでメソッド宣言したら、あとはシステムが勝手にコールしてくれるというわけではない。実際にコールするネイティブメソッドが含まれたライブラリを、クラスに教えてやらないといけないのである。その仕組みはある意味では複雑だろうけども、この部分に関しては、Javaプログラマはやり方だけ知っていればいいということになるだろう。これは、Technotesの#2002に記載されている。
まず、com.apple.mrj.jdirect.Linkerとして用意されているLinkerクラスを生成する。引数には、JDirectを使うクラスのクラス名に、.classをつなげたものを指定する。この.classはJDK 1.1以降で利用できる一種のキーワードで、「クラス名.class」は、クラス名に指定したクラスのjava.lang.Class型のデータを得る。つまり、このJDirectを使うクラスであって、それを引数にLinkerオブジェクトを生成するということだ。変数linkageに代入しているが、この変数名はなんでもかまわないし、後から使うこともないようである。

public class AppleScriptRunner {

public static Object linkage = new Linker(AppleScriptRunner.class);
public static final String JDirect_MacOSX
= "/System/Library/Frameworks/Carbon.framework/Versions/A/Carbon";

そして、もう1つ必要なことは、JDirect_MacOSXというString型のクラス変数を定義しておくことだ。この変数名はこの通りでなければなく、別の名前に変更してはいけない。Linkerの生成によって、ここではAppleScriptRunnerクラスに外部のライブラリを呼び出す機能が追加されたと考えて良いが、ここで、nativeで定義したメソッドを実際に呼び出す段階で、「どのライブラリファイルから取り出すか」ということを、JDirect_MacOSXという変数で指定しておくのである。この変数はライブラリのファイルの絶対パスを指定するのである。ここでは1つだけのライブラリを使うので、クラスの中に定義したが、複数のライブラリファイルを使うのであれば、変数JDirect_MacOSXへの代入を行ったinterfaceクラスをそれぞれ作っておいて、ネイティブメソッド呼び出しのあるクラスでimplementsしてやればいい。この方法は、Technote #2002を参照してもらいたい。
(続く)
関連リンク