Learning Carbon Foo's Edition

Foo: few01@mac.com
2002/12/1

1. マルチメディアプログラムの開発環境、もしくはFooの開発環境遍歴

 Mac OS Xでプログラミングをすることにした。開発目標はマルチメディアを扱うソフトウェアだ。マルチメディアソフトといってもDirectorやFlashでつくるような紙芝居ではなく、ビデオ映像を処理するソフトウェアで、計算能力を要する。それまではsgiのIRIXというOS上で開発をしてきた。IRIXのマルチメディア開発環境はさすがだ。SGIマシンが10万円なら爆発的に広がっただろう。PCの性能が上がってきたので、PCでの開発に移行したいと思い、Windows, Linux, Macを考えて、それぞれでやってみている。WindowsはWindows 2000、LinuxはVineを使っている。

 先行してLinuxで開発してみた。なんとか開発はできた。ImageLibやVideo4Linuxなど優れたライブラリのおかげだ。ただしマルチメディアソフトウェアの開発環境としてはドライバが揃っておらず、開発例も少ないため、相当に面倒であった。それにノートPCで動かしたいと思ったのだが、ノートPCだと映像や音声の入力環境(IEEE1394ドライバなど)が揃っていないという問題がある。またインタフェースは基本的にX Windowを使うことになるのだが、この開発効率と操作性が良くない。WindowsやMacに比べると現状でかなり遅れていると言わざるを得ない。

 Windowsでの開発も、Windows 2000を搭載した速いマシンを調達して開始した。ただこれは同僚がすでに別にはじめているので、そのノウハウがたまってからでも良かろうと思っている。開発資料を数冊読んで、チュートリアルにそって、いくつか作ってみたが、相当に複雑だ。ただ開発したソフトウェアが至る所で動くようになるというのは魅力的なので外せないと思う。

 MacではOS 9以前に何度か開発をしたが、その不安定さから、私には、どうしても大規模なソフトウェアを作ることができなかった。開発中のプログラムというのは、たびたび不具合で終了するのだが、その度に再起動するのでターンアラウンドタイムが長く、うんざりした。また開発用のドキュメントも少なく、英語のInside Macintoshを読み解きながらコーディングをした。

 ところがMac OS Xになって安定さに関しては全く別ものになった。またそれまでCodeWarrier, MPW, MacApp, Think Cなど様々に使ってきたが、それらを上回る素晴らしい開発環境が「ただ」で付属している。ドキュメントに関しても、Webを介して大量にアクセスできる。またiBook(Late 2001)を購入し、いつでもどこでも開発できる環境が整った。もちろんマルチメディアの扱いに関してはQuickTimeという世界最強のライブラリがある。IEEE 1394も標準でついており文句無しだ。これは作るしか無い。


