X作成講座 on TC(10) リソースファイルを扱う


 「ファイルを開いてリソースを扱うのは大変だよ」と言っておいて、具体的な方法を解説していませんでした。苦情が来ました(笑)

 そこで、カレントファイル以外のファイルを開いて、そのリソースを読むXを作ってみます。例によってサウンドリソースのリストを得る XFCN です。Xへの引数としてファイルのフルパスを渡すと、そのファイルのリソースフォークを読んで、サウンドリソースのリストを作ります。

 リソースのリストを得る基本的な流れは XonTC[05] リソースを扱う で行ったのと同じです。ここではリソースファイルを開く処理と、それを閉じる処理が加わります。


        −・−・−・−・−・−・−・−・−・−


 さて、最初にリソースファイルの検索パスについて、基本的な考え方を説明しておきましょう。リソース構造はマックに特有なもので実に便利で面白いのですが、最近は Wind○ws なんかとアプリやファイルの共有を考えなければならないために活用されない方向に進んでいるような気がします。実に残念です。


 まずシステムが起動し、アプリをひとつ立ち上げた状態を考えてみます。この時のリソース検索パスは以下のようになっています。

 [ System ]-[ Application ]*カレント

 アプリケーションが「カレントリソースファイル」ですが、それに繋がったシステムのリソースまでが全て検索パスに含まれます。
 以前説明した GetResource ではこの検索パスにある全てのファイルが読み込み対象になります。例えば「 ID 128 の ICON 」を読もうとした時、もしも Application がそれを持っていなければ、リソースマネージャは System ファイルまでを検索し、自動的に読み出します。 System ファイルがディスク上のどこにあるのかはプログラマは考える必要はありません。リソースマネージャは偉大です。
 ちなみに Get1Resource ではカレントである Application のリソースだけを対象とします。 System のリソースは読みません。

 ここでひとつ書類を開いた状態を考えてみます。

 [ System ]-[ Application ]-[ Document ]*カレント

 開いた書類がカレントリソースファイルです。 GetResource なら Document、Application、System にある全てのリソースを扱えることになります。例えば Document はテキストデータだけを持っていれば良く、フォントは System から読み出せばいいわけです。

 さて HyperCard ではどうでしょう。 HyperCard でスタックをひとつ開いた状態は以下のようになります。

 [ System ]-[ HyperCard ]-[ Home ]-[ stack ]*カレント

 スタックとハイパカの間に Home が挟まっています。さらに start using を使ってスタックをひとつ開くと、

 [ System ]-[ HyperCard ]-[ Home ]-[ usingStack ]-[ stack ]*カレント

 と、なります。カレントリソースは常にカレントスタックですが、Home との間に using されたスタックが挟まる感じです。これによって、using されたスタックのリソースが利用できるようになります。


 このようにカレントから繋がる検索パスにあるファイルのリソースは、全て GetResource や GetNamedResource などで読むことが出来ます。またカレントファイルだけを対象にしたければ Get1Resource や Get1NamedResource を使うことになります。
 では検索パスに無いファイルのリソースはどうやって読むのでしょうか?

 答えは割と簡単で、実は OpenResFile というルーチンを呼び出すだけです。このルーチンは引数としてファイルのフルパスを要求しますので、HyperTalk とは割と相性が良いです。 OpenResFile で Doc ファイルを開いた状態は以下のようになります。

 [ System ]-[ HyperCard ]-[ Home ]-[ usingStack ]-[ stack ]-[ Doc ]*カレント

 そして CloseResFile でこれを閉じると元の検索パスに戻ります。

 [ System ]-[ HyperCard ]-[ Home ]-[ usingStack ]-[ stack ]*カレント

 このように、OpenResFile はファイルのリソースフォークを開いて検索パスに追加し、それをカレントリソースファイルとします。 CloseResFile は指定されたファイルのリソースフォークを閉じ、検索パスから削除します。
 データフォークとリソースフォークは全く別個にオープン/クローズ出来ますから、これらのルーチンによって影響を受けるのはリソース検索パスだけです。

 では "usingStack" を OpenResFile するとどうなるでしょうか? これが面白いことに、検索パスの順番が変わるだけなんですね。

 [ System ]-[ HyperCard ]-[ Home ]-[ stack ]-[ usingStack ]*カレント

 で、CloseResFile すると・・・ あれれ? 元に戻りません。

 [ System ]-[ HyperCard ]-[ Home ]-[ stack ]*カレント

 元々開いていたリソースファイルを勝手に CloseResFile してしまうと、後で困ったことになってしまうのですね。 ではどうするかと言うと、UseResFile と言うルーチンを使います。このルーチンは「開いているリソースファイルをカレントにする」ものです。 UseResFile で "stack" をカレントにしてやれば、ハイ、元に戻ります。

 [ System ]-[ HyperCard ]-[ Home ]-[ usingStack ]-[ stack ]*カレント

 開くのは OpenResFile で良いのですが、閉じる(戻す)時は CloseResFile する場合と UseResFile する場合の2通りがあるわけですね。


        −・−・−・−・−・−・−・−・−・−


 このように、XCMD/XFCN でリソースファイルを開く場合、それが検索パスにあるものか、或いは新たに開くリソースファイルなのかを考えないとなりません。アプリケーションからリソースファイルを利用するときは勝手に開いて勝手に閉じれば良いのですが、XCMD/XFCN では親アプリである HyperCard がそのリソースファイルを使っている場合があるからです。

 実際にリソースファイルを開いて閉じる手順は以下のようになります。


 PBGetFInfo() でファイルの情報を得る。
 この時点でエラーが返ってきた場合は指定されたファイルが無い。

 PBGetFInfo() で得られた ioFlAttrib の bit2 を調べる。
 これが 1 だった場合はリソースフォークがオープンしている。
 これを変数 resOpened に保存する。

 現在のカレントリソースファイルを CurResFile() で得る。
 これを変数 oldRefNum に保存する。

 OpenResFile でリソースフォークを開く。
 オープンしてなかった場合はリソースフォークがオープンされ、
 オープンしていた場合は検索パスが変わる。
 共に指定ファイルがカレントリソースファイルとなる。
 ファイルのリファレンスナンバーが返ってくるので変数 tgRefNum に保存。
 この時点でエラーが返ってきた場合、resOpened が true なら
  他のアプリケーションがそのリソースファイルを使用中。
 resOpened が false なら、
  リソースフォークそのものが無い。

 ここでリソース操作。読み書き自由です。

 リソースフォークを閉じます。
 resOpened が true だった場合は、
  UseResFile() に oldRefNum を渡して検索パスを戻す。
 resOpened が false だったなら、
  CloseResFile() で開いたリソースファイル tgRefNum を閉じる。

 以上。


        −・−・−・−・−・−・−・−・−・−


 では実際にプログラムを組んでみましょう。

