今回は、FSSpec、FSRef、AliasHandle、CFURLRefを用いたファイルアクセスの仕組みについてもう少し深く解説し、その例題として、FSSpecの代わりにFSRefを得ることが可能な「ファイル名入力ダイアロ」(シートウィンドウ版)を紹介します。
Mac OS Xでは、FSSpecの代わりにFSRefを用いてファイルアクセスすることで、Unicodeのロングファイル名(255文字)のサポート、大容量ファイル(4Gバイト以上)へのアクセス、ファイル処理のパフォーマンス向上などのメリットが得られます。Mac OS X 10.3からのCarbon FrameworkやQuickTime 6では、ファイルアクセスをFSSpecのみに依存しているAPIは拡張され、FSRefの併用もOKとなりました。ですから、これからのCarbonアプリケーションでは、積極的にFSRefを利用することが可能です。
まず最初に、FSSpecとFSRefの両構造体を示して比較してみます。

上記のように、FSSpec構造体でのファイル名格納場所はパスカル文字列で63バイト分しかありませんので、Unicodeで255文字(510バイト)のファイル名に対応できないのは明らかです。このままだと、Navigation Serviceで得られたロングファイル名のFSSpecは利用不可のように思われますが、何と「A long long long long file#23A4」といった具合に、名称の最後に「謎の参照値」(笑)が付加されて機能を維持しています。片やFSRef構造体の方は、内容が明示されていない(内緒の...)80バイトのデータ配列を持つだけです。こちらも構造体内にはロングファイル名は格納できないので、配列内に保存された何らかのリファレンス値で別構造体を参照し、ファイルを特定していると考えられます。
前回も言及しましたが、FSSpecは参照先ファイルが無くても定義できますが、FSRefの方はファイルが存在しないと定義できません。つまり、FSCreateFileUnicode() APIなどでファイルを作成する場合には、そのファイルが入るフォルダのFSRefと、Unicodeでエンコードされたファイル名が必要となるわけです。加えて、その構造体内だけではファイルへのリファレンス情報を保持できないので、定義したFSRef構造体をドキュメントに記録し、それを再度読み込んで同じファイルへアクセスしようとすると失敗します。つまりFSRef構造体は、その名前のとおりアプリケーション内で一時的に用いられる「ファイルへのリファレンス」として働いているわけです。
こうした理由から、本サンプルがドキュメント内に記録しているのは、再度読み込んでもファイルアクセスが再現できるFSSpec構造体の方です。もし、Unicodeのロングファイル名が必要になれば、FSSpecからFSRefを作り入手できます(今回のリスト表示では行っていませんが...)。また、FSRefをCFURLRefに変換してから記録しておくという手段もあります。CFURLRefは指定ファイルまでの「パス」をUTF8エンコードとして保存していますので、これだけで完全にファイル位置を把握可能です。加えて、CoreFoundation Frameworkには、ファイル参照のためにCFURLRefしか受け取らない「わがままなAPI」もありますので(笑)、それらを手なずけるのには都合が良いでしょう。ちなみにパスを記憶するだけならば、FSRefMakePath()でUTF8エンコードのパスを作成するという手段もあります。
ただし、FSSpecもCFURLRefもターゲットファイルの保存場所が変わる(パスが変更される)と、その時点で参照できなくなる欠点があります。そうしたケースが想定される場合には、FSSPecやFSRefからAliasHandleを作り、こちらををドキュメントへと記憶することで対処します。Alias Managerは受け取ったAliasHandleの中身を解析し、そのファイルを探し出すために「最大限の努力」をします。AliasHandle内にはファイルのID番号も記録されていますので、同じボリューム内ならば、保存場所が変えられても発見することができます。加えてネットワークボリュームのマウントを要求し、その中をサーチすることまで可能となっています。こうしたファイルの検索順序や方法については、Alias Managerのオプション設定により、ユーザ側で色々とコントロールすることが可能です。
続いて、FSRefを活用する例題として、ファイル名入力用のシートウィンドウを表示するnavPutFileSheet()ルーチンを紹介します。シートウィンドウは親ウィンドウ(通常は保存するドキュメントが表示されているウィンドウ)のタイトルバーの下から降りてきますので、ルーチンの引数には親ウィンドウのWindowRefを渡すことが必要です。

オプション設定の方法については、前回のNavPutFile()とあまり変わりません。ただし、NavGetDefaultDialogOptions()の代わりにNavGetDefaultDialogCreationOptions()を用い、文字列としてパスカルストリングスの代わりにCFStringRef(Unicode文字列)を代入します。NavCreatePutFileDialog()でシートウィンドウを作成し、NavDialogRun()で表示させます。navMyPutFile()との違いは、シートウィンドウを表示し終わると、そのままルーチンから抜け出てしまうことです。では、保存ボタンなどが押された時の処理は、どこに記述すれば良いのでしょうか?
ユーザ操作に対応するための処理は、NavCreatePutFileDialog()に引数として渡されるイベント処理ルーチンのnavPutEventSheetProc()に記述します。

ユーザがシートウィンドウ上でどんな操作をしたかは、引数で渡されるNavCBRecPtr経由で判断することが出来ます。この例では、保存ボタンがクリックされた時点でドキュメントを保存するフォルダのFSRef(ドキュメント自身のFSRefではないので注意!)を得て、それを入力ファイル名のreply->saveFileName(CFStringRef)と共に自作ルーチンに渡し、ドキュメントファイルを保存する処理を実行しています。
FSRefとCFURLRefの使用方法については以下の2つのテックノートが大変参考になりますので、興味がある方はぜひ参照してみてください。どちらも日本語訳が存在しています。
TN2078「Migrating to FSRefs & long Unicode names from FSSpecs」(日本語版)
http://developer.apple.com/ja/technotes/tn2078.html
TN2022「The Death of typeFSSpec:moving along to typeFileURL」(日本語版)
http://developer.apple.com/ja/technotes/tn2022.html
次回は、「フォルダ選択」や「ファイル読み込み」時に使うNavigation ServiceのAPIを説明します。具体的には、ファイルやフォルダを選択するためのダイアログを表示するNavChooseFolder()とNavGetFile()の使用方法や注意点についての解説となります。
copyright 2005 Ottimo, Inc. All rights reserved
無断転載・引用禁止
Contact us: koike@ottimo.co.jp