2. Mac OS X上の開発環境の違い

 Mac OS X上の開発はいろいろな方法で行える。REALbasicのライセンスも持っており、簡単なアプリケーションを作るならこれがベストだ。なにしろあっという間に形になる。しかしREALbasicは、大量のデータを扱うのを苦手とする。大量の文字データやビデオ映像を扱わせると一気に動きが悪くなる。

 Project Builderを使う場合に、大きくわけてCocoa, Carbonの2種類の開発スタイルがある(もちろんBSD UNIXで作ったり, X Windowを走らせることもできるが一般的ではない)。Project BuilderとはMac OS Xに付属の開発用ソフトである。統合環境とも言われる。プログラムを作るには、いろいろなファイルを作ってそれらを組み合わせる必要があるが、Project Builderでは、この必要なファイルを管理し、コンパイルし、リンクし、といった作業を一つのソフトでできるようになっている。Visual C++のようなものだ。

 Cocoaは、Objective-CもしくはJavaというプログラミング言語を使って開発するものだ。Project BuilderにはObjective-CとJavaのコンパイラが用意されている。Cocoaのもう一つの特徴は、Interface Builderというアプリケーションでインタフェースを容易に設計できることだ。CarbonでもInterface Builderは使えるが、Cocoaほど至れり尽くせりではない。

 歴史的にはCocoaはNextStepの開発環境を移植したものであり、APIについているNSという頭文字はNextStepのことである。NextStepについてはここでは述べない(DiscoveringOPENSTEP-J.pdfを参照)。Cocoaだと、Interface Builderで単にインタフェースの見た目が作れるのではなくて、プログラミングの相当な部分もできてしまう。クラスを定義したり、インタフェース要素(ボタンやテキストフィールドなど)の関連がグラフィカルに定義できる。例えば、ボタンを押すとそれに応じて計算値を表示する場合には、計算するクラスを作って(アイコンで表示される)、ボタンからグリグリっと線を引っ張ってきてつないでやる。また計算処理のアイコンからマウスでまた線をグリグリっとひっぱってテキストフィールドにつなげば良い。

 またCocoaで使える開発用ライブラリは、それまでのMac OSのライブラリとはがらりと違う。一般にCocoaを使って開発をすると、Carbonを使うよりも何倍も効率的だと言われる。それはこのライブラリ群と、InterfaceBuilderの使い勝手によるものだ。ただし、Objective-CもしくはJavaを使う必要があり、新しいライブラリ群を使いこなさなければならない、という敷き居がある。私の場合はそれまで作ってきたプログラムが主にC++だったので、なんとかそのノウハウを生かしたい、また古いMac OSのライブラリ群にもある程度、知識があったので、それを生かしたいということを考え、Cocoaでの開発は後回しとした。

 しかし「Cocoaはやっぱり」などを読みながらCocoaコーディングをしてみると、その開発の容易さにはかなり魅力を感じる。C++にはない生っ粋のオブジェクト指向プログラミングの香りがあり、ここで扱うCarbonにはないシンプルさ、ある意味、美しさと呼べるスタイルが貫かれている。またJavaが使えるというのも魅力的だ。Javaで色々なインタフェースを作ってきたが、現状でユーザインタフェースを作るのに最も有効なプログラミング言語だと思っている。しかしJavaの実行環境は速くなってきたとはいえ、マシンの性能をぎりぎりまで引き出すというレベルには達していない。これまでもインタフェースはJavaで作りながらも、データ処理プログラムはC++で書いてきた。

 一方、今回選択したCarbonは、開発環境としては同じくProject BuilderとInterface Builderを使うことができる(もちろんInterface Builderは使わなくてもコーディングできる)。Carbonは、従来からのMac OS開発者が容易に新しいOS X環境に移行できるようにという目的で用意された開発環境である。

 そのため、従来からMac OSに存在するライブラリの多くが、そのままCarbonでは使える。またプログラミング言語もC, C++である。したがってProject BuilderにはC , C++のコンパイラが用意されている。Project Builderでは、CodeWarrierなどと同様に、プログラミング言語に関係なく同じ操作でコンパイル、リンクなどの開発作業ができる。


3. Carbon関連資料

 さて、Carbonで作ることにしたのはいいのだが、このCarbonの全体像がまったく見えなかった。書いている今もちゃんと見えているとは言い難いが。Event Handlerはどうなっているのか?リソースの扱いは?QuickDrawとQuartzの関係は?QuickTimeはどう使うの?Interface Builderは何をしてくれるのか?

 もう何もかもわからない、という状況だ。Appleの開発者用ドキュメントリストを見るとCarbon関連の膨大な資料。またAPIだけで、サンプルがなかったり、何をするのかも書かれていなかったり、まるで深い森に入って迷っているかのようだった。

3.1 ありそうでなさそうなサンプルソース

 ADC Developer Documentation 

 私は、はじめて扱う開発環境では、類似の目的のサンプルソースをとにかく集める所からはじめていた。サンプルソースを解読する過程で、解説書やAPIリファレンスをひも解いて理解を深めると、だんだんと自分がやりたい事ができるようになってくる。IRIX, Linux, Windowsいずれも開発母体や熱心な開発者たちが大量のソースを公開してくれている。これなくして一から自分で作っていたのでは"Hello World!"から抜けるのに相当な習熟期間が必要だろう。

 ありがたいことにMac OS X用にはAppleのデベロッパサイトに多数のサンプルソースが置かれている。しかしこれが曲者だった。多くはOS 9以前のものであり、またOS Xに対応していてもCocoaだったり、Code Warrier用だったり、Carbonであっても古いAPIを多量に使ったものだったりするのだ。(ただし慣れてくるとCode Warrier用や、Interface Builderを使っていないサンプルも変換できるようになってくる)それもダウンロードしてみないとわからない。せめてCarbonなのか、Cocoaなのか、Project Builder用なのか、Interface Builderを使っているのか、などのプロフィールをメモしておいてくれれば良いのに。そのため私は生鮮食品の賞味期限を調べるように、サンプルソースの登録された日が2001年後半のものを探した。

 またOS Xのデベロッパーツールをインストールすると、多量のサンプルプログラムも一緒にインストールされる。GLUTのサンプルなど素晴らしい。しかしCarbonのサンプルソースは主に"AppearanceSample"と"SimpleText"の2種のみである。ちなみにTextEditもSketchもみなCocoaで開発されている。

 ところでCocoaなのかCarbonなのかを区別する方法だが、Cocoaの場合はソースプログラムが".m"と拡張子がmになっている。Carbonは当然".c"である。なぜmなのかはしらないが、ダウンロードして解凍してみてmがついているとがっかりした。