#include "HyperXCmd.h"
#include "SetUpA4.h"

/* Prototypes */
void myRoutin( XCmdPtr paramPtr );
void getAllSoundList( XCmdPtr paramPtr );
OSErr putStrAfterC( Handle resultHand, long *resultOfs, Str255 theStr );
OSErr checkOpen( Str255 filePath, Boolean* resOpened );
void setErrMsg( XCmdPtr paramPtr, Str255 errMsg );
void restResPath( Boolean resOpened, long oldRefNum, long tgRefNum );

/* Main */
pascal void  main( XCmdPtr paramPtr )
{
    RememberA0( );
    SetUpA4( );
    getAllSoundList( paramPtr );
    RestoreA4( );
}

/* my routin */
void getAllSoundList( XCmdPtr paramPtr ){	
	Str255		filePath, theStr, resName, resultStr;
	Boolean		resOpened;
	Handle		resultHand, resHand;
	long			resultOfs, oldRefNum, tgRefNum;
	short		theID, itemNum, i;
	ResType		theType;
	OSErr		err;

	ZeroToPas( paramPtr, *paramPtr->params[0] , filePath );
	
	// check res opened ?
	err = checkOpen( filePath, &resOpened );
	if ( err ){
		setErrMsg( paramPtr, "\pError : Not found file" );
		return;
	}

	// open res file
	oldRefNum = CurResFile();
	tgRefNum = OpenResFile( filePath );
	if ( tgRefNum == -1 ){
		if ( resOpened ){
			setErrMsg( paramPtr, "\pError : File is Busy" );
		} else {
			setErrMsg( paramPtr, "\p" );	// no resource fork
		}
		return;
	}
	
	// read resName
	resultOfs = 0;
	resultHand = NewHandle( 0 );
	itemNum = Count1Resources('snd ');
	SetResLoad( false );
	for ( i = 1; i <= itemNum; i++ ){
		resHand = Get1IndResource( 'snd ', i );
		GetResInfo( resHand, &theID, &theType, resName );
		if ( resName[0] != 0 ){
			err = putStrAfterC( resultHand, &resultOfs, resName );
			if ( err == noErr ){
				err = putStrAfterC( resultHand, &resultOfs, "\p\r" );
			}
			if ( err ){
				SetResLoad( true );
				setErrMsg( paramPtr, "\pError : Mem Error" );
				restResPath( resOpened, oldRefNum, tgRefNum );
				DisposHandle( resultHand );
				return;
			}
		}
	}
	SetResLoad( true );

	// close res file
	restResPath( resOpened, oldRefNum, tgRefNum );
	
	// return to HyperTalk
	ZeroTermHandle( paramPtr, resultHand );
	paramPtr->returnValue = resultHand;

}

