home link download back number special issue

HMDT - Special Issue / CoreFoundation の秘密 / String Services - 固定文字列の作り方


- String Servcies -
固定文字列の作り方

CFString には、定数文字列、固定文字列、可変長文字列があるけど、ここでは固定文字列の作り方を見ていくよ。

固定文字列を作る API

CoreFoundation には、固定文字列を作るための API として次のものが用意されているんだ。

  • CFStringCreateWithPascalString
  • CFStringCreateWithCString
  • CFStringCreateWithCharacters
  • CFStringCreateWithBytes
  • CFStringCreateWithPascalStringNoCopy
  • CFStringCreateWithCStringNoCopy
  • CFStringCreateWithCharactersNoCopy

これらは、何から CFString を作るか?Pascal 文字列?C 文字列?Unicode 文字列?っていうことと、もとのデータをコピーするか?しないか?っていう条件の組み合わせなんだ。

で、実装をみると、これらの API は 1 つの関数を呼び出している。それが __CFStringCreateImmutableFunnel3() だ。

CoreFoundation/String.subporj/CFString.c
__private_extern__ CFStringRef __CFStringCreateImmutableFunnel3(
                CFAllocatorRef alloc, 
                const void *bytes, 
                CFIndex numBytes, 
                CFStringEncoding encoding,
                Boolean possiblyExternalFormat, 
                Boolean tryToReduceUnicode, 
                Boolean hasLengthByte, 
                Boolean hasNullByte, 
                Boolean noCopy,
                CFAllocatorRef contentsDeallocator, 
                UInt32 converterFlags)

固定長文字列の CFString を作る関数だ。たくさんの引数があるけど、それぞれの意味はこんな感じ。

alloc
メモリのアロケータ

bytes
文字列データ

numBytes
文字列データの長さ(バイトで)

encoding
エンコーディング

possiblyExternalFormat
BOM (Byte Order Mark) が含まれていて、それがスワップされている可能性があるか

tryToReduceUnicode
16 bit Unichar じゃなくて、ASCII 8 bit でできるかどうかチェックするか。CFStringCreateWithCharacters だけは true

hasLengthByte
長さがデータに埋め込まれているか。Pascal 文字列のときは true

hasNullByte
NULL で終わっているか。C 文字列のときは true

noCopy
データをコピーしないか。NoCopy 関数のときは true

contentsDeallocator
デアロケータ

converterFlags
エンコーディングするときに使われるフラグ

と、いうわけで、これらが文字列作成に必要なデータだ。基本は、文字列データと、その長さと、エンコーディングだね。

インラインかどうかの判定

前の項で見たように、CFString はすぐ下に文字列データがあるインライン型と、外部にデータを保持する型とがある。どちらを使うかは、CFVeryPrivate.h にある __kCFVarWidthLocalBufferSize の値で決まる。

CoreFoundation/Base.subproj/CFVeryPrivate.h
enum {
     __kCFVarWidthLocalBufferSize = 1008
};

この値より小さかったらインラインで、大きかったら外部に文字列データを置くことになる。現在の値は 1008。半端な大きさなのは、このデータの前に __CFString 構造体のメンバである CFRuntimeBase と length が来るからだな、きっと。

__CFStringCreateImmutableFunnel3() の実装

では、問題の関数 __CFStringCreateImmutableFunnel3() の実装を調べてみよう。ここでの主な仕事は、

エンコードが指定されてたらデコードする
これは __CFStringDecodeByteStream3() で行われるんだ

ASCII にできそうだったら ASCII にする
これで 8 bit のデータになる

文字列のサイズを確定する
文字列を Unicode か ASCII 表現にして、最終的に必要なサイズを決定するんだ

文字列データを設定
インラインだったらコピーして、そうじゃなかったらポインタを設定する

といった感じか。けっこう大きい関数なので、フローチャートにしてみた。

処理の流れはこんなところか。それぞれの細かい処理はソースコードを見てくれ。、、、っていうだけだとしまりが悪いので、最後のところだけ抜き出してみよう。CFString のインスタンスを作って、文字列データを設定するところだ。

CoreFoundation/String.subproj/CFString.c
__private_extern__ CFStringRef __CFStringCreateImmutableFunnel3(
                CFAllocatorRef alloc, 
                const void *bytes, 
                CFIndex numBytes, 
                CFStringEncoding encoding,
                Boolean possiblyExternalFormat, 
                Boolean tryToReduceUnicode, 
                Boolean hasLengthByte, 
                Boolean hasNullByte, 
                Boolean noCopy,
                CFAllocatorRef contentsDeallocator, 
                UInt32 converterFlags) 
{    
    CFMutableStringRef str;
    CFVarWidthCharBuffer vBuf;
    CFIndex size;
    Boolean useLengthByte = false;
    Boolean useNullByte = false;
    Boolean useInlineData = false;

    ...

    /* CFString のインスタンスを確保 */
    // Finally, allocate!
    str = (CFMutableStringRef)_CFRuntimeCreateInstance(
                    alloc, __kCFStringTypeID, size, NULL);
    ...

    if (useInlineData) {
        /* インラインデータの場合 */
        uint8_t *contents = (uint8_t *)__CFStrContents(str);
        if (useLengthByte && !hasLengthByte) *contents++ = numBytes;
        /* メモリコピー */
        memmove(contents, bytes, numBytes);
        if (useNullByte) contents[numBytes] = 0;
    } else {
        /* インラインじゃない場合 */
        /* 文字列のポインタ設定 */
        __CFStrSetContentPtr(str, bytes);
        if (contentsDeallocator != alloc && 
            contentsDeallocator != kCFAllocatorNull) 
                __CFStrSetContentsDeallocator(str, CFRetain(contentsDeallocator)); 
    }
    if (vBuf.shouldFreeChars) CFAllocatorDeallocate(vBuf.allocator, (void *)bytes);

    return str;
}

というわけで、最終的にはメモリを確保してそこにコピーするわけだね。ま、あたりまえだが。さて、これで文字列データの確保の仕方は分かった。だけどもう 1 つ CFString の作成で興味深いのは、エンコーディングの部分だよな。エンコーディングは __CFStringDecodeByteStream3() っていう関数の中で処理されている。これについては、別の項で見ていくぜ。

(Nov. 22, 2002)


Home | Link | Download | Back Number | Speciall Issue

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

HMDT