● Carbon視点でCocoa探求(2008/11/12)

  このニュースは、MOSAの会員にのみ配布されているデベロッパー向けの
  デジタルマガジンMOSADeNのに掲載された記事です。ほぼ一ヶ月遅れで
  ここに掲載されて行きます

 〜 得られたファイルの種類を調べる 〜


今回は、 エイリアスファイルに関わる処理をするresolveAlias()ルーチンや、パス名で示されたファイルがImageBrowserで扱える画像かどうかを判断しているchkImageFile:クラスメソッドを中心に解説します。

まずは、ファイル操作関連のドキュメントから紹介したいと思います。Mac OS Xのファイルシステムに関しての総論については以下のドキュメントを参照します。

File System Overview

次はCocoaアプリケーションでのファイル処理についての解説です。以下のドキュメントには、オープン・パネル(Carbonのファイル選択ダイアログ)やセーブ・パネルなどの利用方法が解説されています。

Application File Management

こちらは、Cocoaアプリケーションで確保したオブジェクトをアーカイブとしてファイル保存するための方法が解説されています。

Archives and Serializations Programming Guide for Cocoa

次のドキュメントは基本的なファイル操作(作成、オープン、リード、ライトなど)についての解説です。NSFileManagerやNSFileHandleクラスのメソッド紹介が中心ですが、今回取り上げる「エイリアス解析」についても、21ページからの「Resolving Aliases」において解説されています。

Low-Level File Management Programming Topics

Mac OS Xのファイルシステムでは、ファイルの種類(JPEGなのかTIFFなのか等)を判別するために「UTI」(Uniform Type Identifiers)が導入されています。これは、Mac OS X 9までのファイルタイプ(半角4文字)による判別や、拡張子(.textなど)による判別に取って代わる仕組みです。以下のドキュメントは、そのUTIについての解説です。

Uniform Type Identifiers Overview

今回の話で取り上げるchkImageFile:メソッドでは、 UTIを調べることで、ImageBrowserで扱える画像ファイル(ムービーとQuartz Composerファイルも登録可能)かどうかを判断することになります。

まずはエイリアスの中身を解析して、その実体ファイルの保存場所(パス名)を得るためのresolveAlias()ルーチンです。これはCルーチンとして提供されています。Objectiv-Cのクラス内では、Cルーチンも問題なく記述することができます。そうしたルーチン内でObjectiv-Cのメソッドを利用することも可能ですが、そのクラスのインスタンス変数にはアクセスできませんので注意してください。CocoaのFoundation FrameworkとCore Foundation Frameworkで取り扱うオブジェクト(例えばNSStringとCFStringRef)には互換性がありますので、お互いに対応するAPIをうまく利用すれば、CarbonとCocoaで共通利用できるユーティリティルーチンが作成できます。

NSString *resolveAlias( CFStringRef path )
{
  CFStringRef  respath=NULL; // 実体が得られない場合はNULLが返る
  Boolean    folder, alias;
  CFURLRef   url,resurl;
  FSRef     fref;
  OSErr     err;

  if( url=CFURLCreateWithFileSystemPath( kCFAllocatorDefault,path,kCFURLPOSIXPathStyle,FALSE ) )
  {                  // パス名からCFURLRefを得る
    if( CFURLGetFSRef( url,&fref ) ) // CFURLRefからFSRefを得る
    {
      err=FSResolveAliasFile( &fref,true,&folder,&alias ); // エイリアス解析
      if(err==noErr && alias ) // エイリアスであれば実体のFSRefを得る
      {
        if( resurl=CFURLCreateFromFSRef( kCFAllocatorDefault, &fref ))
        {          // 解析されたFSRefからCFURLRefを得る
          respath=CFURLCopyFileSystemPath( resurl,
                          kCFURLPOSIXPathStyle );
                  // CFURLRefから実体ファイルのパス名を得る
          CFRelease( resurl );
        }
      }
    }
    CFRelease(url);
  }
  return (NSString *)respath; // 実体ファイルのパス名が得られればそれが返る
}

