Skip to content

Instantly share code, notes, and snippets.

@satoshin2071
Last active January 5, 2024 16:41
Show Gist options
  • Save satoshin2071/e8636d042e0c403ce287 to your computer and use it in GitHub Desktop.
Save satoshin2071/e8636d042e0c403ce287 to your computer and use it in GitHub Desktop.
CoreFoundation入門 CFStringとその周辺

#CoreFoundation入門 CFStrringとその周辺

##概要

入門 イントロのHello,Worldで使用した「CFSTR("Hello, World!")」からCoreFoundationの実装を追っていく。

##CFStringの実装

まずはCFSTRのリファレンスを確認。CFStringRef型ということわかる。

CFSTR
Creates an immutable string from a constant compile-time string.

Declaration OBJECTIVE-C
CFStringRef CFSTR ( const char *cStr );

さらにCFStringRefのリファレンスを確認

Derived from	
CFPropertyList Reference : CFType Reference

Framework	
CoreFoundation/CoreFoundation.h

Declared in	
CFBase.h
CFString.h
CFStringEncodingExt.h

Declared in CFBase.h とあるので ダウンロードしたソースコードから宣言してある箇所を確認。

CFBase.hの361行目あたり
typedef const struct __CFString * CFStringRef;

上記からわかるようにCFStringRef型はtypedefで宣言した、構造体__CFStringのポインタだということがわかる。

構造体の定義はヘッダファイルにあるべきだが__CFStringは定義されていない。つまり中身が隠されているということになる。

C言語で構造体の中身を隠しておきたいときは上記のようにヘッダファイルに定義しない**「Opaque構造体」**というのを使用する。

このパターンはiOS/MacのC言語のインターフェイスを持つフレームワークで頻出する。 (例えばCoreAudioのAudioUnit型はOpaqueAudioComponentのポインタ型。構造体定義は隠されている。)

というわけで実際にこの構造体を確認。

##__CFString構造体

CfString.c 164行目あたりで__CFString構造体の中身を確認できる。

/* !!! Never do sizeof(CFString); the union is here just to make it easier to access some fields.
*/
struct __CFString {
    CFRuntimeBase base;
    union {	// In many cases the allocated structs are smaller than these
	struct __inline1 {
	    CFIndex length;
        } inline1;                                      // Bytes follow the length
	struct __notInlineImmutable1 {
	    void *buffer;                               // Note that the buffer is in the same place for all non-inline variants of CFString
	    CFIndex length;                             
	    CFAllocatorRef contentsDeallocator;		// Optional; just the dealloc func is used
	} notInlineImmutable1;                          // This is the usual not-inline immutable CFString
	struct __notInlineImmutable2 {
	    void *buffer;
	    CFAllocatorRef contentsDeallocator;		// Optional; just the dealloc func is used
	} notInlineImmutable2;                          // This is the not-inline immutable CFString when length is stored with the contents (first byte)
	struct __notInlineMutable notInlineMutable;
    } variants;
};

先頭の「CFRuntimeBase base」がCoreFoundationのオブジェクト指向的な設計を実現している。

オブジェクト指向では「クラス」から「インスタンス」を作成し実際の処理を行っていくが、C言語のCoreFoundationではそれらは構造体で実現することができる。

  • クラスに相当する構造体が「CFRuntimeClass」
  • インスタンスに相当する構造体が「CFRuntimeBase」
  • クラスに一意の識別子32bitの「CFTypeID」を用いる

下記からそれぞれ詳しくソースコードを確認。

##CFRuntimeClass構造体

ダウンロードしたソースコードの中からCFRuntime.hを確認

typedef struct __CFRuntimeClass {
    CFIndex version;
    const char *className; // must be a pure ASCII string, nul-terminated
    void (*init)(CFTypeRef cf);
    CFTypeRef (*copy)(CFAllocatorRef allocator, CFTypeRef cf);
    void (*finalize)(CFTypeRef cf);
    Boolean (*equal)(CFTypeRef cf1, CFTypeRef cf2);
    CFHashCode (*hash)(CFTypeRef cf);
    CFStringRef (*copyFormattingDesc)(CFTypeRef cf, CFDictionaryRef formatOptions);	// return str with retain
    CFStringRef (*copyDebugDesc)(CFTypeRef cf);	// return str with retain

#define CF_RECLAIM_AVAILABLE 1
    void (*reclaim)(CFTypeRef cf); // Set _kCFRuntimeResourcefulObject in the .version to indicate this field should be used

#define CF_REFCOUNT_AVAILABLE 1
    uint32_t (*refcount)(intptr_t op, CFTypeRef cf); // Set _kCFRuntimeCustomRefCount in the .version to indicate this field should be used
        // this field must be non-NULL when _kCFRuntimeCustomRefCount is in the .version field
        // - if the callback is passed 1 in 'op' it should increment the 'cf's reference count and return 0
        // - if the callback is passed 0 in 'op' it should return the 'cf's reference count, up to 32 bits
        // - if the callback is passed -1 in 'op' it should decrement the 'cf's reference count; if it is now zero, 'cf' should be cleaned up and deallocated (the finalize callback above will NOT be called unless the process is running under GC, and CF does not deallocate the memory for you; if running under GC, finalize should do the object tear-down and free the object memory); then return 0
        // remember to use saturation arithmetic logic and stop incrementing and decrementing when the ref count hits UINT32_MAX, or you will have a security bug
        // remember that reference count incrementing/decrementing must be done thread-safely/atomically
        // objects should be created/initialized with a custom ref-count of 1 by the class creation functions
        // do not attempt to use any bits within the CFRuntimeBase for your reference count; store that in some additional field in your CF object

} CFRuntimeClass;

