Carbon Event Managerのさわり 広松 悠介
1.はじめに
さて、去年に引き続き、またもや(前よりも?)大半のプログラマに興味を持たれないことを書くことにしました。MaxOSXの開発環境・Carbonです。
基本的にToolbox面でCarbonになって変わった所なんて、一部のイニシャライズが必要なくなったことと、特定の構造体の情報が隠ぺいされたことと、新しいイベント駆動が追加されたこと程度なんで、もしクラシック用プログラム内でGame Sprocketsを使っていないなら、さっさとCarbonに移行した方がいいです。どうせCarbonLibがあればクラシックでも動くので。
今回僕が書こうとしているのは、Carbonで新しく導入されたイベント駆動についてです。実は、別にこれでイベントを実装しなくても問題ありません。GetNextEvent関数を使えば、クラシックと全く同じイベント駆動が可能なのです。ただまあ、こういうこだわったオブジェクト指向っぽいイベント駆動もあるんだ、ってことで。
※なお、ここからはマックプログラミングを始めたばかりの初心者にもわかりやすいように書くつもりはありません。まだプログラミング歴2年目の奴が偉そうに言えることではないですが。
それと、関数に使う定数は、例に挙げているものを除いて解説してません。定数について知りたいなら、例えばアップルのホームページのデベロッパのドキュメントでも見て下さい。
2.HyperTalk的コールバック関数
OSXには付属してないでしょうが、以前のマックにはHyperCardというアプリケーションがついてました。そのアプリケーションで動かせるプログラミング言語?がHyperTalkです。僕はそれをやってみたことがないので詳しくは知りませんが、例えばウィンドウ内にボタン等を配置してから、そのボタンに対し、クリックされたらこれこれこんなことをしろ、と、ボタンに対してクリックされた時の処理をプログラムできるという、とてもオブジェクト指向してる言語でした。
そのHyperTalkが、Carbon Eventとどう関係があるのかというと、まさにそのイベント処理方法がHyperTalkの方法そのものであるということです。
Carbon Eventでは、アプリケーション、ウィンドウ、メニュー(Command Event)、キーボード、マウス、コントロール等の様々な挙動に対して、(種類毎でなく単体)一つ一つにコールバック関数を設定できます。
で、どうやってコールバックをセットするのかと言うと、下記の関数を使います。
OSStatus InstallEventHandler (EventTargetRef target,
EventHandlerUPP handlerProc, UInt32 numTypes,
const EventTypeSpec* typeList, void* userData,
EventHandlerRef* handlerRef);
はい、ここで引かない引かない。大丈夫、僕だって説明するからには、この部分はわかったつもりですから。
第一引数EventTargetRef target
そもそもEventTargetRefってのがわかりませんね。中身を調べようとしても隠ぺいされてるのでさっぱりわかりませんが、要はコールバックを登録させる対象のことです。例えばアプリケーションに登録したいなら、GetApplicationEventTarget()を呼べば、返値にアプリケーションのEventTargetRefが返ってきます。
他の場合はこれと別で、GetWindowEventTarget(WindowPtr)とかいう様に、専用の関数にターゲットのポインタ等をいれないとEventTargetRefは返ってきません。メニューとかの場合に呼ぶ関数については、アップルホームページのデベロッパでCarbon Eventのリファレンス見れば載ってるので調べてみて下さい。
第ニ引数EventHandlerUPP handerProc
またもや中身を隠ぺいされている構造体ですが、実はこの構造体の中に、コールバック関数をセットするのです(コールバックの引数については、後で解説します)。セットの仕方はNewEventHandlerUPP(/*登録するコールバック関数*/)。返値がEventHandlerUPPなんで、それを使います。
第三引数UInt32 numTypes、第四引数const EventTypeSpec* typeList
これらはセットです。EventTypeSpecは中身が隠ぺいされておらず、
struct EventTypeSpec{
UInt32 eventClass;
UInt32 eventKind;
};
となっています。eventClassは、このコールバックを登録するイベントの大まかなタイプ。eventKindはその詳しい内容、といったところでしょうか。eventClassに入力するアプリケーション用の定数、kEventClassApplication(アプリケーションイベント)におけるeventKindは、kEventAppActivated(アクティベート。クラシックで言うレジューム)、kEventAppDeactivated(デアクティベート。クラシックではサスペンド)などがあります。
さて、実は第四引数typeListは、その名の通り配列を入力してもいいのです。配列でも単体でも、入力した時にその数要素数も入力しなければならないのですが、それが第三引数、UInt32 numTypesなのです。なお、他のイベントについてはやはりデベロッパで調べて下さい。
第五引数void* userDataここには自由にデータを入れられます。後でイベント駆動時に、ここに入力されたデータが引数として送られるので、うまく使いましょう。
第六引数EventHandlerRef* handlerRefこれにはインストール後のコールバックの構造体が入力されます。やはり内部は隠ぺいされているのでアクセスできませんが、後でイベント内容を変えたり、イベント終了して破棄したい時(手動で破棄しないとリークします)に、これをToolbox関数の引数使ってアクセスします。
返値はインストールの合否です。あんまり気にしないので、詳しくは知りません…ええ、いい加減ですよ。
さて、次はコールバック関数の仕様です。
pascal OSStatus MyCallBack(EventHandlerCallRef inHandlerCallRef,
EventRef inEvent, void* inUserData)
サクサク行きますか。
第一引数EventHandlerCallRef inHandlerCallRef
現在呼んでいるコールバック関数の後ろに繋がれたコールバック関数へのリファレンスです。使ったことないので、よくは知りません…ただ、コールバックの登録はスタックのようになっているようなので、現在の関数の依然に登録した関数が隠ぺいされている状態になっているようです。また、関数の優先順位は、コントロールの関数→ウィンドウの関数→アプリケーションの関数となっている様なので、下位の処理に任せたい時に、この情報を使うとよさそうです。なお、コールバック関数を呼ぶ時は
OSStatus CallNextEventHandler(
EventHandlerRef inCallRef, EventRef inEvent)
を使いましょう。
第二引数EventRef inEvent
イベント内容です。ただし、隠ぺいされてます。意味ないじゃん!と突っ込まないこと。どうせ関数内で使うイベント情報なんて一部の情報のみなんで、そこだけを抜き取れればいいわけです。そこでまたまた関数。
OSStatus GetEventParameter (
EventRef inEvent, EventParamName inName,
EventParamType inDesiredType, EventParamType *outActualType,
UInt32 inBufferSize, UInt32 *outActualSize, void *outData)
OSStatusはどうせエラーチェックなんで、解説を省略。
第一引数EventRef inEvent
もちろん、解析したいイベントを入力するわけです。
第二引数EventParamName inName
う、あ…よくわからない。ただ、定数を入れる場所だってのはわかりましたけど。普通はkEventParamDirectObjectを入力すればいいみたいです。
第三引数EventParamType inDesiredType
現在必要としているイベントの種類はどういうものかを入力するそうですが、現在はサポートされてないそうです。とりあえず、今から行うイベントの種類の定数を入力すればいいと思います。
第四引数EventParamType *outActualType
実際に摘出したイベントの種類が返ってきます。必要ない情報なら、NULLでも入れてやればOKです。
第五引数UInt32 inBufferSize欲しいデータのサイズです。基本的には構造体のサイズですが。sizeof()を使ってサイズを入力しましょう。
第六引数UInt32 *outActualSize実際のデータサイズが返ってきます。いらないならやっぱりNULLを入れてもOK。
第七引数void *outDataここが欲しいイベントが返ってくる場所です。第五引数には、基本的にはここに入力する構造体のサイズを入れればいいはずです。イベントによって構造体が違うんで、イベント毎に調べないといけないっぽいです。
さて、コールバック関数の方に話を戻して、
第三引数void* inUserData
インストール時に入力したデータが入ってます。使いたい人は使って下さい。
返値
もしこれでイベント処理を終了したいなら、noErrを返します。もし何も処理しなかったり、Toolboxに処理を移行したい時(基本的に、コールバックを登録しなくても、Toolboxが最低限の処理を用意しているようなので)にはeventNotHandledErrを返せばいいようです。
3.駆動開始
残す所…実際の処理については構造体内のデータの意味を調べれば大体OKでしょう。そして、ウィンドウやコントロール、アプリケーションを消す時(又は後)には、必ず
void DisposeEventHandlerUPP(EventHandlerUPP userUPP)を呼んでコールバックの構造体を消しましょう。ヌルイベントに関しては、EventLoopTimerという、定期的に呼ばれるコールバック、アップルイベントに関しても似たようなコールバックを、今まで説明したのと似たような方法でセットして使えばできます。
そうそう、コールバックをセットして、いよいよイベント駆動開始ですが、そこで呼ぶのが
void RunApplicationEventLoop()。後は勝手に駆動してくれます。駆動を止める時は
void QuitApplicationEventLoop()を呼べば、
RunApplicationEventLoopを呼んだ位置まで帰ってこれます。
以上、さわりは終了です。後はアップルデベロッパの英語資料を、(拒絶反応を起こす人は)のたうち回りながら読んで下さい。しばらくすれば運命(≒英語)を受け入れられる体になりますから。
4.終わりに
CarbonやCarbon Eventの仕様を知った時、すぐ連想したのがPowerPlantでした。クラスは、手続きの様々な部分を隠ぺいしています。オブジェクト指向なら、クラスのメンバ変数は基本的にprivateかprotectedにして、外からは関数呼ばないとアクセスできませんよね?ところでCarbon。やけに構造体の隠ぺいが多くありませんか?とりわけCarbon Eventなんて、駆動自体隠ぺいされてるじゃないですか。しかも、下位のイベントに処理を依託できたりして、仮想関数っぽかったりPowerPlantのコマンダっぽかったりしすぎ。
結論。Carbon-Toolboxはオブジェクト指向である。僕としてはここまでオブジェクト指向なんだから、CarbonをC++用開発環境にしてほしい。大体、Cでここまでオブジェクト指向するんだったら、C++の開発環境にした方が無理がなくていいと思う。ただそうすると、Cプログラマが離れるな…とも思うが。でもなぁ…ここまで設計が面白いのに、詰めが甘い気がしてしまったりするなあ。C++でないだけで、色々勿体無い気がするのは、僕だけでしょうか。
そういえば、Cocoaはオブジェクト指向言語・Objective-Cが開発環境だったな…でも、Objective-C自体に、魅力を感じない…アップルは、なんか色々中途半端な仕様を決めすぎです。
注記…別にどうでもいいこと
HyperCard
Macにとって古きよき時代のアプリケーション。モノクロ。HyperTalkでプログラミング可能。
HyperTalk
ほとんどのコードをボタン等のオブジェクトに打ち込む言語。HyperCardで動く。使った人曰く、「最高のオブジェクト指向言語」。
Carbon
MacOSX用の開発環境の一つ。これを使って書いてコンパイルしたアプリケーションは、CarbonLibが入ったクラシックMacOSでも動く。開発環境はこの他にCocoaともう一つ、何かあったような気がするんだが…覚えてない。
Cocoa
MacOSX用の開発環境の一つ。Objective-Cというオブジェクト指向言語を使う。こっちはCarbonと違い、OSXでしか動かない。何故かもてはやされている気がする…
PowerPlant
metrowerks社製、発電所(違)。C++で書かれたMacOS(OSX、クラシック両方)用のアプリケーションフレームワーク。Windowsで言う所のMFC。同社のCodeWarriorを買うともれなくソースのまんまでついてくる。
格好いい設計をしているにも関わらず、MFCの様に解説書が大量に出ているわけではないため、使う人が少なく、マイナーである。ソースはとても勉強になった。
ところで、やけにデベロッパって単語が多かったような…