MOSA Multi-OS Software Artists

MOSAはソフトウェア開発者を支援します

  • iPhone/iPod touch アプリ紹介
  • MOSA掲示板
  • 活動履歴
  • About MOSA(English)

MOSA Developer News[MOSADeN=モサ伝]第154号

2005-03-29

目次

  • SqueakではじめるSmalltalk入門  第36回  鷲見 正人
  • 小池邦人の「Carbon API 徒然草」
  • 「Behind the WebObjects」    第40回  田畑 英和 ★最終回★
  • ニュース・解説               木下 誠

SqueakではじめるSmalltalk入門   第36回  鷲見 正人

 本連載では、名前は知っていてもなかなか触れる機会のないSmalltalkについて、最近話題のSqueakシステムを使って紹介しています。コレクションの抽象クラス「Collection」のプロトコルを覗きながら、コレクションがどんなメッセージを受け付けるオブジェクトなのか、その“正体”を探っているところなのですが、ちょっと寄り道して、コレクションに対する二項演算がどのように実現されているのかを見ています。

 前回は、数値を引数にとる場合の解説をしました。たとえば#(1 2 3)に「* 4」というメッセージを送信している式なら、Collection >> #*から、Number >> #adaptToCollection:andSend:が呼び出され、そこでの定義から元の式が、

#(1 2 3) collect: [:element | element * 4]

と解釈可能となり、結果、各要素に「* 4」を送信して得られた結果を要素とする新しいコレクション#(4 8 12)が返ってくる…という流れでした。

 そして課題は、パラメータが4ではなく#(4 5 6)のとき、どんなカスケードが起こるのか…、もう少しうがった事を言うと、case-switchのような条件分岐を使わずにパラメータの違いによる振る舞いを変更するためにどうしているのか?でしたね。では、早速。

 とりあえず#(1 2 3)は、送られるメッセージが「* 4」であろうと「* #(4 5 6)」であろうと、セレクタが「#*」である限り、同じメソッドCollection >>#*を起動します。

Collection >> * arg
   ^ arg adaptToCollection: self andSend: #*

 まったく同じメソッドの起動ですから、この時点ではまだ多態はしていません。

 前回、引数argには、NumberのサブクラスであるSmallIntegerに属する4が束縛されていましたが、今回はCollectionのサブクラスであるArrayに属する#(4 5 6)を束縛しています。argに送るメッセージは同じですが、argに束縛されているオブジェクトが異なるので、起動するメソッドも、4のときのNumber >>#adaptToCollection:andSend:とは別のものになります。

 ここでようやく振る舞いに変化が生じます。引数argに改めてメッセージを送っているのがミソですね。なお、このようにレシーバだけではなく、引数についてもこれに依存的な多態を実現するための機構を、一般に「ダブルディスパッチ」と呼びます。

 Arrayとそのスーパークラス群にはCollectionに至るまで#adaptToCollection:andSend:は定義されていないので、結局、argに束縛された#(4 5 6)は、Collection >> #adaptToCollection:andSend:を起動します。

Collection >> adaptToCollection: rcvr andSend: selector
   rcvr isSequenceable & self isSequenceable ifFalse:
      [self error: 'Only sequenceable collections may be combined arithmetically'].
   ^ rcvr with: self collect:
      [:rcvrElement :myElement |
         rcvrElement perform: selector with: myElement]

 最初の式は、レシーバ、引数のいずれかが要素の順番を扱うことができないコレクションならエラーを生じさせるためのフェールセーフです。とりあえずこれは無視して第二式目に集中しましょう。「* 4」のときと同様に、self、rcvr、selector、それぞれに実際に束縛されているオブジェクトを割り当てて、より分かりやすい式に書き換えてみると次のようになります。ブロック変数名も短くしてみました。

#(1 2 3) with: #(4 5 6) collect: [:a :b | a perform: #* with: b]

 さらに、#perform:with:も、第一引数のセレクタが確定していれば、等価なメッセージ式に置き換えることができましたよね。結果、#(1 2 3) * #(4 5 6)は、次のように解釈されると考えてよさそうです。