最初の「CFIndex version」はこの構造体のヴァージョンを示す

次の行の「const char *className」はクラス名を表し、提供するメソッド(Cなので関数ポインタ)が続く。続いているのは以下。

  • void (*init)(CFTypeRef cf); → 初期化
  • CFTypeRef (*copy)(CFAllocatorRef allocator, CFTypeRef cf); → オブジェクトの確保
  • void (*finalize)(CFTypeRef cf); → 多分解放される前によばれるやつかな。
  • Boolean (*equal)(CFTypeRef cf1, CFTypeRef cf2); → 値が等しいか
  • CFHashCode (*hash)(CFTypeRef cf); → ハッシュ値計算
  • CFStringRef (*copyFormattingDesc)(CFTypeRef cf, CFDictionaryRef formatOptions); → 文字列表記
  • CFStringRef (*copyDebugDesc)(CFTypeRef cf) → デバッグ用文字列表記

このCFruntimeClass構造体はクラス毎に定義されることになる。具体的な例として次はCFString.cを確認

CFString.cの中から「CFRuntimeClass」をキーワードに検索すると__CFStringClassがヒットする。 1193行目あたり。

static const CFRuntimeClass __CFStringClass = {
    _kCFRuntimeScannedObject,
    "CFString",
    NULL,      // init
    (CF_STRING_CREATE_COPY)CFStringCreateCopy,
    __CFStringDeallocate,
    __CFStringEqual,
    __CFStringHash,
    __CFStringCopyFormattingDescription,
    __CFStringCopyDescription
};

クラス名として"CFString"、それぞれ処理に対応する必要なクラスポインタが設定されていることがわかる。

##CFTypeID

CFTypeIDはCFRuntimeClassの種類を一意的に表すもの。定義はCFBase.hにある。346行目あたり。これでCFRuntimeClass構造体に対応する整数値を定義する。

CFBase.h

#if __LLP64__
typedef unsigned long long CFTypeID;
#else
typedef unsigned long CFTypeID;

Objective-Cでクラスが読み込まれるときにランタイムに登録されるのと同様にCoreFoundationでは起動時にクラスがランタイムに登録される。

上記の登録を行うのが_CFRuntimeRegisterClassという関数。CFRuntime.cでの実装を確認。

CFRuntime.c

CFTypeID _CFRuntimeRegisterClass(const CFRuntimeClass * const cls) {

    // className must be pure ASCII string, non-null
    if ((cls->version & _kCFRuntimeCustomRefCount) && !cls->refcount) {
       CFLog(kCFLogLevelWarning, CFSTR("*** _CFRuntimeRegisterClass() given inconsistent class '%s'.  Program will crash soon."), cls->className);
       return _kCFRuntimeNotATypeID;
    }
    __CFSpinLock(&__CFBigRuntimeFunnel);
    
    if (__CFMaxRuntimeTypes <= __CFRuntimeClassTableCount) {
	   CFLog(kCFLogLevelWarning, CFSTR("*** CoreFoundation class table full; registration failing for class '%s'.  Program will crash soon."), cls->className);
        __CFSpinUnlock(&__CFBigRuntimeFunnel);
	return _kCFRuntimeNotATypeID;
    }
    
    if (__CFRuntimeClassTableSize <= __CFRuntimeClassTableCount) {
	   CFLog(kCFLogLevelWarning, CFSTR("*** CoreFoundation class table full; registration failing for class '%s'.  Program will crash soon."), cls->className);
        __CFSpinUnlock(&__CFBigRuntimeFunnel);
	return _kCFRuntimeNotATypeID;
    }
    
    __CFRuntimeClassTable[__CFRuntimeClassTableCount++] = (CFRuntimeClass *)cls;
    CFTypeID typeID = __CFRuntimeClassTableCount - 1;
    __CFSpinUnlock(&__CFBigRuntimeFunnel);
    return typeID;
}

