Macintosh Developer Online (MDOnline)


2002年2月7日発行号 - AppleScriptのcall method



明日はセミナーがあるので、出る前の午前中に発行するか、そうでなければ、夕方か、夜遅くかの発行になるかと思います。なお、セミナーの内容は記事にはしないつもりですが、今日の記事のサンプルプログラムを紹介することになるかと思います。
Appleのサイトを毎日チェックしているのですが、Technical Notesが今年になってから1つもでていません。Technical Q&Aは少しはでているのですが、昨年に比べてドキュメントの出方が緩やかになっているような気がします。ただ、WebObjects 5.1関連は今年になってどっさりでてきていますが、システムのバージョンアップのタイミングを図っているような気がします。いずれにしても、Developer Toolsは3か月交代になっているで、次はMarch 2002版かなということがひとつの予測です。ただ、2001年のいくつかのDeveloper Toolsのリリースでは、そのつどいろんな問題がありましたけど、システムの変化は少なくなってきたので、そろそろ安定してきていて、ツールの開発頻度は少なくなるということも考えられるでしょう。来月で、Mac OS Xは正式発売から1年を経過します。頻繁にアップデートをしたこともあって、けっこう安定してきていますし、10.1は明確なマイルストーンになりました。少なくとも、今勉強したことは、ずっと生きるような気がします。
(新居雅行 msyk@mdonline.jp


AppleScript Working》4 _ AppleScriptからJavaを呼び出す(1)

   この記事のPDFファイル(650KB)は以下のアドレスにあります。
   ダウンロードには、MDOnlineのアカウントが必要です。
   pdfs/MDOnline020008.pdf
 ‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥
AppleScript Studioで作成するアプリケーションは、クラスやあるいはオブジェクトのメソッドを呼び出すという機能が利用できる。AppleScriptに対するインタフェースを特別に定義したわけではないようなクラスを直接呼び出せるのだ。AppleScript Stduioのチュートリアルにも、Objective-Cでの事例が掲載されており、Cocoaのクラスにあるメソッド等を呼び出す方法が書いてある。CocoaだからObjective-Cだけかという雰囲気もあるが、実は、Javaで作ったクラスもAppleScriptから直接呼び出すことができる。これらは、機能拡張の手法に大きな柔軟性を与えるものだ。

――――AppleScriptからのメソッド呼び出し
AppleScript StudioのApplication Suiteには、call methodというコマンドがある。アプリケーションの中なら、そのまま使えるわけだが、用語辞典を日本語におおまかになおすと次のようなものだ。

call method メソッド名
[of class クラス名]
[of object オブジェクト]
[with parameter 引数]
[with parameters 引数のリスト]

つまり、いきなりcall methodコマンドを使えるのだが、クラスないしはオブジェクトの一方を指定し、呼び出すメソッドがあれば、引数あるいは引数リストのいずれか一方を指定することになる。このメソッドを使って、Objective-Cのクラスを呼び出す方法が、以下の文書に掲載されている。

◇More on AppleScript Studio
 http://developer.apple.com/techpubs/macosx/CoreTechnologies/AppleScriptStudio/applescriptstudio/chapter3/More_on_App_ript_Studio.html

Objective-Cの場合はクラスメソッドであれば、クラス名とメソッドを与えればいいが、ウインドウ上のボタンへの参照をオブジェクトとして指定してもかまわないのである。また、自分でソースプログラムを供給したクラスを指定し、そこに定義されているメソッドを呼び出すこともできる。この場合、一般にはクラスメソッドを定義して、そこでさまざまな処理を組み込むことになるだろう。Objective-Cで作成するのが基本となっているが、クラス定義部分はもちろん、Objective-Cでの文法に従う必要があるが、メソッド内の処理ではCの記述も可能であるため、その意味ではToolboxのAPIをそのまま呼び出したり、あるいはCocoaのクラスを利用するということは可能である。
しかしながら、後で説明するように、Objective-Cだけでなく、Javaでも同様にプログラミングはできる。それぞれ長所や短所はあるが、両方できるという点は選択肢の上でも広くなり好ましいことだと言えるだろう。なお、CarbonのAPIを使うにはC言語のAPIであるため、Objective-Cで作る方が何かと楽だろう。一方、JavaだとPure Javaのさまざまなライブラリが使えるという点が大きい。Javaだと実行速度の上では若干不利であるのだが、AppleScript自体の処理スピードのことを考慮すれば、Javaの処理能力低下はほとんど考慮外になることもあるだろう。

――――Objective-Cのクラスを定義して呼び出す
自分で作ったObjective-Cのクラスのメソッドを呼び出す方法をまずは説明しよう。Project Builderで、AppleScript Applicationを選択して作ったプロジェクトがあるとする。そのプロジェクトに、次のようにして、Objective-Cのソースファイルを追加する。まず、「ファイル」メニューから「新規ファイル」(Command+N)を選択する。ダイアログボックスが表示されるので、ここではCocoaのObjective-Cクラスを選択する。

◇新規ファイルでObjective-C Classを選択する
 

すると、クラス名やこのクラスを追加するターゲットを指定するダイアログボックスになる。ここでは、SoundPlay.mというファイルに加えて、ヘッダのファイルも作成し、最初からあるターゲットに追加するように指定した。

◇ファイル名とターゲットを指定する
 

これで、SoundPlay.mとSoundPlay.hという2つのファイルが追加された。ここで、プログラムを追加するが、文字通り、サウンドを鳴らすプログラムを追加したい。システム環境設定の「サウンド」で、警告音の一覧が出るが、これは/System/Library/Soundsおよび~/Library/SoundsにあるAIFFファイル(拡張子は.aiff)の一覧である。NSSoundの機能を使うと、これらのサウンドファイルを、ファイル名を指定するだけで鳴らすことができる。どんな名前でいいのかは、システム環境設定で見る方が確実だろう。
それそれのリストは次のようにした。まず、ヘッダファイルは次の通りだ。

// SoundPlay.h

#import

@interface SoundPlay : NSObject {}

+ (void)playSound;
+ (void)playSound:(NSString *)soundName;
+ (void)playSound:(NSString *)soundName async:(BOOL)isAsync;

@end

ヘッダでは、とりあえず、クラスメソッドのインタフェースを記述する。ここでは、3種類のplaySoundメソッドを定義するが、インポートするヘッダは、初期状態のFoundation.hからCocoa.hにしておくのが何かと便利だろう。
最初のplaySoundは、Templeという名前のサウンドを無条件に鳴らす。2つ目は1つの引数を指定し、その引数にサウンド名を指定して、そのサウンドを鳴らす。3つ目のものは、2つの引数を取り、サウンド名と非同期で鳴らすかどうかを論理値でそれぞれ指定する。2つ目の引数は同期で鳴らすなら、trueを指定する。なお、最初の2つのメソッドはサウンドを非同期で鳴らすため、サウンドを慣らしている最終に次の処理に移動する。
そして、インプリメントを記述するSoundPlay.hは次のようにした。

// SoundPlay.m

#import "SoundPlay.h"


@implementation SoundPlay

+ (void)playSound;
{
NSSound * theSound = [NSSound soundNamed:@"Temple"];
[theSound play];
}
+ (void)playSound:(NSString *)soundName;
{
NSSound * theSound = [NSSound soundNamed:soundName];
[theSound play];
}
+ (void)playSound:(NSString *)soundName async:(BOOL)isAsync;
{
NSSound * theSound;
theSound = [NSSound soundNamed:soundName];
[theSound play];
if(isAsync)
while([theSound isPlaying])
sleep(1);
}

@end

いずれも、NSSoundというクラスにあるクラスメソッド、soundNamedで指定した名前のサウンドのインスタンスを得て、playメソッドで実際に音を鳴らしている。なお、同期で鳴らすために、isPlayingメソッドで鳴らしている途中かどうかを判断しながら、鳴らしている途中であれば、sleepで1秒待って鳴り終わるのを待つというわけだ。
これらのメソッドをAppleScriptで使うプログラムをまず見ていただきたい。

call method "playSound" of class "SoundPlay"
call method "playSound:" of class "SoundPlay" with parameter "Frog"
call method "playSound:async:" of class "SoundPlay" with parameters {"Temple", true}

(この項、続く)

カテゴリ:AppleScript, AppleScript Working


AppleScript Working》4 _ AppleScriptからJavaを呼び出す(2)

まず、クラスメソッドの利用なので、of class "SoundPlay" がいずれのコマンドにもつけられている。1行目は引数のないplaySoundメソッドを呼んでいる。2行目は引数が1つのplaySoundメソッドを呼んでいるが、結果的にコロンが1つメソッド名についている。3つ目は引数が2つのplaySoundを呼び出しているが、「playSound:async:」となっている点に中止してもらいたい。いずれにしても、メソッドのインタフェース定義の部分にあるメソッド名に加えて、メッセージキーワードとコロンを含めたものをメソッド名として指定する必要がある。
ここで、まず注意したいのは、正しくないメソッド名を指定してもエラーは出なという点だ。だから、間違えたAppleScriptの記述をしても「何もおこらない」かのような現象になってしまう。
それから、引数のあるものは、with parameter(s)で指定を行う。ここで、文字列を指定すると、Objective-C側のプログラムでは(NSString *)で受ければいいし、論理値ならBOOL、整数ならintのように、いずれにしても、素直に対応付けられる型で受け取ることができる。また、戻り値がある場合も同様だ。複数の引数があるときには、リスト型で順序を間違えないように指定する。ただし、前のプログラム例で、with parameter {"Temple", true} つまり「s」が1つないだけで、これは、「配列の引数が1つ指定されている」とみなされてしまう。そして、1番目の引数に大カッコの中身の値が(NSArray *)で引き渡されてしまうので、2つ目の引数には値は渡らないのである。
なお、Cocoaのクラスのクラスメソッドも同様に呼び出すことができる。クラス名に「NSNumber」などと指定をすれば良い。



――――Javaのクラスを呼び出す
Javaの場合は少し実験をしながら作ったクラスで例を示そう。やはり、AppleScript Applicationをプロジェクトのテンプレートとして選択したアプリケーションで、「ファイル」メニューの「新規ファイル」(Command+N)を選択して、新しいファイルを作るが、Java classを選択して現在のターゲットにそのファイルを追加するようにしておく。ここでは、JavaObj.javaというファイルを作ることにした。そして次のように、クラスメソッドを作った。

// JavaObj.java

import com.apple.cocoa.foundation.*;
import com.apple.cocoa.application.*;


public class JavaObj {

static public int actionA(){
return 99;
}
static public int actionB(int a){
return a*99;
}
static public int actionC(int a, int b){
return (a+b)*99;
}
static public NSArray actionD(){
Object obj[] = {new Integer(19), "Sender OK", new Float(0.876)};
return new NSArray(obj);
}
static public NSDictionary actionE(){
Object obj[] = {new Integer(19), "Sender OK", new Float(0.876)};
Object key[] = {"Age", "Message", "The Number"};
return new NSDictionary(obj, key);
}
static int stock = 0;
static public void setStock(int n){stock = n;}
static public int getStock(){return stock;}
}

引数がいくつもある場合や、リスト、レコードを戻せるかといった点を中心にチェックしたわけだが、これらの呼び出しを行うAppleScriptのプログラムは次のようになる。まず、引数に数に応じて、メソッド名をどのように指定しないといけないかを探るシリーズ、actionA〜ActionCの呼び出し結果を見てみよう。以下のステートメントは問題なく動くことをもちろん確認している。メソッドを呼び出した結果を、display dialogでダイアログ表示してみた。

set x to call method "actionA" of class "JavaObj"
display dialog x as string
set x to call method "actionB:" of class "JavaObj" with parameter 10
display dialog x as string
set x to call method "actionC::" of class "JavaObj" with parameters {10, 4}
display dialog x as string

引数のないactionAメソッドはそのままメソッド名を書けばいいが、引数のあるものの場合は、コロンがその引数の数に応じて必要になる。つまり、引数の数だけコロンを続けたものをメソッド名として指定する必要がある。これは、JavaとObjective-Cのブリッジ部分の仕様であるのだろう。この場合は、Objective-Cのメソッド呼び出しの機能で、Javaのメソッドを呼んでいる。Javaのメソッドは、メッセージキーワードのない状態で定義されていると考えれば、Objective-Cのクラスを呼ぶ場合の記述と対応がとれるだろう。
続いて、actionDはNSArray型のデータを戻してみた。actionEはNSDictionary型のデータを戻してみた。

set x to call method "actionD" of class "JavaObj"
display dialog (item 2 of x) as string

set x to call method "actionE" of class "JavaObj"
display dialog (item 2 of x) as string
display dialog (Age of x) as string

ここで、actionDの戻り値は、確かにAppleScriptでのリストになっているのだが、actionEの戻り値はレコードにはなっていない。理由は分からないが、NSDictionaryではだめなのかあるいはAppleScriptシステム側に問題があるかもしれない。
続いて、結論を言えば当たり前なのだが、ここでのJavaObjクラスはstaticであることから、値を覚えるかどうかを試してみたのが、setStock、getStockのメソッドだ。いずれも、static変数のstockへの値の設定や取り出しを行っている。setStockで設定した値を、getStockで取り出されるのは以下のプログラムで確認できる。

call method "setStock" of class "JavaObj" with property 123
set x to call method "getStock" of class "JavaObj"
display dialog x as string

したがって、staticな変数を定義しておけば、call methodをまたがって、同じ値を共有できるということになる。複雑な処理をさせる場合には、こうした手法も有効だろう。
なお、Javaのライブラリを使うには、たとえば次のようにプログラムを作成すればよい。Mathクラスにmaxというメソッドがあり、引数を2つ取る。したがって、メソッド名は「max::」となる。また、クラス名は、フルパスで指定するのが基本のようである。

set x to call method "max::" of class "java.lang.Math" with parameters {3, 4}


――――JDBCを利用したサンプル
Objective-Cのクラスを作るメリットは、比較的示しやすいかもしれない。たとえば、キーチェーンの処理をさせたりといった、システムのAPI呼び出しができるからだ。一方、Javaのメリットとしては、やはり純正Javaライブラリの利用ということになるが、格好のサンプルとして、JDBCを使って、データベースアクセスを行う例を示したい。JavaにはJDBC(Java DataBase Connection)として、SQLデータベースへのアクセスを行う機能が用意されている。サーバサイドがJavaにシフトしていることもあって、JDBCに対応したデータベースが一般的である。最近では、MicrosoftのSQL ServerまでもJBDCドライバをまともに作るということがニュースになったほどだ。JDBCドライバを用意すれば、SQLステートメントの違いなどはデータベースエンジンごとにあるものの、接続やSQL実行、そこからの値の取り出しのプログラムはほとんど共通のものが使える。つまり、データベースエンジンに依存しないデータベースアクセスがJDBCによって可能になっているというわけだ。
いろいろなデータベースが結果的に使えるのであるが、今回のサンプルでは、OpenBase SQLを使用しよう。OpenBase SQLは、WebObjectsに付属しているのでなじみがあるかもしれない。もし、WebObjectsを持っていなくても、OpenBaseの評価版は無償でダウンロードして利用できるので、Mac OS Xにインストールして使ってみよう。GUIの管理ツールがあるなど使い勝手もよく、日本語にも対応している。今回は、OpenBaseがインストールされた状態であるとして、以下の例を示したい。OpenBaseには、「WOMovies」という映画のデータベースのサンプルがある。そのデータベースを起動した状態で、以下の作業を行うものとする。

◇OpenBase International
http://www.openbase.com/

このWOMoviesというサンプルデータベースには、MOVIEというテーブルがあって、映画の一覧表が用意されている。以下は、OpenBase Managerで見たそのテーブルである。

◇サンプルデータベースのWOMoviesにあるMOVIEテーブル
 
(この項、続く)

カテゴリ:AppleScript, AppleScript Working


AppleScript Working》4 _ AppleScriptからJavaを呼び出す(3)

それでは、このデータベースから値を取り出して、テーブル(NSTableView)のコントロールに取り出し結果を表示するといったプログラムを作成してみよう。まず、ユーザインタフェースの部分は、もちろん、Interface Builderで作成する。ここでは、プロジェクトとしてAppleScriptアプリケーションとして作ったものを利用するが、そこには最初からMainMenu.nibというnibファイルが用意されているので、それをダブルクリックし、最初から用意されているウインドウにユーザインタフェースを作り込むことにしよう。以下の図のようなものを作成したが、要は、ボタンが1つあり、このボタンは、Infoパレットで、clickedイベントを発生するようにしておき、そのプログラムをデフォルトのプログラムファイルであるApplication.applescriptに設定してある。また、テキストフィールド、テーブルをそれぞれ配置した。同じ種類のコンポーネントがないので、今回は、AppleScript Nameの設定を行わない方法でプログラムを組んでみよう。
それから、ツールパレットのAppleScriptのカテゴリから、青い四角いボックスを配置しておき、ASKDataSourceというインスタンスを作っておく。そして、NSTextViewのDataSourceとつなげておく。これは前回説明した通りのデータソースの確保である。

◇作成したウインドウとデータソースへの接続
 

なお、NSTableViewは3列を表示できるようにしておき、列幅は適当にドラッグして設定しておこう。
これでユーザインタフェースは作成できた。ちなみに、テキストフィールドに検索条件を記入すると、その文字を含む題名の映画をテーブルの一覧するという動作をさせたい。
続いて、データベースアクセスをおこなうクラスをJavaで作る。「ファイル」メニューの「新規ファイル」(Command+N)を選択して、Java classを選び、ここではDBAccessというクラスを定義した。プログラムはテキストでも示そう。

◇JavaのDBAccessクラスを定義する
 


// DBAccess.java

import java.sql.*;
import java.util.*;
import com.apple.cocoa.foundation.*;
import com.apple.cocoa.application.*;

public class DBAccess {

static public NSArray getData(String criteria){
try{
Class.forName("com.openbase.jdbc.ObDriver");
Connection con = DriverManager.getConnection(
"jdbc:openbase://localhost/WOMovies","","");
Statement st = con.createStatement();
ResultSet rs = st.executeQuery(
"SELECT * FROM MOVIE WHERE TITLE LIKE ’%"
+ criteria + "%’");
List tableContents = new ArrayList();
while(rs.next()){
Object obj[] = {rs.getString(1), rs.getString("TITLE"),
rs.getString("CATEGORY")};
tableContents.add(new NSArray(obj));
}
rs.close();
con.close();
return new NSArray(tableContents.toArray());
}
catch(Exception e){
System.out.println(e.getMessage());
return null;
}
}
}

プログラムの細かな点はJDBCに立ち入ることなので、ここでは概要を説明したい。getDataメソッドは1つの引数を取るが、その引数を検索条件として、MOVIEテーブルからレコードを取り出す。SELECTステートメントとして、LIKE演算子やワイルドカードを使ってSQLコマンドを作っているが、つまりは、TITLEカラムのデータに、引数に指定された文字列が含むものであれば、すべて抽出するとういSELECT文となっている。
データベースの接続には、JDBCドライバのクラスのフルパス記述や、あるは接続文字列の指定が必要だが、これは、OpenBaseのドキュメントを参考に、プログラムにあるように決めた。なお、これは、接続先としてlocalhostを指定している。つまり、実行するマシンで稼働しているOpenBaseを対象にデータベースアクセスするわけだ。このあたりのプログラムは、JDBCの定番的な流れである。
そして、取り出した結果を、2次元配列として戻したいのだが、NSArrayを要素に持つNSArrayを作れば、それは2次元配列としてAppleScript側に伝わる。1レコード分はまとめてNSArrayを作ってしまえばいいが、レコードを順に調べていき、それらを逐次記憶するためにArrayListクラスを使っている。このクラスを使って最終的にtoArrayメソッドで配列を得て、そこから、NSArrayを得ているのである。
続いて、AppleScript側のプログラムを紹介しよう。ボタンをクリックすると、以下のようなハンドルが呼び出される。

on clicked theObject
set crit to text field 1 of window of theObject
set dbData to call method "getData:" of class "DBAccess" with ツ
parameter string value of crit

set theTable to table view 1 of scroll view 1 of window of theObject
set ds to data source 1 of theTable
if (count data columns of ds) is 0 then
make new data column at the end of data columns of ds
make new data column at the end of data columns of ds
make new data column at the end of data columns of ds
end if
delete data rows of ds
repeat with aRow in dbData
set newRow to make new data row at the end of data rows of ds
set contents of data cell 1 of newRow to item 1 of aRow
set contents of data cell 2 of newRow to item 2 of aRow
set contents of data cell 3 of newRow to item 3 of aRow
end repeat
end clicked

まず、最初に、JavaのDBAccessクラスのgetDataメソッドを呼び出している。テキストフィールドの値を、メソッドの引数として指定しているわけだ。これで、call methodステートメントの戻り値は、データベースから取り出した結果が含まれた2次元の配列となる。
あとは、前回のプログラムとかなり近い。データソースにカラムが用意されているかどうかを調べて、なければ作成する。そして、まず、行を全部削除してから、各行に、各カラムのデータを設定している。
実際の動きを見るのが早いかもしれない。
まず、アプリケーションを起動すれば、ウインドウが表示される。テキストフィールドに、ここでは条件として「Hoo」を指定してボタンをクリックしてみた。

◇検索条件を入れてボタンをクリック
 

すると、テーブルの部分に、条件に合ったレコードが取り出さされる。繰り返し、検索を行ってもかまわないようにしてある。

◇検索結果が表示された
 


◇別の検索を行った結果
 

ここで、検索条件に何も指定しないで、ボタンをクリックしてみよう。その場合、すべてのレコードが取り出されて、100行以上に渡ってテーブルへの書き込みが行われる。データベースのアクセスはローカルだとすぐに終わるのだが、テーブルのデータソースの行の追加といったしょりに時間がかかるあたりが分かるだろう。

このように、データベースアクセスのためのクラスをJavaで用意しておくことができるが、もちろん、汎用的なフェッチメソッドを作るのもいいし、特定の用途のものをつくるのもいいだろう。クラス変数を使えば、コネクションをキープしたままいくつかのメソッドで処理することもできるし、取り出したデータをクラス内にキャッシュするということもできるだろう。こうしたJavaでのデータベースアクセスの形態は、まさに3階層システムになっているというわけだ。つまり、ユーザインタフェースをAppleScriptで組み、ビジネスロジックをJavaで組むというわけである。
call methodはある意味ではアドホック的な手段ではあるが、AppleScriptのソースがベタな感じになるという気はするものの、手軽にこうしてAppleScript以外の手段とリンクできるようになっている点は発展性を強く感じるところだ。もちろん、フレームワークとしてしっかりしたものを作り、AppleScriptの用語集などを備えた、AppleScriptでのクラスやコマンドとして使えるものを用意するのが最終的な手段ではあるだろうけど、必要な機能のObjective-CやJavaで作られたソースを自分のプロジェクトに組み込んで、call methodで使うというのが、Mac OS X時代のお手軽OSAXではないかと思われる。
(この項、以上)

カテゴリ:AppleScript, AppleScript Working


Mac OS XでAppleScriptベースのCGIを稼働させるアダプタ

James Sentman氏は、AppleScriptベースのCGIをMac OS Xで利用できるようにする「acgi dispatcher」をリリースした。Mac OS 9までのMacでのCGIは、AppleScriptで作成されているものも多かったが、Mac OS XではAppleScriptベースのCGIはサポートされていない(Mac OS X Serverではサポートされている)。そうしたAppleScriptベースの従来のCGIをMac OS Xで利用できるようなるのが「acgi dispatcher」である。バックグランドで動作する。β版として期限付きのものが配付されているが、正式版は安価なシェアウエアとなる予定だ。

関連リンク:acgi dispatcher on MacOSX
カテゴリ:サーバー製品, AppleScript


KBase》新型iMacのサウンド入力について

液晶ディスプレイを備えている新型iMacのマイク入力端子についての情報が、Knowledge Baseに掲載されている。内蔵のマイクは液晶ディスプレイの下の部分に組み込まれており、本体でデジタル信号に変換される。S/N比は65dBである。ほかに、内蔵ディスクからのデジタルオーディオ、内蔵モデムからの接続を示す音、USBポートに接続したオーディオデバイスからのサウンドを受け付ける。

関連リンク:iMac (Flat Panel): Sound Inputs
カテゴリ:Knowledge Base(旧TIL), iMac


KBase》iTunesでのCD書き込み時のエラーについて

iTunes 2で、CD-Rに書き込むときに、-50のエラーが出る原因は、MP3ファイルが壊れているからである。エンコードし直す必要がある。

関連リンク:iTunes 2: "Error-50" Alert Appears When Burning a CD
カテゴリ:Knowledge Base(旧TIL), メディアプレイヤ