再:CarbonかCocoaか?

HMDTさんのところに,

October 2007 | HMDT Journal

Mac OS Xでもっともよく使われるアプリケーションは、Carbonです。

というのがあって,「cocoa carbon」でググってみると自分の書いた以下のエントリがWikipediaの次につける始末だったので,改めて調査。

XO: CarbonかCocoaか?

たぶん「CarbonかCocoaか」っていうキーワードで認識している人が多いと思うんだけど、実際は結構複雑


上記のエントリにも書いたとおり,Carbon APIとCocoa APIは,どっちかしか呼べないものではなくて,相互に呼べるのね。

なので,CarbonかCocoaかを判定するのは一体何なのか?というのを調べてみた。
判定の基準は,HMDTさんのところにもあるようにGetProcessInformationというCarbon APIを使用して調べる。
(GUIを付けたツールを「CarbonOrCocoa」という名前で作成したので,欲しい人はどうぞ。ちなみにこのツールも,Carbon APIは使ってますが,Cocoaです)

まず,Xcode 2.4.1で新規プロジェクトを作成。選ぶのは「Carbon Application」。

main.nibというのとmain.cというのが含まれたプロジェクトが生成される。
さっそくビルドして実行して,CarbonOrCocoaで確認すると,当然「Carbon」と判定される。

じゃ,自動生成された以下のmain関数で実行されている何がCarbon判定を引き起こすのか?を調べてみる。

int main(int argc, char* argv[])
{
    OSStatus                    err;
    static const EventTypeSpec    kAppEvents[] =
    {
        { kEventClassCommand, kEventCommandProcess }
    };

err = CreateNibReference( CFSTR("main"), &sNibRef );
require_noerr( err, CantGetNibRef );

err = SetMenuBarFromNib( sNibRef, CFSTR("MenuBar") );
require_noerr( err, CantSetMenuBar );

InstallApplicationEventHandler( NewEventHandlerUPP( AppEventHandler ),
GetEventTypeCount( kAppEvents ), kAppEvents,
0, NULL );

HandleNew();

RunApplicationEventLoop();
CantSetMenuBar:
CantGetNibRef:
return err;
}

この中の関数呼び出しは5個あって,順に省略していってどこでCocoaになるのかを調べる。
(RunApplicationEventLoop()は省略するとアプリがすぐに終了してしまうため,省略できない。
で,代わりにCocoaのイベントループであるところのNSApplicationMain(argc, (const char **)argv);と差し替える。
ただし,プロジェクトにCocoa.frameworkを追加し,ターゲットのプロパティで,主要クラスのところに「NSApplication」を指定しておく必要がある)。

すると以下が分かる。

  • NSApplicationMainを使っても,UI要素がCarbon Nibから生成される場合はCarbonと見なされる
  • RunApplicationEventLoopを使うとUI要素がなくてもCarbonと見なされる
  • UI要素を生成せずに,NSApplicationMainに入るとCocoaと見なされる

では,CocoaからCarbon Nibを使ってメニューやウィンドウを生成するとどうなるの?ということで,今度はXcodeでCocoa Applicationを選んで新規プロジェクトを作成。
NSApplicationの派生クラスMyApplicationを作って,主要クラスとして設定し,そのawakeFromNibにさっきのmain()の中身のうち,RunApplicationEventLoop()以外をコピペ。

それで実行すると,メインメニューやウィンドウはCarbon Nibから生成されたCarbonのメニューやウィンドウなんだけど,CarbonOrCocoaでの判定は「Cocoa」になる。

以上の調査結果をまとめると,

  • イベントループ前にUI要素が生成されていれば,そのUI要素がCarbon APIで作られたものならCarbonアプリ,Cocoa APIで作られたものならCocoaアプリと判定される
  • イベントループ前にUI要素が生成されてなければ,イベントループがRunApplicationEventLoop(=CarbonのイベントループAPI)ならCarbonアプリ,NSApplicationMain(というかNSRunLoop?)ならCocoaアプリと判定される

となる。

ちなみに,CarbonのプロジェクトのHandleNew()で,CocoaのNibファイルからNSWindowを生成するようにすることももちろんできる。
その場合でも,NSApplicationMainより前にメインメニューをCarbon Nibから作っちゃうと,やはりCarbonアプリと見なされる。

で,CarbonとCocoaを混ぜることができるわけなので,ならばFinderとiTunesがどの程度Carbonなのかというと,


nm /System/Library/CoreServices/Finder.app/Contents/MacOS/Finder
nm /Applications/iTunes.app/Contents/MacOS/iTunes

で調べると,少なくともTigerのFinder/iTunesはCocoa APIを一切利用してない,ということが分かる。
Leopardではどうかね。

で,HMDTさんの「Mac OS Xでもっともよく使われるアプリケーションは、Carbonです」というところに戻る。
実際に,TigerでAppleのアプリを起動してCarbonOrCocoaで見たのが以下。

こうしてみると,もはやAppleのリリースするアプリケーションでCarbonなのはFinderとiTunesしかない(あとDVD周り)。

iTunesはWindows版のCOM実装を外す訳にはいかないだろうし,Mac版とWindows版でコードを2つに分ける,なんてこともしないだろうから(むしろWindows版のテストをしていないという見方もあるぐらいだし),C++での実装は続くんじゃないだろうか。

Finderは,徐々にCocoa化していくことは可能と思うけど,ただ,もしAppleのアプリがすべてCocoaになったらCore FoundationとかCarbon APIはテストされなくなるんじゃないかという気がする。

Carbon API最後の砦としてのFinder。