引数としてCFRuntimeClassのポインタ。バリデーションを通過できたら__CFRuntimeClassTableという配列に登録して、配列カウント-1したインデックスをtypeIDとして返していることがわかる。

各クラスには上記で登録されたCFTypeIDを取得するための関数が用意されている。 CFStringを例に挙げて確認する。CFStringから「CFTypeID」をキーワードに検索をかけて返り値としてCFTypeIDを設定しているものを探すとヒット。

CFTypeID CFStringGetTypeID(void) {
    return __kCFStringTypeID;
}

##CFTypeRefとCFTypeID

CFTypeID識別子は一体なんのために定義されているのか?これはオブジェクト指向のポリモーフィズムの実現に関わっている。

CFTypeIDと連動するCoreFoundationで全てのオブジェクトを表すためにCFTypeRef型について確認(CFSTRのドキュメントでCFTypeRef objが確認できる)

CFTypeRefはCFBase.hで定義されているので確認。358行目あたり

CFBase.h

/* Base "type" of all "CF objects", and polymorphic functions on them */
typedef const void * CFTypeRef;

コメントにもある通り全てのオブジェクトを受けることができる。ちょうどCFShowが「CFShow(CFTypeRef obj);」となっているので本当に受けることができるかどうかCFStringRefとCFArrayRefを渡して実験。

#import <CoreFoundation/CoreFoundation.h>

int main(int argc, const char * argv[]) {

    CFStringRef strRef;
    strRef = CFSTR("Hello world");
    
    CFStringRef texts[] = {CFSTR("soko"), CFSTR("doko")};
    CFArrayRef aryRef;
    aryRef = CFArrayCreate(kCFAllocatorDefault, (const void **)texts, 2, NULL);
    
    CFShow(strRef);
    CFShow(aryRef);
    
    CFRelease(aryRef);
    
    return 0;
}


実行結果
Hello world
<CFArray 0x100109540 [0x7fff75274f00]>{type = immutable, count = 2, values = (
	0 : <0x100001070>
	1 : <0x100001090>
)}

実験成功。文字列と配列に問題なく対応できている。

CFTypeRefからCFTypeIDへ戻る。

CFTypeRefに挿入された値が一体なんのオブジェクトなのか?を調べるためにCFTypeIDは存在する。調べるための関数は以下。

CFBase.h

/* Polymorphic CF functions */

CF_IMPLICIT_BRIDGING_ENABLED

CF_EXPORT
CFTypeID CFGetTypeID(CFTypeRef cf);

CFTypeRefを引数にCFTypeIDを返している。

##ポリモーフィズムの具体例(CFHash)

CFTypeIDとCFTypeRefでどのようにポリモーフィズムを実現しているかはCFHashの実装がわかりやすい。CFHashは引数としてCFTypeRefを取り、そのオブジェクトに対応したハッシュ値を返す。ハッシュ値の求め方はオブジェクトによって異なるのでそれぞれに対応した処理を行う必要がある。

CFRuntime.cで実装を確認。703行目あたり

CFRuntime.c 

CFHashCode CFHash(CFTypeRef cf) {
    if (NULL == cf) { CRSetCrashLogMessage("*** CFHash() called with NULL ***"); HALT; }
    CFTYPE_OBJC_FUNCDISPATCH0(CFHashCode, cf, hash);
    __CFGenericAssertIsCF(cf);
    CFHashCode (*hash)(CFTypeRef cf) = __CFRuntimeClassTable[__CFGenericTypeID_inline(cf)]->hash; 
    if (NULL != hash) {
	return hash(cf);
    }
    if (CF_IS_COLLECTABLE(cf)) return (CFHashCode)_object_getExternalHash((id)cf);
    return (CFHashCode)cf;
}

引数にCFTypeRefが渡されてバリデーション、CFHashCode の取得を行っている。

上記で使われている「__CFGenericTypeID_inline(cf)」という関数がCFGetTypeIDの実装にあたるので、CFHashCode取得の処理は__CFRuntimeClassTableを参照してクラス定義を取り出している、ということになる。

上記のようにオブジェクトからクラス定義を取り出し処理を行っている。これがCFTypeIDの役割となる。

##CFRuntimeBase構造体

最後にインスタンスに相当するCFRuntimeBase構造体の定義をCFRuntime.hで確認。 __CFRuntimeBaseで検索するとヒット。

CFRuntime.h

typedef struct __CFRuntimeBase {
    uintptr_t _cfisa;
    uint8_t _cfinfo[4];
#if __LP64__
    uint32_t _rc;
#endif
} CFRuntimeBase;


  • _cfisa → メタ情報。クラスの動的な型付けや判別を行う。
  • _cfinfo → オブジェクト情報 (型情報を表す)
  • _rc → インスタンスの参照カウント

定義までを確認。

_cfisaポインタが関連するTool-free bridegeは次に。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment