home link download back number special issue

HMDT - Special Issue / CoreFoundation の秘密 / Base Services - Toll-free bridge の実現方法


- Base Services -
Toll-free bridge の実現方法

Toll-free bridge とは?

CoreFoundation のいくつかのオブジェクトは、Cocoa のクラスとの間を toll-free bridge で結ばれているんだ。えっ?Toll-free bridge とはなにかって?ここでは、オブジェクトを変換することなく、キャストするだけで相互互換可能な仕組みのことらしい。

たとえば、CoreFoundation の CFStringRef と、Cocoa の NSString は toll-free bridge で結ばれてるんだ。CFStringRef には CFStringGetLength() っていう関数がある。これを NSString のオブジェクトに使うことができるんだ。

(sample)
NSString* cocoaString;
CFIndex length;

cocoaString = @"Cocoa String";
/* これは OK! */
length = CFStringGetLength((CFStringRef)cocoaString);

逆も同じ。CFStringRef のインスタンスを使って、NSString の length メソッドを呼び出せる。

(sample)
CFStringRef cfString;
int length;

cfString = CFSTR("CoreFoundation String");
/* これも OK! */
length = [(NSString*)cfString length];

ここではキャストを付けているけど、warning を気にしなければ、付けなくても OK だ。

こんな感じで使える toll-free bridge。実際に使ってみると、ユーザの立場からはすごく楽。キャストするだけで、2 つのフレームワークをいったり来たりできるなんて、あー楽チン。Mac OS X 10.2 では、以下のクラスが toll-free bridge として提供されているんだ。

CoreFoundation Cocoa
CFArray NSArray
CFCharacterSet NSCharacterSet
CFData NSData
CFDate NSDate
CFDictinoary NSDictionary
CFRunLoopTimer NSTimer
CFSet NSSet
CFString NSString
CFURL NSURL

CFArray や CFDictionary があるのに、Enumerator がないのは片手落ちのような気がするぞ。詳しいことは、Apple の Foundation のリリースノートのドキュメントを見てくれ。
(http://developer.apple.com/techpubs/macosx/ReleaseNotes/Foundation.html) "Toll-free" Bridging with CF Types

CoreFoundation と Cocoa のオブジェクトの判別

ということで、toll-free bridge がどうやって実装されているのか見てみよう。ここでは CoreFoundation API に Cocoa のオブジェクトが渡されたときにどうなるか、っていうところを見ていく。Toll-free bridge を実装するには、渡されたオブジェクトが CoreFoundation のものか、Cocoa のものかを判別する必要がある。まず、その仕組みを見てみよう。

まずは、CFRuntimeBase 型から。すべての CoreFoundation のオブジェクトは、この型を先頭に持っている。この中に _isa というポインタがあるんだ。

CoreFoundation/Base.subproj/CFRuntime.h
typedef struct __CFRuntimeBase {
    void *_isa;
#if defined(__ppc__)
    uint16_t _rc;
    uint16_t _info;
...
#endif
} CFRuntimeBase;

この _isa ポインタは、Cocoa のオブジェクト(つまり Objective-C の object)にもある。

objc.h
typedef struct objc_object {
    Class isa;
} id*;

typedef struct objc_class {
    struct objc_class *isa;
    ...

} *Class;

こんなわけで、両方のオブジェクトの先頭に isa があるんだ。だから、いきなりキャストしあっても、isa なら問題なくアクセスできる、っていう仕組みなんだ。で、この isa ポインタを使って、オブジェクトが CoreFoundation のオブジェクトか Cocoa のオブジェクトかを判別してるんだ。CoreFoundation のオブジェクトの場合、isa は次のような感じで初期化される。

CoreFoundation/Base.subproj/CFRuntime.c
CFTypeRef _CFRuntimeCreateInstance(
                CFAllocatorRef allocator, 
                CFTypeID typeID, 
                uint32_t extraBytes, 
                unsigned char *category) 
{
    CFRuntimeBase *memory;
    ...

    memory->_isa = __CFISAForTypeID(typeID);
    ...

isa__CFISAForTypeID() で初期化している。_CCFISAForTypeID() はなにかっていうと、CFTypeID に応じたクラスのポインタを返すんだ。こうして isa が CoreFoundation のオブジェクトと結び付けられるんだ。

(もうちょっと突っ込むと、実は Cocoa には CoreFoundation に対応したクラスが用意されている。たとえば、CFString に対応する NSCFString といった具合に。NSCFString は Objective-C のクラスだ。__CFISAForTypeID() は、このクラスを返す。NSCF** 系のクラスを CoreFoundation は覚えている。それと比較することによって、CoreFoundation のクラスか Objective-C のクラスかが分かるんだ。)

Toll-free bridge の実現

じゃ、実際の関数の中を見てみよう。たとえば CFRetain ではこんな感じ。

CoreFoundation/Base.subproj/CFRuntime.c
CFTypeRef CFRetain(CFTypeRef cf) {
    ...
    CFTYPE_OBJC_FUNCDISPATCH0(CFTypeRef, cf, "retain");

CFTYPE_OBJC_FUNCDISPATHC0() っていうマクロが、toll-free bridge を実現するためのものだ。こいつの定義はこうなっている。ちなみに、引数の数に応じて CFTYPE_OBJC_FUNCDISPATHC1() になったり CFTYPE_OBJC_FUNCDISPATHC2() になったりする。

CoreFoundation/Base.subproj/CFInternal.h
#define CFTYPE_OBJC_FUNCDISPATCH0(rettype, obj, sel) \
        if (CFTYPE_IS_OBJC(obj)) \
        {rettype (*func)(void *, SEL) = (void *)__CFSendObjCMsg; \
        static SEL s = NULL; if (!s) s = __CFGetObjCSelector(sel); \
        return func((void *)obj, s);}

このままだと分かりにくいよな。CFRetain() の内部で展開してみよう。

(extrancted macro)
CFTypeRef CFRetain(CFTypeRef cf) {
    ...
    if (CFTYPE_IS_OBJC(cf)) {
        CFTypeRef (*func)(void *, SEL) = (void *)__CFSendObjcMsg;
        static SEL s = NULL;
        if (!s) s = __CFGetObjCSelector("retain");
        return func((void *)cf, s);
    }

まず func っていう変数に関数ポインタを代入する。関数の型は CFTypeRef (*)(void*, SEL) っていう型。これって、つまり引数なしの Objective-C のメソッドの型っていうことだね。この func に代入されるのは __CFSendObjcMsg っていう関数のポインタだ。次にセレクタを取得する。セレクタは __CFGetObjcSelector() っていう関数にセレクタの名前を渡すことによって取れる。static 変数に代入しているから、キャッシュされることになるんだね。そして、func つまり __CFSendObjcMsge を呼び出す。引数として、オブジェクトとセレクタを渡す。この中で、Objective-C のメソッドが呼び出されるのでしょう。

というわけで、まとめると、toll-free bridge が用意されている関数を呼び出すと、isa ポインタを見て Objective-C のクラスかどうか判別する。Objective-C のクラスだったら、対応するメソッドのセレクタを取り出す。そしてオブジェクトに対してメッセージを送ってやって、関数を抜ける。っていうことになるんだ。

ということは、実装が二重に用意されていることになる。たとえば、CFStringRef と NSString は、似たような機能を持っているけど実装は別々なんだ。これは二重の手間だよな。もしかすると、NSString の実装の中で、CFStringRef のコードを呼び出しているのかもしれない。これは Cocoa のソースコードを見ないと分からないから、なんともいえない。将来的に 1 つの実装になるようになるのかもしれないし、あくまで CoreFoundation と Cocoa の実装は別物として扱うかもしれない。


Home | Link | Download | Back Number | Speciall Issue

mailto: mkino@xd5.so-net.ne.jp

HMDT