#(1 2 3) with: #(4 5 6) collect: [:a :b | a * b] ” => #(4 10 18) ”

 #with:collect:は初出ですが、#collect:と見た目が似ているので動作の予想は容易いと思います。ブロックを評価した結果を集めて(collect=コレクト…して)返す点では同じです。ただ、#collect:はレシーバの各要素を評価に用いるのに対し、#with:collect:は第二引数のコレクションの同じ順番の要素も一緒に用います。詳しくは#with:collect:の定義をご覧ください。#with:collect:の定義は、ブラウザ中段のimplementorsボタンを押したときに現われるポップアップから「with:collect:」を選んで呼び出すのが早いでしょう。

 さて。前回から引き続き、以上駆け足でしたが、#(1 2 3)に「* #(4 5 6)」というメッセージを送信したとき、対応する各要素の積を要素とする配列#(4 10 18)が返ってくるしくみ、ひいては「* 4」を送信したときと振る舞いを変えることがどうして可能なのかについての理解を深めていただくことができたかと思います。case-swithという手続き的な記述で済ますのではなく、あくまでオブジェクトにメッセージを送って、その適切な振る舞いに期待する…というスタイルは、まさに(ケイの)「オブジェクト指向」の面目躍如といったところでしょうか。また、実際にシステム内で運用されている、こうしたカラクリのかなりの部分がSmalltalk自身で記述されており、いつでも気軽に簡単にそのソースに当たることができる…という点も、Smalltalkシステムならではの特徴と言えそうです。

バックナンバー:
http://squab.no-ip.com:8080/mosaren/

小池邦人の「Carbon API 徒然草」(2005/03/25)

Navigation Service APIの活用(その7)

今回は「Navigation Service APIの活用」の最終回です。FSSpecの代わりにFSRefを得るために有用なシートウィンドウ版の「フォルダ選択ダイアログ」と「ファイル名選択ダイアログ」を紹介したいと思います。

まず最初に、フォルダ選択用のシートウィンドウを表示するnavChooseFolderSheet()ルーチンを紹介します。使用方法は以前(その3)で解説したnavPutFileSheet()ルーチンとほとんど同じですので、そちらを参照してください。前回と少し異なるのは、フォルダ選択時に親ウィンドウが存在しないと(window==NULLの場合)シートウィンドウではなく、通常のモーダルダイアログとしてオープンするように設定されている箇所です。フォルダが選択された後の実際の処理は、NavCreateChooseFolderDialog()に引数として渡されるイベント処理ルーチンのnavGetEventSheeProc()の方に記述します。

#define   MY_SIG   'MosA'   // アプリケーションのシグネイチャ

short navChooseFolderSheet( WindowRef window )
{
    short                       ret=1;
    NavDialogRef                dptr;
    Str255                      str;
    NavDialogCreationOptions    opt;

    if( ! NavGetDefaultDialogCreationOptions( &opt ) ) // オプションを初期化
    {
        if( window ) // 親ウィンドウがあるのでシートウィンドウとして表示
        {
            opt.parentWindow=window;                  // 親ウィンドウを代入
            opt.modality=kWindowModalityWindowModal;  // シートと設定
        }
        else        // 親ウィンドウが無いのでモーダルダイアログとして表示
            opt.modality=kWindowModalityNone;         //  ダイアログと設定
        opt.preferenceKey=MY_SIG;                     // 使用形態の識別値を設定
        opt.optionFlags+=kNavNoTypePopup;     // 種類ポップアップメニュー未使用
        GetIndString( str,128,1 );
        opt.clientName=CFStringCreateWithPascalString( NULL,str,
                                               CFStringGetSystemEncoding() ) );
                                           // クライアント名をリソースから設定
        GetIndString( str,128,2 );
        opt.message=CFStringCreateWithPascalString( NULL,str,
                                               CFStringGetSystemEncoding() ) );
                                           // メッセージ文字をリソースから設定
        if( ! NavCreateChooseFolderDialog( &opt,navGetEventSheeProc,NULL,
                                                      (void *)window,&dptr ) )
                                            // シートウィンドウを作成する
        {
            if( ret=NavDialogRun( dptr ) )  // シートウィンドウを表示する
                NavDialogDispose( dptr );
        }
        if( opt.message )
            CFRelease( opt.message );     // メッセージ文字をリリース
        if( opt.clientName )
            CFRelease( opt.clientName );  // クライアント名をリリース
    }
    return( ret );
}