3.2 「Converter: Creating a User Interface with Interface Builder」

Converter: Creating a User Interface with Interface Builder(pdf)

 ADCのサイトに置かれている、「Converter: Creating a User Interface with Interface Builder」というPDFファイルを読みながら作ると、とりあえずボタンとフィールドを使った簡単なアプリケーション(摂氏を華氏に変換するダイアログ)が作れるようになる。イベントハンドラーの扱い方の第一歩が踏み出せる。最初にやってみたのがこのチュートリアルだ。ただし内容は次の入門Carbonでカバーされているし、英語なので入門Carbonを持っている人にはいらないだろう。

3.3 「入門Carbon」

 O'Reilly Japan

 つい最近のことだが「入門Carbon」(O'Reilly Japan)という本を購入した。日本語で書かれた唯一のCarbon関連書籍だと思う。これは英語の"Learning Carbon"を翻訳したものである。内容は、簡単なダイアログ型アプリケーションを作る過程を通じて、Carbonでのプログラミングについて解説したものになっている。作れるアプリケーション(月旅行プランニングソフト)自体はたいしたことないのだが、導入部の文章や、解説の随所にCarbonでの開発をする上で知らないではすまないことが書かれている。私の場合、相当に苦労してから、この本を読んだので、「あぁ、そういう意味だったのか!」と目から鱗が落ちるような経験をいくつもした。これからCarbonプログラミングをする人には必須だろう。

 ただし私が目的としているような面倒なプログラムを作るには、ここに解説されている内容では、あまりに不十分だ。特にマルチメディアデータの扱いと、画面への自由な描画に関して全く触れられていない。

 

3.4 「小池邦人のプログラミング日記」「Macプロ裏ミング日記」

 小池邦人のプログラミング日記

 とりあえず画面に自由に描いてみたいと思ったのだが、「入門Carbon」を読んでも、サンプルコードを探しても、ちっともわからない。まず何を使って描けば良いのかすらわからないのだ。そんな中に見つかったMacWIREの連載記事が「小池邦人のプログラミング日記」だ。特に8回に渡って連載された「Quartz 2D (Core Graphics)を使う」は、どこにも書かれていないことがちゃんと書かれていて大変参考になった。部分的なサンプルソースしか記載されていないが、色々なことがわかった。こういう先達がいてくれて本当にありがたいと思う。

 その後、小池氏の開発日記は、以下のサイトに、もっとたくさん古くからあることがわかった。

 Macプロ裏ミング日記

ここには以降で解説したCarbon Event ManagerやNibベースのCarbonプログラミングに関して詳細に解説されている。しかし実のところ、いま読むと内容がわかるが、はじめの頃にこれを読んでもさっぱりわからなかっただろう。従来のMac OSプログラミングに十分に習熟した人を前提に書かれている。


4. Carbonプログラミングの第一歩

4.1 プロジェクト作成

 さて、本章から実際の開発に入る。まずは「入門Carbon」と重複する内容からはじめる。最も単純なアプリケーションはProject BuilderとInterface Builderで作成する。入り口はProject Builderだ。

 Project Builderは/Developer/Applicationsの下にある。ダブルクリックして起動する。

 ファイルメニューから新規プロジェクトを選ぶ。今回はApplicationのCarbon Application (Nib Based)を選ぶ。Nib BasedというのはInterface Builderを使うタイプのプロジェクトだ。main.nibというファイルが自動生成される。このmain.nibというのがInterface Builderで扱うファイルである。nibのibはInterface Builderの頭文字だ、とは書かれていたが、nが何をあらわすのかはどこにも書かれていない。New Interface Builderだろうか?Next Interface Builderだろうか?

 (Nib Based)のついていないCarbon Applicationを選ぶこともできる。こっちはmain.rが自動生成される。こっちはベタのテキストファイルである。Interface Builder用のファイルではない。

 さて、Carbon Application (Nib Based)を選ぶと、次にプロジェクト名を入れるダイアログが出てくる。作りたいプログラムの名前を入れるのがいいだろう。保存場所はデフォルトでは~/、つまりホームの下だ。好きな場所に変更して良い。

 「完了」ボタンを押すと自動的に作られてプロジェクト画面になる。左のグラフィカルなメニューの三角ボタンを押して、どういうファイルが作られたのか見てみよう。

 Sourcesには"main.c"だけがある。コメントをはずすと20行ぐらいの最小プログラムである。ResoucesにはInforPlist.stringsとmain.nibがある。InforPlist.stringsには、このプログラムの名前なんかの基本情報がテキスト記述されている。Resourcesの下のExternal FrameworksやProductsは後で触れる。ここはとりあえずmain.nibのさいころみたいなアイコンをダブルクリックしてみよう。

4.2 Interface Builderを触ってみる

 Project Builderでmain.nibをダブルクリックすると自動的に立ち上がるのがInterface Builderだ。そのAqua全開の美しい見てくれにはうっとりする。四つのウィンドウが表示される。Windowという大きなウィンドウ、main.nib - MenuBarというメニューバー、main.nibというアイコンの並んだウィンドウ、それとCarbon Controlsというツールボックスである。実は一番大事なインスペクタというウィンドウが開いていないのだが、ま、それは置いておいて。main.nibにはすでに自動的に作られたインタフェース要素がアイコン表示されている。MenuBarというのがメニューで、MainWindowというのがメインのウィンドウ、そのままだ。

 さてせっかく開いたのだから少し遊ぼう。Carbon Controlsという所から、ボタンとかスライダーとか好き勝手にWindowにドラッグアンドドロップしてみよう。好きに配置したらセーブする。簡単にインタフェースの見てくれができる。

 

4.3 Buildする

 Project Builderに戻って、プログラムをBuildしてみよう。Buildとはソースプログラムをコンパイルして、ライブラリやリソースをリンクして、アプリケーションにする操作だ。UnixだとMakeに相当する。Buildは左上のとんかちアイコンをクリックすればできる。左から三番目のとんかちの上にパソコンの画面が重なったアイコンをクリックすると、Buildした上に、アプリケーションを実行してくれる。今回はこっちをクリックしてみよう。

 しばらくコンパイルに時間がかかる(main.cをコンパイル中とか、Linkingとか出て、最後にビルドは問題なく完了しました、になる)。そして勝手にアプリケーションが起動する。さっき適当に配置したボタンなどが、ちゃんと反映されているのがわかるだろう。

 ただおかれているのではなくて、一応すべて動作する。ただ、ボタンを押しても、それに相当するプログラムが書かれていないので、何も起こらないが。

 アプリケーションを終了すると、Project Builderに戻る。アプリケーションを強制的にとめるには、Project Builderで、さっき押したアイコンの所の赤い停止ボタンを押せば良い。

4.4 main.cの中身

main.cの中身を一行づつ解説してみよう。私にも何から何までわかっているとは言えないが、わかる範囲で書き、さらにわかったことが増えたら修正してゆこうと思う。

#include <Carbon/Carbon.h>
/*
Carbonのヘッダーを読み込んでいる。Carbonは膨大なライブラリの集まりだが、この一行をincludeするだけで使えるようになる。
*/

int main(int argc, char* argv[])
{
// おなじみmain定義、何の変哲もない。

IBNibRef nibRef;
/*
main.nibを参照するために使うIBNibRefという変数を定義する。Interface Builder Nib Referenceだろうか?
*/

WindowRef window;
/*
画面に最初に描画するウィンドウ用にWindowRefを定義する。このWindowRefというのは画面上のさまざまなウィンドウを識別するもので、OSから(Window Managerから)もらうことになる。 今回はmain.nibを渡してもらうことになる。
*/
OSStatus err;

/*
エラーが発生したときに、返ってくる値を受け取るための変数を定義する。OSStatusという型は、一般的にエラー用に定義されているようだ。OSStatus型を返すメソッドがたくさんある。
*/
err = CreateNibReference(CFSTR("main"), &nibRef);

/*
mainという文字列をCFSTRで作って、CreateNibReferenceに渡す。main.nibのnibという拡張子はデフォルトなので省略されている。なぜCFSTRに変換しなければならないのかは知らないがおまじないみたいなものだとしておこう。そうするとnibRefにmain.nib、つまりさきほどInterface Builderで作ったユーザインタフェースへのリファレンス(参照子)が得られる。アドレス(&)でCreateNibReferenceに渡すと、中身を入れて返してくれるのだろう。
*/
require_noerr( err, CantGetNibRef );

/*
エラーだと、CantGetNibRefに飛んで終了である。CantGetNibRefというラベルはプログラムの後ろの方にある。
*/
err = SetMenuBarFromNib(nibRef, CFSTR("MenuBar"));

/*
次はMenuBarをセットする。SetMenuBarFromNibってそのままの関数名。引数としては先ほど値を入れてもらったnibRefと、メニューバーの名前("MenuBar")を渡す。MenuBarと言うのはInterface Builderで付けた名前である。というか、自動的につけられていた名前である。他の名前を使っても良い。例えばMainMenuという名前を付けたければ、Interface BuilderでMenuBarのところを、MainMenuと書き換えて、ここでCFSTR("MainMenu")として渡してあげれば良い。
*/

require_noerr( err, CantSetMenuBar );
// 同様にエラーなら飛んで終わる。

err = CreateWindowFromNib(nibRef,
CFSTR("MainWindow"), &window);
/*
さて、最後にwindowを作る。CreateWindowFromNibに渡すのはnibRefと、Interface Builderで付けたwindowの名前"MainWindow"である。ここも同じく別の名前にしても良い。例えば一つでなくて、たくさんの種類のウィンドウの内の一つを表示させたい時などは、名前を何種類かつけますね。WindowRefのwindowに値が入ってきます。ありがたい。これでwindowが扱える。このwindowという参照子がイベントでも描画でも出発点になる。
*/

require_noerr( err, CantCreateWindow );
// エラーならばあっさり終わる。

DisposeNibReference(nibRef);
/*
windowさえできてしまえば、こっちのもの。nibRefはお払い箱。消してしまう。
*/

ShowWindow( window );
/*
作ったウィンドウはhidden状態で、画面には表示されない。メモリ上にあるだけ。このShowWindowで画面に表示する。
*/

RunApplicationEventLoop();
/*
さて、これでイベントループに入ります。RunApplicationEventLoopを実行したら、それ以降はプログラムが終了するまで実行されない。アプリケーションが動いている間ずっとこのループです途中の処理はすべてイベントハンドラを介して行います。この簡単なプログラムにはイベントハンドラは一つも入っていない。
*/

CantCreateWindow:
CantSetMenuBar:
CantGetNibRef:
return err;
}


以上。


5. マニュアルはどこに

 プログラミングを続けてゆくと、知らない関数や構造体、メソッドやクラスが出てくる。そういう時、UNIXならばman ***としたりman -k ***とすればわかる。IRIXだとさらにBook Shelfというのがあって、そこに開発用のドキュメントが一式揃っている。しかしMac OS XのTerminalでmanとしても出てくるのはBSD UNIX関連のものだけだ。Carbon関連は出てこない。ではどうやって情報を入手するのか。

5.1 ADCで検索する

ADC

 ADCのサイトに入るとほとんどのページで上部に検索用の枠がある。ここに関数名を入力してエンターキーを押すと、関連するドキュメントが検索される。Googleを使って検索しているらしく、だいたい上位に良い解説文がヒットする。ただし全部英語である。それに、単にヘッダファイルをWebページにしただけのようなものもあり、それがけっこうな頻度でヒットする。これでは何に使うのかはわからない。それでもmanの代わりに使う頻度はこれがもっとも多い。なお、デベロッパーツールをインストールすると、その時点の最新の関連文書がHDにインストールされる(/Developer/Documentation)。たとえば、QuickDrawの事がしりたければ、/Developer/Documentation/Carbon/graphics/QuickDraw に関連文書がある。これにSharlockをかければ同じような環境になる。しかしネットワークの文書は更新されるので、ネットに接続した環境ならばADCで検索する方が良い。

5.2 Project Builderで検索する

 Project Builderには強力な検索機能がある。プログラミング中にちょっとわからなかったり、忘れたりした場合には、これを使う。検索窓に関数名などを入れて検索するとすごい速度で、今扱っているプロジェクト内をすみずみまで探してくれる。実は標準の検索だと、External Frameworkの中もガンガンに検索してくれる。このExternal Frameworkの中には、Mac OS Xで使えるライブラリのヘッダーファイルが網羅されている。なので、直接ライブラリヘッダーを検索できることになり、引数がわからなかったりした時にとても便利だ。何にしろすごく速いので多用する。ちなみにExternal Frameworkに関してはもっと詳しく後述する予定。

5.3 Googleで検索する

 実は直接Google(www.google.com)で関数名を検索することも多い。そうするともちろんトップにはADCのページがヒットするが、下の方にAppleでないプログラマ達が公開しているソースコードなどがヒットすることが多い。解説がついていたりするので、大変役に立つ。

5.4 ADCのメニューを辿る

 それぞれのライブラリがどういうことを担当しているかがだいたい分かってきたら(そこまで達するのがけっこう大変)、ADCのメニューから解説文書を辿れば良い。簡単なプログラムを作るのであれば、あまり詳しくOSの構造を知る必要はないが、普通とは違う機能を加えたくなると、やはりある程度知らないとだめだ。その意味で「入門Carbon」の最初のあたりの章は重要だと思う。コンパクトに解説されていて見通しが良くなる。今は使わないライブラリでも、それがあることをしっているだけで、後で全然違う。


6. イベントハンドラを使う

 上述のチュートリアルの温度変換ソフトや、「入門Carbon」の月旅行プランソフトなどの玩具ソフトを作った後で、まず最初に思ったのは、Interface Builderに用意されている他のインターフェース要素を使うにはどうすれば良いだろうか、というものである。

 インターフェース要素のことをコントロール(Control)というのだが、その使い方は一応Developer/Examples/Carbon/AppearanceSample にある。ほぼすべてのコントロールの扱い方が網羅されている。しかしそのためにコードが大きく、複雑になっている。なぜ小さなばらばらのサンプルプログラムにしなかったのだろう。読み解くことも練習になる、という意味だろうか。それとも単なる手抜き?またCarbon Event Handlerスタイルではなくて、WaitNextEventスタイルの古い方法が取られている。Carbon Event Handlerスタイルだと、WaitNextEventをプログラマが直接呼ぶことはない。

 しかし何はともあれCarbonでさまざまなコントロールを使いこなすには、このAppearanceSampleがわからなければ仕方がないので、解読しつつ、スライダーを使う簡単なプログラムを自作することを最初のステップとした。その前に、まず本章では、Carbon Event Handlerの説明と、もっともシンプルなイベントハンドラを使ったアプリケーションの作り方について解説する。

6.1 Carbon Event Handlerとは

 さてどのプラットフォームで開発をするにしろ、イベントハンドラというのは、避けて通れないものである。どれにでもある。開発者でイベントハンドラを知らない人はいないと思うが、おおざっぱな解説をしておく。

 普通のアプリケーションというのは、人間が指令(コマンドやマウス処理)を与えて、仕事をしている以外の時間は何もしていない。そして、その何もしていない時間というのがとても多い。たとえば電卓プログラムでは、数字ボタンを押されたら瞬時に数字を表示する。しかし次の入力までは何もすることがない。また+ボタンなどが押されると計算結果を即座に表示する。そして訓練中の犬よろしく次の指令を待ち続ける。これを待っている間ループにしてプログラミングすると、他のことが何もできなくなる。他の事というのは電卓を使いながらワープロで文書を入力したり、Webページを閲覧したりする、ということだ。だから、電卓アプリは何か指示が来るまではループをしたりしてはいけないのだ。

 そのために、OSは、何もやっていないアプリケーションが安心して休めるように指示を集中して受け付ける窓口をやっている。その窓口にやってくる指令がイベントである。

 Carbon Event Handlerでは、上述のプログラムで、RunApplicationEventLoop();が呼ばれると、プログラムは窓口をOSに渡して、待ちの状態に入る。OSは待ちに入ったアプリケーションにはCPUの演算をさせない。完全に眠った状態だ。(細かくは眠った状態にも何種類かあるが)このあたりの解説は「入門Carbon」にあり、より詳しいし、正確なのでぜひ読まれることをおすすめする。

 では眠ってしまったアプリはどうやって起きるのか、というとイベントハンドラというものを使うのである。イベントハンドラというのはモーニングコール依頼のようなものだ。「こうこう、こういうイベントがやってきたら起こして下さい」という風に記述してRunApplicationEventoLoop()を呼び出す前にOSに渡しておく。そうすると、そのイベントが発生すると、例えばアプリのボタンの所にマウスポインタがやってきて、クリックイベントが発生したりすると、OSは「さて、このイベントは誰に渡すんだっけ」ということで、依頼票を見渡して、眠っているアプリを起こすことになる。

 この仕組みは最近の計算機ならばどれでも同じである。Carbon Event Handlerというのは、Carbon環境で導入された新しいイベントハンドラである。従来はWaitNextEventという関数をアプリが明示的に呼び出すことでイベントをもらっていたが、このCarbon Event Handlerでは、そういうのがない。RunApplicationEventLoop()を呼び出したら、もう何もしない。その分、OSが良きにはかってくれる。OS Xのマルチタスク処理性能は高いので、まかしておけば無駄なことにCPUを使わない、結果としてパソコンのユーザはスムーズにパソコンを使えることになる。何もやっていないのに、いつまでもCPUの処理を続けるアプリがあると、他の作業がどんどん重くなる。そのためにもなるべくCarbon Event Handlerを使った方が良いらしい。

6.2 イベントハンドラの使い方


図に示すような単純なアプリでイベントハンドラの使い方を解説する。このアプリでは「こんちは」ボタンを押すと、下のテキストフィールドに"Hallo?"と表示される。以下にソースファイルを示し、一行づつ説明する。

#include <Carbon/Carbon.h>
// 説明済み

// Define ID for Controls
#define kHalloCommand 'hl01'
#define kAnswerFieldSignature 'txtf'
#define kAnswerFieldID 121
/*
 InterfaceBuilderで作ったボタンとフィールドに、それぞれコマンドIDとコントロールシグネチャ(Signature)、コントロールIDをつけるのだが、それを定義している。この文字列や番号でインタフェースのどれを指すかが識別される。大文字、小文字の区別あり、1とlを間違えないように。ボタンの方はプログラムで特段の制御はせず、押されたときにコマンドイベントを発行するだけ(ボタン->コマンド発行->イベントハンドラ起動、という流れのみ)なので、コントロールシグネチャやIDは定義していない。フィールドの方は文字をプログラムで入力するので定義して、後で入力する時に使う。
 InterfaceBuilder上では、コマンドID, コントロールシグネチャ、コントロールIDいずれもインスペクタで記入する。
*/

// Prototype of eventHandler
pascal OSStatus HalloCommandHandler
(EventHandlerCallRef handlerRef,
EventRef event, void *userData);
/*
 イベントハンドラのプロトタイプを定義する。イベントハンドラは必ずpascal OSStatus型で定義する。そして、引数もEventHandlerCallRefと、EventRefと、voidの3点セットに決まっている。EventHandlerCallRefは何に使うのだろう、実は知らない。2番めのEventRefには、どういうイベントが渡されたのかという情報が入ってくる。これがないと、目を覚まされたのはいいが、何で呼ばれたのかわからない。最後のvoid型には、ユーザ定義のデータを入れる。標準のEventRefでは渡されない情報を渡したいときに使う。このHalloCommandHandlerでは、WindowRefであるwindowを渡している。何を渡すかは、後述のInstallEventHandlerを呼ぶときに指定する。イベントハンドラでWindowRefをもらえると、そこからウィンドウ上のボタンやフィールドなどへの参照を得ることができる。
 なお、HalloCommandHandlerの実体は後述。
 */

int main(int argc, char* argv[])
{
IBNibRef nibRef;
WindowRef window;
OSStatus err;
// 以上、説明済み

// Define Command Event Type
EventTypeSpec commSpec =
{kEventClassCommand, kEventProcessCommand};
/*
 どういうイベントを受け取るイベントハンドラにしたいのかを定義する。commSpecという変数(EventTypeSpec型)を、イベントクラス(eventClass)とイベント種別(eventKind)で定義する。任意のイベントはeventClassとeventKindで定義される。eventClassは、マウスなのか、キー入力なのか、コマンドなのか、メニューなのか、などで範囲が指定される。evventKindにはどういうことがされたのか、マウスクリックなのか、コマンド実行なのか、などが指定される。この場合は、コマンドイベント(kEventClassCommand)で、コマンド実行(kEventProcessCommand)である。自分が作りたいイベントハンドラの受けつけるイベント名を調べることは多いだろうので、上述の"Handling Carbon Event"や「入門Carbon」にはリストが付録されている。ちなみに複数のイベントを定義する方法もそれらに書かれている。
*/

err = CreateNibReference(CFSTR("main"), &nibRef);
require_noerr( err, CantGetNibRef );
err = SetMenuBarFromNib(nibRef, CFSTR("MenuBar"));
require_noerr( err, CantSetMenuBar );
err = CreateWindowFromNib(nibRef, CFSTR("MainWindow"), &window);
require_noerr( err, CantCreateWindow );
DisposeNibReference(nibRef);
ShowWindow( window );
// 以上、説明済み

// Install Command Event Handler
err = InstallWindowEventHandler(window,
NewEventHandlerUPP(HalloCommandHandler),
1, &commSpec, (void *) window, NULL);
/*
 このInstallWindowEventhandlerでイベントハンドラを設定する。上述のイベントハンドラの解説に合わせていうと、依頼票の申請にあたる。"Handling Carbon Events"というADCの解説の"Creating and Registering and Event Handler"という章に詳しく解説されている。

 基礎となるのはInstallEventHandlerという関数(windowが抜けている)である。イベントは、「何に対して」「どういう」イベントか、で区別される。その「何に対して」の部分はさまざまである。中でもアプリケーションではwindowに対して起こるイベントが最も頻度が多い。そこでウィンドウ用の関数(実は中でInstallEventHandlerを呼び出すマクロ)が定義されている。

 第一引数のwindowは、どのウィンドウに対してか、という情報を表す。この場合はInterface Builderで作って、CreateWindowFromNibで呼び出したメインウィンドウwindowである。第二引数は、イベントハンドラへの参照子である。NewEventHandlerUPPという関数にハンドラ名を渡して作ってもらう。UPPというのはuniversal procedure pointerというやつで任意のハンドラ(プロシージャ)への参照子になる。次の"1"は受けつけるイベントの数で、この場合は一つだけ、上述のcommSpecである。その次に受けつけるイベント型変数へのアドレスを渡す。その次のwindowというのがuserDataである。今回windowを渡して、返事を書くテキストフィールドをそこから辿るために使う。最後はEventHandlerRefへのポインタを入れるらしいが、使っていないのでNULLとしておく。
*/

RunApplicationEventLoop();
CantCreateWindow:
CantSetMenuBar:
CantGetNibRef:
return err;
}
// 以上、説明済み

/*
以下がイベントハンドラ本体のコードだ。
*/
// Event handler (invoked when bottn is pushed)
pascal OSStatus HalloCommandHandler(
EventHandlerCallRef handlerRef,
EventRef event, void *userData )
{
HICommand command;
// コマンドイベントを識別するために使う

ControlHandle answerField;
// 画面内のテキストフィールドを参照する為に使う

CFStringRef text;
/*
 文字列を格納するのに使う。CFはCore Foundationの意味。Core Foundationというのは、ライブラリ群の最下層に位置する共有ライブラリだ。CFStringはUnicodeらしい。
*/


ControlID answerFieldControlID =
{kAnswerFieldSignature, kAnswerFieldID};
/*
 コントロール(この場合はテキストフィールド)は、上述したようにシグネチャとIDで識別される。それらを与えてControlIDを定義する。
*/

GetEventParameter(event, kEventParamDirectObject,
typeHICommand, NULL,
sizeof(HICommand), NULL, &command);
/*
 さて、まずは受け取ったeventのパラメータの一つである、コマンドを取り出す。どのコマンドが発行されたのか調べるためだ。第一引数はevent、第二引数はパラメータ名で、"Handling Carbon Events"のリストに列挙されている。第三引数はパラメータの型で、これもリストに列挙されている。今回はkEventCommandProcessから来たイベントの引数なので、この二つになっている。第四引数は "Carbon Event Manager Reference"によると、Actual Type of Parameterだそうだが、NULLで良いそうだ。次はパラメータのサイズ、その次はActual typeのサイズで、ここもNULL、最後がパラメータを入れてもらう変数への参照を渡す。

*/

if (command.commandID == kHalloCommand) {
/*
 入れてもらったイベントパラメータからcommandIDを取り出し、それが今回のkHalloCommandと同じ場合にのみ処理をする。そうでない時は別のコマンドがきているのだから、横取りしてはいけない。たとえば、Quitコマンドなどもイベントとしてやってくる。ここで勝手に処理してしまうと、終わらないアプリケーションにしてしまうことになる。
*/


GetControlByID((WindowRef)userData, &answerFieldControlID,
&answerField);
/*
 コントロール(何度も言うが^_^;、この場合はテキストフィールド)への参照子をGetControlByIDで得る。ここで役に立つのが、userDataとして渡されているWindowRefである。このwindowと、テキストフィールドの識別IDを渡すと、どのウィンドウのどのコントロールかがわかるので、参照子がanswerFieldに入れられてくる。ありがたやありがたや。
*/

text = CFStringCreateWithFormat(NULL, NULL, CFSTR("Hallo?"));
// Hallo?という文字列をテキストフィールドに入れようと思うので、作る。

SetControlData(answerField, 0, kControlEditTextCFStringTag,
sizeof(CFStringRef), &text);
/*
 textをanswerFieldにセットしよう。このあたりは"Inside Carbon: Control Manager"を参照。第一引数は値をセットするコントロールを指示。第二引数はコントロールが複数のパーツで構成されている場合に、どれかを指示するために使う。今回は0、たぶんkControlEntireControlのことだと思う。第三引数は、さらにコントロールのどこかを指示する。"Inside Carbon: Control Manager"のConstantsに列挙されている。このkControlEditTextCFStringTagは'cfst'らしい。次はセットするデータのサイズ、最後がじっさいにセットするデータへのポインタである。なんにしろ回りくどいように思えるが、すべて意味があるのだ。たくさんのコントロールを統一的に扱うためにうまく設計された仕組みだということがわかるだろう。

*/

DrawOneControl(answerField);
// answerFieldを画面上でアップデートする

return noErr;
// このイベントを処理するのはこのハンドラだけなので、ここでnoErrを返す

} else return eventNotHandledErr;
/*
 このelseは、上の方のif (command.commandID == kHalloCommand) {
に対応している。つまり、別のコマンドイベントの場合は、eventNotHandledErrを返す。そうすると、OSは次のイベントハンドラに処理を移す。Quitイベントなどの場合は、アプリケーションに渡され処理されることになる。だから、無関係なイベントはちゃんとeventNotHandledErrを返してあげないといけない。
*/

}

以上。

--------------TO BE CONTINUED---------------

(c) few01@mac.com --[2002/1/27]--