Core Foundationで確保したオブジェクトはAutoReleaseされませんので、不用なものはCFRelease()で解放することを忘れないでください。次は、chkImageFile:クラスメソッドです。こちらはメソッド内でCore FoundationのAPIを多用する例です。呼び出し方は、 MyDocumentクラスのクラスメソッドですので...

[MyDocument chkImageFile:path];

という感じになります。メッセージのpathにはファイルパス名を設定します。こちらもresolveAlias()と同様にCルーチンとして記述しても構いませんが、比較のためにクラスメソッドとして用意してみました。

得られたファイルのパス名からCFURLRefを得て、それをLSCopyItemInfoForURL()に渡すことでLSItemInfoRecord構造体を得ています。構造体メンバーに含まれているファイル情報(拡張子とファイルタイプ)を、UTTypeCreatePreferredIdentifierForTag()やUTTypeCreatePreferredIdentifierForTag()に渡してUTI情報を得ています。最終的には、それがCoreGraphicsでサポートしている画像種類に含まれるかどうかを比較して調べます。未サポートであれば、最後に、それがムービーファイルか?Quartz Composerファイルか?を再調査し、そうであればImageBrowserへの登録対象と判断します。

+ (NSString *)chkImageFile:(NSString*)path
{
  CFStringRef    cc,type,comf=nil;
  CFStringRef    uti=nil;
  CFArrayRef    suport;
  LSItemInfoRecord info;
  CFIndex      i,ct;
  CFURLRef     url;

  if( url=CFURLCreateWithFileSystemPath( kCFAllocatorDefault,(CFStringRef)path,kCFURLPOSIXPathStyle,FALSE ) )
  {               // パス名からCFURLRefを得る
    if( ! LSCopyItemInfoForURL( url,kLSRequestExtension | kLSRequestTypeCreator,&info ) )
    {             // CFURLRefからLSItemInfoRecordを得る
      if( info.extension )  // 拡張子が付いていればそこからUTIを得る
      {
        uti=UTTypeCreatePreferredIdentifierForTag(
         kUTTagClassFilenameExtension,info.extension,kUTTypeData );
        CFRelease(info.extension);
      }
      if( ! uti ) // 拡張子は付いていなかった
      {
        if( type=UTCreateStringForOSType( info.filetype ) )
        {  // その場合はファイルタイプからUTIを得る
          uti=UTTypeCreatePreferredIdentifierForTag(
                  kUTTagClassOSType,type,kUTTypeData );
          CFRelease( type );
        }
      }
      if( uti ) // UTIを得ることができた
      {
        suport=CGImageSourceCopyTypeIdentifiers();
          // CoreGraphicでサポートする画像情報を得る
        ct=CFArrayGetCount( suport );
        for( i=0;i<ct;i++ ) // 個数分チャックを繰り返す
        {
          if( comf=(CFStringRef)CFArrayGetValueAtIndex( suport,i ) )
          {
            if( UTTypeConformsTo( uti,comf ) ) // サポート対象の種類
              break;
            comf=nil;
          }
        }
        if( comf==nil && uti ) // UTIは得たがサポート対象ではなかった
        {
          cc=CFSTR("public.movie");
          if( UTTypeConformsTo( uti,cc ) ) // ムービーファイルなのか?
            comf=cc;
          if( CFStringCompare( uti, CFSTR(
              "com.apple.quartz-composer-composition" ), 0 )==0 )
                     // Quartz Composerファイルなのか?
            comf=CFSTR( "public.executable" ); 
        }
      }
    }
  }
  return (NSString *)comf; // 画像情報文字列を返す
}

ここでのLSCopyItemInfoForURL()やUTTypeCreatePreferredIdentifierForTag()などは「LaunchServices Framework」に所属するAPIです。定義されているヘッダファイルはLaunchServices.hとUTType.hですので、APIや構造体の詳細についてはそちらを参照してみてください。

次回は、ImageBrowserへの画像登録の「別の道筋」である「ドラッグ&ドロップでウィンドウに画像を追加登録」と「アプリ・アイコンへのドロップで新規ウィンドウを開き画像を登録」について解説したいと思います。

copyright 2008 Ottimo, Inc. All rights reserved
無断転載・引用禁止