次は、ファイル選択用のシートウィンドウを表示するためのnavGetFileSheet()ルーチンです。navChooseFolderSheet()と異なるのは、前回紹介したnavMakeTypeList()で、選択対象ファイルタイプをNavTypeListHandleに変換してから渡している箇所です。ただし、今回は拡張子によるファイルタイプの判断はしていませんので、実際にこのルーチンを用いる場合には、前回と同様にフィルタルーチンによる判断が必要な場合もあります。注意してください。

short navGetFileSheet( WindowRef window,OSType type )
{
    short                       ret=1;
    NavDialogRef                dptr;
    NavTypeListHandle           list;
    NavDialogCreationOptions    opt;

    if( ! navMakeTypeList( 1,&type,&list ); // ファイルタイプリストを作成
    {
        if( ! NavGetDefaultDialogCreationOptions( &opt ) ) // オプションを初期化
        {
            if( window ) // 親ウィンドウがあるのでシートウィンドウとして表示
            {
                opt.parentWindow=window;                  // 親ウィンドウを代入
                opt.modality=kWindowModalityWindowModal;  // シートと設定
            }
            else       // 親ウィンドウが無いのでモーダルダイアログとして表示
                opt.modality=kWindowModalityNone;        //  ダイアログと設定
            opt.preferenceKey=MY_SIG;          // 使用形態の識別値を設定
            opt.optionFlags+=kNavNoTypePopup;  // 種類ポップアップメニュー未使用
            GetIndString( str,128,1 );
               opt.clientName=CFStringCreateWithPascalString( NULL,str,
                                               CFStringGetSystemEncoding() ) );
                                           // クライアント名をリソースから設定
            GetIndString( str,128,3 );
            opt.message=CFStringCreateWithPascalString( NULL,str,
                                               CFStringGetSystemEncoding() ) );
                                           // メッセージ文字をリソースから設定
            if( ! NavCreateGetFileDialog( &opt,list,navGetEventSheeProc,NULL,
                                                 NULL,(void *)window,&dptr ) )
                                           // シートウィンドウを作成する
            {
                if( ret=NavDialogRun( dptr ) ) // シートウィンドウを表示する
                    NavDialogDispose( dptr );
            }
            if( opt.message )
                CFRelease( opt.message );      // メッセージ文字をリリース
            if( opt.clientName )
                CFRelease( opt.clientName );   // クライアント名をリリース
        }
        DisposeHandle( (Handle)list );
    }
    return( ret );
}


両シートウィンドウにおけるユーザ操作(ボタンクリック等)に対応するための処理は、NavCreateChooseFolderDialog()とNavCreateGetFileDialog()に引数として渡されているnavGetEventSheeProc()(イベント処理ルーチン)の方に記述します。フォルダやファイルが選択され、ウィンドウ上の「選択」や「開く」ボタンが押されると、このルーチンが呼ばれることになります。ユーザの操作内容は、引数で渡されるNavCBRecPtr経由で判断することが出来ます。今回は、どちらのボタンクリックでもnavGetEventSheeProc()ルーチンが呼ばれていることが理解できます。

