title.png

^<< 2011.06/1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 >>$

Trackback の仕組みはありませんので、コメントにでも残していただくと嬉しいかも、です。

(2011.06.11)

Mac OS X に於けるファイルポイント方法

先日、Twitterでツブヤいたら意外(?)にも結構な反応をいただきました。 昔、ってのは大体前世紀のことです:-)

FSSpec.png

昔話がてら、Mac OS X におけるファイルポイント方法についてすごーくざっくりと現時点の私の認識を書いてみようと思います。間違ってたらぜひご指摘下さい。あと、私は iOS の開発はしたことがないのでそっちはよく分かりません。

まず、ざっと次に挙げるくらいがある、と思ってます。

  • unix形式(path)
  • URL形式
  • FSRef
  • FSSpec
  • AliasData
  • 旧MacOS 形式

後半かなり昔話になっちゃうのですが、イキオイで書いてみます:p 最近Macのプログラミングを始めた方が、Appleのサイトなどで思いがけず昔のサンプルコードを読む必要に駆られたときの参考程度にしてくださいませ。

unix形式(path)

いわゆる、皆が「パス」だと捉えているものです。"/path/of/file.ext" こういう形式のやつですね。".." などを用いて相対的な表現も出来ます。、、、ってこんなのは敢えて説明するまでもないことですが。

"..namedfork" という仮想的なパスを利用したフォークへのアクセスや、volfs を利用したアクセスについては割愛します。

Cocoa のAPIや、unix由来の関数(fopenとかね) は基本的にこの形式を使います。CocoaのAPIだけを使う、つまり NSString に格納して引き回すときはいいのですが、unix由来の関数などに渡すときは NSString を const char* にする必要があります。この時にちょっと気をつけなきゃいけないことがあって、 HFS+ はファイル名やディレクトリ名を NFD(のApple改版)で正規化する、という部分です。NSStringの -UTF8String は保持する文字列をNFCで返すので -fileSystemRepresentation を使ってファイルシステムに合わせた形式で取り出す必要があります。

例えば、ホームディレクトリに "ほげ.txt" というファイルがあったとして、以下は失敗します。

fopen([pathStr UTF8String], "r");

正しくはこう。

fopen([pathStr fileSystemRepresentation], "r");

これは「げ」を正規化した結果がNFCとNFDで違うことに起因するんですが、、、下のコードを実行するとわかりますね。

NSString* pathStr = [NSString stringWithString: @"/Users/amano/ほげ.txt"];

printf("UTF8String:strlen = %d\n", strlen([pathStr UTF8String]));
printf("fileSystemRepresentation:strlen = %d\n", strlen([pathStr fileSystemRepresentation]));

結果はこう。

UTF8String:strlen = 23
fileSystemRepresentation:strlen = 26

3bytes 多い、つまり Codepoint 1つ分の違いがあることがわかります。 unix系のOSS由来で POSIXな関数を使っているものを使う時にはハマりポイントになるかもしれません。

URL形式

"file://〜〜" っていうあれで、Cocoa(Foundation)だと NSURL、Core Foundationだと CFURLRefで保持します。

APIによっては NSString(や char*) のファイルパスではなくて、こちらが要求されます。他のURLスキーマを持つリソースと透過的に扱えたりするところがミソですかね。

あと、ここで書いている各種形式を相互に変換するときに直接変換する方法がなく、URL形式を経由することで可能になる場合もあります。

FSRef

CarbonCore.framework のFiles.hには以下のように定義されています。

struct FSRef {
  UInt8 hidden[80];  /* private to File Manager; •• need symbolic constant */
};

つまり「なんだかわからないけど80bytesの領域」です。中がどうなっているかは意識する必要はありません。 最近のFileManager のAPIは殆どの場合 FSRefを使ってファイルを指定します。例えばFSGetCatalogInfo()など。

