Idlauに使った他のTips [プログラミング]
夕食時に停電があるとどんどん冷えてきて、なんだか会社に閉じ込められていたときを思い出してうんざりする。身の回りのこまごまとしたものの多くは榴岡に残してきていて、心もとないせいもある。ところで横浜の家に帰ってきてからすでに何回も停電があったが、どこぞの想像力欠乏症の知事のいるあたりは停電にはならないらしい。不公平感がある。宮城県内では人がいるのにまだ電気が回復していないところもあるので文句は言えないけど。
さて、気を取り直して、Idlauには知ってると便利、だけど知らなくても困らないというようなプログラミング上のTipsが、昨日あげた以外にもいくつか含まれている。ヒマにまかせて、面白いのを書き出しておく。
しかし実はNSLog()は単にstderrに出力しているだけのようである。従って普通のunixと同じようにstderrをリダイレクトすればファイルへの書き出しができる。つまりNSLog()を使う前のどこかで
もちろん、こう言うことをやった場合、逆にstderrが塞がってしまう。先にstderrを退避してもいいけど、Cocoaアプリではあまりコンソール出力は使われないのでそれほど不自由は感じない。
一方、launchApplication:メソッドはバンドル型の、普通は.app拡張子を持ったアプリだけしか立ち上げることができない。普通のファイルを開くにはやはりNSWorkspaceの
例えばpublic.dataをconformするUTIにはpublic.text、public.image、public.audiovisual-contentなんかがあって、さらにpublic.imageをconformするUTIとしてpublic.tiff、public.jpegなどがある、ということになっている。
これを使ってファイルだけでなく、クリップボードなどのメモリ上のデータも区別する。実際にMac OS XではUTIをもとにNSOpenPanelでの選択の制御などが行われている。
外部のファイルは基本的には拡張子に基づいてUTIが決められる。従ってそのままでは昔のタイプとクリエータに比べると柔軟性は乏しく、便利とは言えない。いずれはファイルそのものや、あるいはひとつのファイルに複数のデータが含まれているときにはそれぞれに対して、何らかの形でUTIが付加されるようになるのだろう、と僕は思っている(それってリソースフォークじゃん)。
あとは
他には
拡張子との変換のために
なぜかUTTypeEqual()に対応するメソッドがない。Cocoaでは文字列として比較しろ、ということか?でもそれならCore FoundationでもCFStringCompare()でいいはずだよな。よくわからない。
ここまでは10.5で使える。これらの他に10.6で新しく追加されたメソッドもあるけど、ここでは省略する。
このうち最初のふたつは抽象タイプとでも言うべきもので、10.5以降で実行できるタイプとみなせるのはcom.apple.application-bundleとpublic.unix-executableのふたつである。Idlauでは、それ以外のタイプは一般の書類とみなすことになる。登録されたファイルのパスからtypeOfFile:error:メソッドでUTIを知って、com.apple.application-bundleならlaunchApplication:showIcon:autolaunch:メソッドで起動する。また一般の書類ならOpenFile:メソッドに渡してしまえばいい。
public.unix-executableの場合はCocoaでは簡単に起動するということができない。この場合はunixのfork()とexec()システムコールを使うことになるけど、もうすこし簡単な
コマンドが実行できたかどうかを正確に判断するのは難しいけど、シェルのプロセスが起動できなかったら-1、プロセスは起動できてシェルの起動に失敗したら127が返るのでこれはエラーと判断できる。あとはシェルに任せるということになる。
ところで、このやりかたはさっきのNSLog()のリダイレクトに問題を起こす。unixでは子プロセスはstdin、stdout、stderrを親から受け継ぐので、もしこのsystem()で起動したアプリがstderrに出力すると、NSLog()のリダイレクト先に送られてしまう。それを回避するためにはsystem()を呼ぶ前にstderrを元に戻す、という作業をしなければいけない。だが、平均的なMac OS Xのユーザはstderrの出力をほとんど読まないので、実際にはそれほど実害はない。
さて、気を取り直して、Idlauには知ってると便利、だけど知らなくても困らないというようなプログラミング上のTipsが、昨日あげた以外にもいくつか含まれている。ヒマにまかせて、面白いのを書き出しておく。
3 その他のTips
3.1 NSLogのリダイレクト
ログを残すときに使うNSLog()はprintf()と基本的には同じフォーマット仕様で、そのうえにCocoaのオブジェクトを渡すと自動的にdescriptionを投げた文字列を出力してくれたりするので便利である。しかし出力先が決まっていてfprintf()のような機能は持っていない。例えばファイルに書き出したくてもそのままではできない。しかし実はNSLog()は単にstderrに出力しているだけのようである。従って普通のunixと同じようにstderrをリダイレクトすればファイルへの書き出しができる。つまりNSLog()を使う前のどこかで
char *fileName = "/tmp/log.txt"; int fd = open(fileName, O_APPEND | O_CREAT); int err = dup2(fd, STDERR_FILENO);としてやれば、それ以降はファイルに書き出されることになる(上の例では/tmp/log.txtに)。unixのシステムコールを知っている人ならこのコードが何をしているかはすぐわかるだろう。説明すると長くなるので親切なサイトでdup2()システムコールの説明を読んでもらいたい。
もちろん、こう言うことをやった場合、逆にstderrが塞がってしまう。先にstderrを退避してもいいけど、Cocoaアプリではあまりコンソール出力は使われないのでそれほど不自由は感じない。
3.2 アプリなのか書類なのかを知る
他のアプリケーションをCocoaアプリからプログラマティックに立ち上げるとき、NSWorkspaceを使う。例えば- (BOOL)launchApplication:(NSString *)appNameなんていう簡単なメソッドでアプリを起動することができる。しかし、「ログイン項目」の仕様ではアプリだけでなく、普通のファイルも登録できる。その場合、デフォルトのアプリが立ち上がってそのアプリで開かれることになる。
一方、launchApplication:メソッドはバンドル型の、普通は.app拡張子を持ったアプリだけしか立ち上げることができない。普通のファイルを開くにはやはりNSWorkspaceの
- (BOOL)openFile:(NSString *)fullPathを使うことになる。従ってユーザが登録したファイルがなになのかを知る必要がある。昔のMacならタイプとクリエータというそれぞれ32ビット幅の情報(4文字コード、OSType)がファイルについていてそれを読み出せばすぐにわかった。Mac OS Xではタイプとクリエータは使うな、という方向になっていて、そもそも昔からあるアプリによって作られたファイルでなければそもそも設定されていないことの方が多い。
3.2.1 UTI
ではかわりにどうするかと言うと、Mac OS XではUTI、Uniform Type Identifierというのを使え、となっている。UTIはConformanceという階層構造を持っていて、大づかみな分類から具体的なフォーマットにしぼっていくことができる。またその階層構造は多重継承的に複数のUTIにconform(適合)できることになっている。例えばpublic.dataをconformするUTIにはpublic.text、public.image、public.audiovisual-contentなんかがあって、さらにpublic.imageをconformするUTIとしてpublic.tiff、public.jpegなどがある、ということになっている。
これを使ってファイルだけでなく、クリップボードなどのメモリ上のデータも区別する。実際にMac OS XではUTIをもとにNSOpenPanelでの選択の制御などが行われている。
外部のファイルは基本的には拡張子に基づいてUTIが決められる。従ってそのままでは昔のタイプとクリエータに比べると柔軟性は乏しく、便利とは言えない。いずれはファイルそのものや、あるいはひとつのファイルに複数のデータが含まれているときにはそれぞれに対して、何らかの形でUTIが付加されるようになるのだろう、と僕は思っている(それってリソースフォークじゃん)。
3.2.2 UTI用のユーティリティ関数
UTIの判別などに使えるCore Foundationの関数がいくつかある。UTIはCFStringのオブジェクトとして扱われる。Boolean UTTypeEqual ( CFStringRef inUTI1, CFStringRef inUTI2 ); Boolean UTTypeConformsTo ( CFStringRef inUTI1, CFStringRef inUTI2 );まず、このふたつは読んで字のごとし。
あとは
CFStringRef UTTypeCreatePreferredIdentifierForTag( CFStringRef inTagClass, CFStringRef inTag, CFStringRef inConformingToUTI );というのがある。これは拡張子なんかがわかっている場合にそれに対応するUTIを得る、というもので、inTagClass引数に
const CFStringRef kUTTagClassFilenameExtension; const CFStringRef kUTTagClassMIMEType; const CFStringRef kUTTagClassNSPboardType; const CFStringRef kUTTagClassOSType;のどれかを指定する。これらも読んで字のごとし。
他には
CFStringRef UTTypeCopyDescription( CFStringRef inUTI );というのがある。これはローカライズされたUTIのdescription文字列が返される。UTIをユーザに表示する必要があるなら、これを使えばいい。
3.2.3 NSWorkspaceのUTI関連メソッド
CocoaからもUTIは扱える。NSWorkspaceが一手に引き受けている。- (NSString *)typeOfFile:(NSString *)absoluteFilePath error:(NSError **)outError;これはファイルを指定してUTI文字列を得るというもの。
- (BOOL)type:(NSString *)firstTypeName conformsToType:(NSString *)secondTypeName; - (NSString *)localizedDescriptionForType:(NSString *)typeName;これはCore FoundationのUTTypeConformsTo()とUTTypeCopyDescription()のラッパ。
拡張子との変換のために
- (BOOL)filenameExtension:(NSString *)filenameExtension isValidForType:(NSString *)typeName; - (NSString *)preferredFilenameExtensionForType:(NSString *)typeName;がある。ひとつめは拡張子が指定したUTIとして正しいかどうか、ふたつめは指定したUTIに望ましい拡張子を返す。
なぜかUTTypeEqual()に対応するメソッドがない。Cocoaでは文字列として比較しろ、ということか?でもそれならCore FoundationでもCFStringCompare()でいいはずだよな。よくわからない。
ここまでは10.5で使える。これらの他に10.6で新しく追加されたメソッドもあるけど、ここでは省略する。
3.2.4 UTIによるアプリケーションの分類
Mac OS Xではいくつかの形式のアプリが実行できる。それぞれにUTIが定義されている。public.executable | 実行可能形式 |
com.apple.application | アプリとして実行可能な形式でpublic.executableをconform |
com.apple.application-bundle | 普通のMac OS Xアプリ |
com.apple.application-file | 単一ファイルの(バンドルではない)Classic環境用アプリなど |
com.apple.deprecated-application-file | コードリソースなど(これはMac OS Xでは実行不能) |
public.unix-executable | unixのa.out(Macでは実はMach-O)形式 |
public.unix-executableの場合はCocoaでは簡単に起動するということができない。この場合はunixのfork()とexec()システムコールを使うことになるけど、もうすこし簡単な
#include <stdlib.h> int system(const char *command);というunixのライブラリ関数がある。これはsh(シェル)を起動して引数の文字列をシェルのコマンドとして渡して実行させると言うもの。この関数自身はシェルが終わるまでブロックしてシェルのexit statusを返す。このコマンド文字列はシェルが解釈するので、パイプやリダイレクションも使えるし、&で文字列を終わらせればデタッチできる。
コマンドが実行できたかどうかを正確に判断するのは難しいけど、シェルのプロセスが起動できなかったら-1、プロセスは起動できてシェルの起動に失敗したら127が返るのでこれはエラーと判断できる。あとはシェルに任せるということになる。
ところで、このやりかたはさっきのNSLog()のリダイレクトに問題を起こす。unixでは子プロセスはstdin、stdout、stderrを親から受け継ぐので、もしこのsystem()で起動したアプリがstderrに出力すると、NSLog()のリダイレクト先に送られてしまう。それを回避するためにはsystem()を呼ぶ前にstderrを元に戻す、という作業をしなければいけない。だが、平均的なMac OS Xのユーザはstderrの出力をほとんど読まないので、実際にはそれほど実害はない。
2011-03-24 22:20
nice!(0)
コメント(0)
トラックバック(0)
コメント 0