pascal void navGetEventSheeProc( NavEventCallbackMessage sel,
                                      NavCBRecPtr parm,NavCallBackUserData ud )
{
    NavReplyRecord   reply;
    DescType         rtype;
    FSRef            fsref;
    WindowRef        wptr;
    Size             len;
    AEKeyword        key;
    FSSpec           fsc;

    if( parm->context )
    {
        wptr=(WidnowRef)ud;  // 親ウィンドウのWindowRefを得る
        switch( sel )
        {
            case kNavCBUserAction:  // ユーザが何どんな操作を実行したか?

                switch( parm->userAction )
                {
                    case kNavUserActionChoose: // 選択ボタンが押された

                        navGetEventSheeProc( wptr,0,&reply ); // フォルダ処理
                        NavDisposeReply( &reply ); // NavReplyRecord構造体破棄
                        break;

                    case kNavUserActionOpen:  // 開くボタンが押された

                        navGetEventSheeProc( wptr,1,&reply ); // ファイル処理
                        NavDisposeReply( &reply ); // NavReplyRecord構造体破棄
                        break;
                }
                break;

            case kNavCBTerminate:

                NavDialogDispose( parm->context ); //シートウィンドウを削除
                break;
        }
    }
}


getFilesInfo()ルーチンがどちらの処理から呼ばれたのかは、引数のkindがゼロか1かで判断できます。最初にAEGetNthPtr()を使い、選択された個数分だけフォルダやファイルのFSRefを得ます。その後、FSGetCatalogInfo()によりFSRefをFSSpecへ変換し、同時にFSpGetFInfo()によりファイルタイプを得て、それ以降の処理に渡すための情報を準備しています。しかし、こうした処理はアプリケーション側の処理内容によっては不必要な場合もあるでしょう。

short getFilesInfo( WindowRef window,long kind,NavReplyRecord *reply )
{
    long         i,ct=0;
    DescType     rtype;
    FSRef        fsref;
    short        ret=1;
    OSType       type;
    FSSpec       fsc;
    Size         len;
    AEKeyword    key;
    FInfo        ff;
    long         ct;

    AECountItems( &reply->selection,&ct ); // 幾つオブジェクトが選択されたか?
    if( ct )
    {
        for( i=1;i<=ct;i++ )              // 選択された個数分だけループする
        {
            if( ! AEGetNthPtr( &reply->selection,i,typeFSRef,&key,&rtype,
                                           (Ptr)&fsref,sizeof( FSRef ),&len ) )
                                          // 選択オブジェクトのFSRefを得る
            {
                FSGetCatalogInfo( &fsref,kFSCatInfoGettableInfo,NULL,NULL,
                                                                  &fsc,NULL );
                                          // FSRefからFSSPecを得る
                FSpGetFInfo( &fsc,&ff );  // Finder情報を得る
                type=ff.fdType;           // ファイルタイプを抽出

                if( kind==0 ) // ここにフォルダ選択用の処理を記述する
                {
                }
                else          // こちらにはファイル選択用の処理を記述する
                {
                }
            }
        }
        ret=noErr;
    }
    return( ret );
}


今回で「Navigation Service API」の話は終了です。次回は、Macintoshの「もうひとつのファイル読み込み方法」である、Finderからアプリケーションへのアイコン(フォルダやファイル)のDrag&Dropについて解説したいと思います。

つづく

「Behind the WebObjects」  第40回  田畑 英和

 運用の解説の3回目ですが、前回Monitorの設定までおこないましたので、今回はいよいよアプリケーションを登録して運用を開始する方法を説明したいと思います。

アプリケーションの登録

 Monitorを起動すると最初に「Applications」画面が表示(パスワードを設定している場合はパスワードの入力が必要)されますが、ここでアプリケーションの登録をおこないます。すでにアプリケーションを登録している場合は、この画面に登録済みのアプリケーション一覧が表示されます。
 アプリケーション名を入力するテキストフィールドが画面の下にありますので、こちらにアプリケーション名を入力して「Add Application」ボタンをクリックします。するとアプリケーションの設定画面に移動しますので、ここでこれから運用をおこなうアプリケーションの様々なパラメータを入力していきます。
 入力項目は色々あるのですがまずはPathの入力をおこなってください。ここにはアプリケーションの起動スクリプトのパスを入力します。起動スクリプトのパスですが、”MyApp”という名前のプロジェクトをインストールした場合、起動スクリプトのパスは以下のとおりになります。