FSRefについて留意するべきは以下の2点かな。

  • プロセス内でのみ有効な値であること
    • プロセス間通信や共有メモリをつかってFSRefの内容を別プロセスに送っても使える保証はありません(ファイルパスや後述するAliasDataにする必要があります)
    • とうぜん設定値の保存などの永続化にも使えません
  • 存在しないファイル(ディレクトリ)をポイントすることはできないこと
    • そういうことをしたい場合(があるかどうかは別として)は「存在する親ディレクトリのFSRef」と「ファイル名」で保持するなどの工夫が必要

FSSpec

やっと冒頭で書いたFSSpecにたどり着いたw

ファイルシステムがHFS+になる前、HFSだったころにメインで使われていたものでFieManagerのAPIとして現在も一応(Deprecated指定されつつ)残っていますが、過去のコード資産を参照する時などを除いてあまり触れる機会ももはやないでしょう。FSSpecからFSRefへの移行の過渡期には FSRef版のAPIが提供されておらず FSSpec版のAPIしかなかったりした(QuickTime系のAPIとか)のですが、さすがに十数年かけてこの状態になりました。

同じく Files.hに以下のように定義されていて、64bit用にはもはやコンパイルを通すためだけに存在していることもわかります。

#if __LP64__
struct FSSpec {
  UInt8 hidden[70]; /* FSSpecs are invalid for 64 bit, but defined in case they appear in shared structs*/
};
typedef struct FSSpec FSSpec;
#else
struct FSSpec {
  FSVolumeRefNum vRefNum;
  SInt32 parID;
  StrFileName name; /* a Str63 on MacOS*/
};
typedef struct FSSpec FSSpec;
#endif  /* __LP64__ */

FSRefとちがって、存在しないファイルを指すこともできます。内容は構造体のフィールド定義の通りで、各ボリュームに一意な Volume Reference Numberと、親のディレクトリのIDとファイル名です。すべてのファイルとディレクトリには(そのボリューム内で一意の)IDが振られていて、存在するファイルの場合、parIDに「自分自身のID」を入れて nameは空にすることでポイントすることも(たしか)出来ました。ちなみに、いまだ使えるとはいっても、すでに内容としては(FSRef同様に)opaqueなものだと考えた方がよくって、nameフィールドを参照してファイル名を取得するのはやめた方がいいです。

AliasData

FSRefのところでプロセス間通信には使えないとか、永続化には使えないって書きましたが、じゃあどうするかというとAliasDataというデータ塊にしてそれを使っていました。例えば Finderでなにかの書類をダブルクリックした場合には、その書類に関連づけられたアプリに 'odoc' のAppleEvent が飛んできて、該当ファイルが AliasData の形で指定されてきたものです。アプリではそれを FSSpecなりFSRefに変換しなおして使うわけです。

あるいは、アプリの設定としてファイルを永続化しておく場合にはAliasDataにして、それを記録していました。おおむね200〜300bytesのバイト列です。

その名の通り実際のファイルに対するエイリアス、つまりUnixのSymbolicLink やWindowsのショートカットファイルに近いといえば近いのですが、実際のファイルにする必要がない(リソースフォークにAliasDataを格納した「エイリアスファイル」というものもありましたけど)のと、AliasDataにしておくとポイント先のファイルがファイルシステム上で移動(or リネーム)しても追随できます(ボリュームをまたいだり、ネットワーク越しだったりするとたしか何か制限があったように思います。うろ覚え)。

冒頭のツブヤキで 「厳しく躾られた」 と書いたのはこのことですね。いったんアプリを終了して、ファイルを移動してからもう一度起動したときにファイル移動に追随できないようなアプリは MacOSっぽくないとdisられたものです(^^;

旧MacOS形式

これはコロンで区切られた以下のような文字列です。

Macintosh HD:Dir1:Dir2:File.ext

まぁ旧MacOS の頃においても異なるボリュームに同じ名前を付けることはできたので、この形式の文字列だとファイルを一意に特定することもできないし、通常はUIへの表示用途でのみ使われていた、というものですね。

ツッコミ