/* -- restore resorce path -- */
void restResPath( Boolean resOpened, long oldRefNum, long tgRefNum ){
	if ( resOpened ){
		UseResFile( oldRefNum );
	} else {
		CloseResFile( tgRefNum );
	}
}

/* -- put P-string after C string Handle -- */
OSErr putStrAfterC( Handle resultHand, long *resultOfs, Str255 theStr ){
    SetHandleSize( resultHand, *resultOfs + theStr[0] );
    if ( MemError() )    return MemError();
    BlockMove( theStr +1, *resultHand + *resultOfs, theStr[0] );
    *resultOfs += theStr[0];
    return noErr;
}


/* ----- set Err message to HyperTalk ----- */
void setErrMsg( XCmdPtr paramPtr, Str255 errMsg ){
	paramPtr->returnValue = PasToZero( paramPtr, errMsg );
}


/* ----- check resource fork open ----- */
OSErr checkOpen( Str255 filePath, Boolean* resOpened ){
	ParamBlockRec		pb;
	OSErr			err;
	
	pb.ioParam.ioNamePtr = filePath;
	pb.ioParam.ioCompletion = 0;
	pb.ioParam.ioVRefNum = 0;
	pb.ioParam.ioVersNum = 0;
	pb.ioParam.ioMisc = 0;	//ioFDirIndex
	err = PBGetFInfo( &pb, 0 );
	*resOpened = pb.fileParam.ioFlAttrib & 0x0004;  // if res opened then return true
	return err;
}

 基本的な流れは説明した通りです。

 XonTC[05] とは putStrAfterC ルーチンの仕様が少し変わっていることに注意して下さい。返値を Boolean から OSErr に変えました。また HyperTalk へ値を返すルーチン setHCMsg を追加しています。

 PBGetFInfo や ParamBlockRec構造体についてはここでは解説しきれません(^^;) 使いこなすと便利なんですが、なにしろパラメータが膨大で... ToolBox 解説書にも説明が無い場合が多いと思います。 THINK Reference や InsideMac を参照して下さい。
 これを使っている checkOpen ルーチンは単にファイルパスを渡すとそのリソースフォークが使用中かどうかを返すだけのルーチンです。必要ならどうぞコピーしてお使い下さい。リソースフォークがオープンされていれば第2引数に true が返ります。ルーチンの返値自体は PBGetFInfo のエラーです。

 エラー処理などでXを抜ける時に、リソース検索パスを戻すのを忘れやすいので気を付けましょう。


Next



THINK C Lab.

UDI's HomePage
inserted by FC2 system