・起動スクリプトのパス(Mac OS X用)
/Library/WebObjects/Applications/MyApp.woa/MyApp

 インストール先は自由に変更することができますが、特に指定しなかった場合はこのようなパスになります。このパスをMonitor上で登録するわけですが、Pathの入力フィールドはプラットフォームごとに分かれていますので、適切なフィールドにパスを設定してください。パスは直接入力することもできますが、Monitorの「Path Wizard」機能を利用して画面上でパスを指定することもできます。

起動の確認

 最低限パスの設定をおこなえばアプリケーションを起動することができますが、まずは運用環境でアプリケーションが正常に起動するかを確認しておきましょう。設定ミスのためにアプリケーションを正常に起動できない場合も考えられます。
 起動の確認をするにはターミナル上で起動スクリプトを実行してみてください。設定が正しくおこなわれている場合はこれでアプリケーションが起動します。開発環境ではアプリケーションの起動時に自動的にWebブラウザが起動してアプリケーションにアクセスしますが、運用環境ではこれを手動でおこなう必要があります。
 もしアプリケーションが正常に起動しない場合は、アプリケーションが利用しているフレームワーク/ライブラリが運用環境にインストールされているかや、モデル上でデータベースの接続情報が正しく設定されているかを確認してください。開発環境と運用環境でデータベースを使い分けている場合、データベースの接続情報の書き換えが必要になります。

起動の確認

 さて、起動の確認ができましたらいよいよ運用の開始です。パスを設定したアプリケーションの設定画面の右上に「Detail View」ボタンがありますのでこちらをクリックしてインスタンスの一覧画面に移動します。WebObjectsでは同じアプリケーションを複数同時に起動することができますが、起動した個々のアプリケーションのことをインスタンスと呼んでいます。
 画面の下に追加するインスタンスの数を入力するフィールドがありますのでまずはインスタンス数1で「Add」ボタンをクリックしてください。するとインスタンスが1つ追加されて、デフォルトの設定ではそのまま自動起動するようになっています。しばらくするとStatusがONになりインスタンスの起動が確認できます。いつまでもStatusがOFFのままの場合はどこかで設定が間違えているはずですので、もう一度最初から設定を確認してみてください。
 また、本格的な運用をおこなう前にまずは単純なサンプルアプリケーションを作成してそちらで運用のリハーサルをおこなうのもよいでしょう。

複数インスタンスの起動

 WebObjectsの運用環境は標準で負荷分散に対応しており、1台のアプリケーションサーバ上でインスタンスを複数起動することができます。もっともメモリなどサーバのリソースには限りがありますので、ある程度の規模になるとアプリケーションサーバを追加してシステムのスケーラビリティを上げることもできます。
 インスタンスを複数起動するのはMonitor上で簡単にできるのですがこのとき注意しなければならないことがあります。それはインスタンス間でのデータの同期です。EOFはデータベースから取得したデータをキャッシュしますのであるインスタンスでデータが変更されても、別のインスタンス上では古いデータを保持したままの状態であることが考えられます。また他のアプリケーションから同一のデータベースを更新したときにも同様の問題が発生します。
 このようなインスタンス間でのデータの不整合を防ぐには、アプリケーションを開発する時点でキャッシュを考慮した設計をおこなっておく必要があります。WebObjectsにはキャッシュを回避してデータをリフレッシュするAPIも用意されています。以前この連載で自動的にインスタンス間でのデータの同期をおこなうProject WONDERのChangeNotification JMSを紹介しましたが、残念ながら場合によっては動作に問題があるようです。
 他のインスタンス(あるいはアプリケーション)によって更新される可能性のあるデータは、毎回直接データベースを参照するなどして明示的にデータのリフレッシュをおこなう必要があります。

