Macintosh Developer Online (MDOnline)
2001年11月23日発行号 - JDirectは大変…
- Java Watch on the X》2 - 結果的に実用的じゃないJDirectのサンプル(2)
- Java Watch on the X》2 - 結果的に実用的じゃないJDirectのサンプル(3)
- Java Watch on the X》2 - 結果的に実用的じゃないJDirectのサンプル(4)
やはりアメリカは感謝祭だと完全に社会がストップするようですね。いろんなサイトでも更新はとまっています。今日は、昨日の続きで、JDirectの解説だけになります。
少し前のニュースですが、地上波TVのデジタル化スケジュールが延期になりそうだと出ていました。デジタル化をすすめて、2011年には現在のアナログ放送(ってのも妙だけどまあいいでしょう)を終了させると言うスケジュールだったそうすが、これも延期されるだろうという見通しだそうです。一方、NHKにネットビジネスに出さないようにしろという圧力もかかっているようで、放送業界もビッグなビジネスだけにいろいろあるようですが…。だけど、前者のデジタル化の遅れは、もしかすると、今免許を持っていないところにとってはチャンスが広がるかもしれません。そうこうしているうちに、ブロードバンドが当たり前になり、インターネットベースで放送局を展開できるようになることが考えられます。現在の「放送」という発想では、とにもかくにも「受像機」という専用ハードにパッケージングしないといけないですが、インターネットベースでの配信はソフトウエアですので、変化に追随するというか、変化を作ることが十分に可能です。要は、アナログだデジタルだ、規制しろなどと言っている間に、今までのテレビとは違ったサービスを、ストリーミング放送とかで提供することで、長期的に立場を逆転できるんじゃないかということは十分に期待できるのじゃないかと思います。その意味では放送業界って食い込む余地はこれまで少なかったけど、インターネットの普及で意外に今後は流動的なのかもしれません。衛星放送系でも膨大な資金でなかなか企業運営が大変ですが、ネットワーク系は遥かに資金はかからないし、スペックの変更の費用も低いですからね。低予算をベースに、変化を提供して追随を許さなくするというビジネスモデルで既存の体勢に食い込めないかなと考えたりもしています。
プレゼントのJavaOneの展示会のチケットですが、都合により本日夕方投函となりました。うちのあたりは田舎なので、実質的には24日に集配されるでしょうね。たぶん、月曜か火曜には到着すると思います。あと少しJavaOneのチケットはありますから、欲しい方はメールください。
JavaOneチケット11/23発送予定分(敬称略):chobi、高山和夫、RATS、小倉孝志、m.y.、たけなお
それでは、この土日は、今日の振り替えでMDOnlineはお休みをいただきます。次回は、27日の火曜日に発行です。
(新居雅行 msyk@mdonline.jp)
Java Watch on the X》2 - 結果的に実用的じゃないJDirectのサンプル(2)
【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[]で受け渡しするということになる。
(続く)
カテゴリ:Java, Java Watch on the X
Java Watch on the X》2 - 結果的に実用的じゃないJDirectのサンプル(3)
【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を参照してもらいたい。
(続く)
カテゴリ:Java, Java Watch on the X
Java Watch on the X》2 - 結果的に実用的じゃないJDirectのサンプル(4)
【MDOnline読者様限定コンテンツ】
ここで、それじゃあ、AECreateDescの実際のプログラムが存在するライブラリはどのファイルなのかということになる。実際に、こうしたシステムコールを使いたいとなると、一般にはCarbonのコールだろう。CocoaのAPIを使いたいのなら、GUIのことを無視すればいっそのことCocoa-Javaで作るのが早い。QuickTimeだったらQuickTime for Javaがある。そうなると、JDirectで使うのはCarbonのコールやあるいはコアシステムのコールということになるだろう。Carbonの場合は、サンプルにあるように、フレームワークの中にある、ライブラリのファイルを参照すればいい。システムのAPIの場合は、たいがいはCarbonのフレームワークを指定すればいいようだ。たとえば、AECreateDescは、ApplicationServicesフレームワークのAEフレームワークにあるAEDataModel.hに定義されている。だから、こちらのフレームワークのライブラリへのパス名である "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/AE.framework/Versions/A/AE" を、変数JDirect_MacOSXに代入してもきちんと動作する。だが、Carbonのライブラリを指定してもAECreateDescのネイティブメソッド呼び出しはきちんと動くのである。Carbonはいろんなフレームワークを統合していると思っていいかもしれない。いずれにしても、フレームワークのライブラリファイルを探し出して、そのパスを得て、JDirect_MacOSXという文字列に指定しておくのである。
なお、デバッグ機能として、JDirectによるメッセージをコンソールに表示するということができるようになっている。1つの方法は、ソース中に、
com.apple.mrj.jdirect.Linker.verbose = true;
というプログラムを入れておけばよい。これにより、ネイティブメソッドが呼び出されたときにロードされる様子や、JDirect_MacOSX変数を調べてライブラリをロードする様子を見ることができる。少なくとも、使っているライブラリが正しいかどうかといったチェックはできるだろう。ただ、GUIを使っていると、AWTがネイティブメソッドを使う段階のメッセージが大量に出てくるので、ちょっと中身が見づらくなる。なお、シェル変数の設定でも同様に、JDirectのメッセージを表示できるが、「setenv JDIRECT_VERBOSE」というコマンドを与えておけばよい。
―――注意点とハンドルやポインタの利用
ここからは個別の事情となる。まず、今回のAppleScriptを動かすプログラムを見ていただきたいが、以上のように、APIコールをネイティブメソッドで呼び出し、AEDescレコードなどをだましだまし使うという手法で、OSADoScriptを使って、AppleScriptのプログラム実行にこぎつけるまでのところはだいたい説明できているはずだ。もっとも、このnativeメソッドの定義にえらくてこずったのであるが、結果を見ればクリアなはずだ。
注意点としては、AECreateDescは、ハンドルを確保するため、その確保したメモリ領域をリリーするため、AEDisposeDescを呼び出さないといけないことだ。Javaではメモリのリリースなんてまずはしないのだが、実際に動いているのはCarbonのAPIである。だから、システムの内部的にはリリースが必要になるのだ。見てくれはJavaだけど、動きはCarbonであることは忘れてはいけない。
さらに、どうしてもハンドルやポインタを使わないといけない場面が出てくる。もちろん、そうした機能はJavaにはないが、そこでMRJでは、GenericHandleとGenericPointerというクラスを使えるようにしてある。com.apple.mrj.jdirectというパッケージに用意されている。
AppleScriptを実行した戻り値は変数resultDataに戻されるが、これはAEDescレコードである。その後半の4バイトがハンドルであるが、そのハンドルの値を直接はJavaでは使えない。そこで、new GenericHandle(ハンドルの値)で、新たにGenericHandleオブジェクトを生成する。これで、Javaのプログラムでハンドルの先のメモリブロックを管理できるようになった。GenericHandleにあるgetBytesAtメソッドは、ハンドル内の指定した位置からのデータをbyte配列として戻す。これをもとに、ハンドルにあるデータから文字列Stringを生成しているのである。
err = OSADoScript(comp, sourceData, 0, 0x54455854, 0x00000000, resultData);
if (err != 0)
OSAScriptError(comp, 0x65727273, 0x54455854, resultData);
AEDisposeDesc(sourceData);
GenericHandle dHandle = new GenericHandle(resultData[1]);
byte[] strs = dHandle.getBytesAt(0, GetHandleSize(resultData[1]));
rValue = new String(strs);
GenericHandleやGenericPointer、あるいは汎用的な構造体を利用するクラスについてのドキュメントは、Mac OS X向けにはまだ公開されていないようだが、MRJ SDKにはある。MRJ SDKのフォルダにあるMisc Documentationフォルダを見れば、そこのJavaDoc形式でドキュメントがある。それを見れば、だいたいの機能は分かるだろうし、構造体関連のクラスは、JDirectのドキュメントにも記載がある。
ただ、MRJ SDKに記載された内容のすべてがMac OS Xに適用できるものでもないのも事実だ。当面は、存在するドキュメントをうまく読みこなすということが必要になるだろう。
◇MRJ SDK 2.2
ftp://ftp.apple.com/developer/Development_Kits/MRJ_SDK_2.2_Install.sit.bin
(この項、以上)
カテゴリ:Java, Java Watch on the X