The End

 さてこの連載ですが実は今回で一区切りとさせていただいて、次回より新連載を開始します。これまではWebObjectsの様々な技術を紹介してきましたが、これからは実際にアプリケーションを開発しながらその過程を逐次レポートしていく、より実践的な連載を始める予定です。実際の開発現場で問題になるようなことを積極的に取り上げていく予定ですので、ご期待ください。

ニュース・解説

今週の解説担当:木下 誠

———————————————————————-
WWDCのセッションが一部公開
———————————————————————-

WWDC 2005の情報が更新されました。それに伴い、カンファレンス・セッションとハンズ・オン・セッションのタイトルの一部が公開されています。今年のWWDCは、もちろんTigerがメインになるでしょうから、Spotlight、Core Data、Dashboard、Automator といった、すでに公開されている Tiger 技術のセッションが目を惹きます。これからも、公開されていく情報は要注目です。

WWDC 2005の事前受付は、4月22日までです。

WWDC 2005
http://developer.apple.com/wwdc/index.html

WWDC 2005 – Conference Sessions
http://developer.apple.com/wwdc/descriptions/desc-p.html

WWDC 2005 – Hnads on Sessions
http://developer.apple.com/wwdc/descriptions/desc-ho.html

———————————————————————-
OpenGLの最適化を解説したドキュメント
———————————————————————-

Appleが、Mac OS XでのOpenGL最適化を解説したドキュメント、「Optimizing OpenGL Data Throughput on Mac OS X」を公開しています。

OpenGLの最適化では、GPUとCPUのパワーバランスを考えることが重要になります。このドキュメントでは、CPUとGPUの最適な利用の仕方を簡単に解説しています。

このドキュメントが主眼においているのは、頂点データの処理の最適化です。頂点データを使った描画処理、静的な頂点データ、動的な頂点データを扱う上で気を付ける点を、サンプルコードとともに解説しています。

http://developer.apple.com/graphicsimaging/opengl/optimizingdata.html

———————————————————————-
Mach-Oコードをメモリ上から実行するサンプル
———————————————————————-

サンプルコード「MemoryBasedBundle」が公開されました。このサンプルは、Mach-Oのコードをメモリ上から実行する方法を紹介しています。

DarwinのAPIである、NSCreateObjectFileImageFromMemoryを使って実行しています。また、CFBundleを使ったり、他の dyld APIを使って実行するサンプルも含まれています。

MemoryBasedBundle
http://developer.apple.com/samplecode/MemoryBasedBundle/MemoryBasedBundle.html

———————————————————————-
Keynote 2 のファイルの読み書き
———————————————————————-

Technical Q&A 1412が公開されました。Keynote 2のファイルを読み書きする方法を取り上げようとしています。

Keynote 2のファイルは、Keynote 1.xから大きく変更されたため、そのままでは読めないようです。このQAでは、Keynote 2のファイルを読むアプリケーションを作りたい方はDeveloper Relationsまでメールを送るように、とだけ書かれています。あまり役に立たないQAでした。

関連情報:
Keynote 1.xのXML schemaは、こちらにあります。
Technical Note TN 2067
About the Keynote XML File Format (APXL Schema)
http://developer.apple.com/technotes/tn2002/tn2067.html

Technical Q&A QA1412
How can I add the ability to read and write Keynote 2 documents to my application?
http://developer.apple.com/qa/qa2005/qa1412.html

MOSAからのお知らせと編集後記は割愛します

MOSA Developer News   略称[MOSADeN=モサ伝]
Apple、Mac OSは米国アップルコンピュータ社の登録商標です。またそのほかの各製品名等はそれぞれ各社の商標ならびに登録商標です。
このメールの再配信、および掲載された記事の無断転載を禁じます。
特定非営利活動法人MOSA  http://www.mosa.gr.jp/
Copyright (C)2005 MOSA. All rights reserved.