X作成講座 on TC(6) 構造体を扱う


 もう少し「 ToolBox らしいもの」を扱ってみます。
 どのへんがTBらしいかと言うと、TBのルーチンを使うのはもちろんですが、TB独自の構造体を扱います。

 テキストファイルのパスを指定すると、それを読み込んで内容を返すXFCNです。

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

/* Prototypes */
void myReadFile( XCmdPtr paramPtr );

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

/* my routin */
void myReadFile( XCmdPtr paramPtr ){
	OSErr	err;
	Str255	myFilePath;
	Handle	myDocHand;
	long	myFileSize;
	short	myVRefNum, myDirID, myRefNum;
	FSSpec	mySpec;
	FInfo	myFInfo;

	ZeroToPas( paramPtr, *paramPtr->params[0], myFilePath );
	err = FSMakeFSSpec( myVRefNum, myDirID, myFilePath, &mySpec );
	if ( err != noErr ){
		paramPtr->returnValue = PasToZero( paramPtr, "\pNot found file" );
		return;
	}
	FSpGetFInfo( &mySpec, &myFInfo );
	if ( myFInfo.fdType != 'TEXT' ){
		paramPtr->returnValue = PasToZero( paramPtr, "\pFile is not TEXT" );
		return;
	}
	err = FSpOpenDF( &mySpec,1, &myRefNum );
	if ( err != noErr ){
		paramPtr->returnValue = PasToZero( paramPtr, "\pCan't open file" );
		return;
	}
	GetEOF( myRefNum, &myFileSize );
	myDocHand = NewHandle( myFileSize );
	if ( myDocHand == 0 ){
		FSClose( myRefNum );
		paramPtr->returnValue = PasToZero( paramPtr, "\pNot enogh memory" );
		return;
	}
	err = FSRead( myRefNum, &myFileSize, *myDocHand );
	if ( err != noErr ){
		FSClose( myRefNum );
		paramPtr->returnValue = PasToZero( paramPtr, "\pCan't read file" );
		DisposHandle( myDocHand );
	}
	FSClose( myRefNum );
	ZeroTermHandle( paramPtr, myDocHand );
	paramPtr->returnValue = myDocHand;
}

 ファイルパスを使ってテキストファイルを開くときの基本的な流れは以下のようになります。

  FSMakeFSSpec を使ってファイルパスから FSSpec を作る
  FSpGetFInfo を使って FSSpec の指しているファイルの FileType 等をチェック
  FSpOpenDF でファイルのデータフォークを開く(この時 refNum が得られる)
  GetEOF で refNum の指しているファイルの大きさを得る
  その大きさのメモリブロックを用意する
  FSRead でファイルの内容をそのメモリに読み込む
  FSClose で refNum の指しているファイルを閉じる

 大きな流れとしては「開いて」「読んで」「閉じる」訳ですから HyperTalk とそれほど違いませんが、ファイルの指定の仕方がいくつかあるのと、実際に読み込むメモリをプログラマが用意しないとならない点が大きく異なります。
 エラー処理が頻繁に出てきますが、エラーで return する時は作ったハンドルは破棄し、開いたファイルは必ず閉じることを忘れないで下さい。


 FSSpec と FInfo という2つの構造体が出てきました。

 FSSpec はディスク上でのファイルの「位置」情報を持つ構造体です。通常は FSMakeFSSpec にボリューム参照番号、ディレクトリID、ファイルパス、の3つの情報を渡して
  FSMakeFSSpec( myVRefNum, myDirID, myFilePath, &mySpec );
で mySpec に FSSpec 構造体が出来上がるのですが、このルーチンの便利なのはファイルパスだけでも FSSpec を作ってくれることです。実際にソースを追ってみれば分かりますが、このルーチンを呼んでいる時、myVRefNum も myDirIDもゼロのままです。

 一方 FInfo はファイルタイプやクリエータなどの情報を持つ構造体で、FSpGetFInfo ルーチンに FSSpec を渡してやれば作ってくれます。このソースでは実際に開こうとしているファイルのファイルタイプが "TEXT" であるかどうかをチェックするために使っています。

 これらの構造体の情報や作り方などは InsideMacintosh を参照しましょう、というのが正しい方法ですが、取りあえず初心者としては
  Macintosh アプリケーションプログラミング 上下巻 各3800円
   (株式会社ディーアート 新居雅行著 ISBN4-88648-443-3 ISBN4-88648-444-1)
に解説のあるものから順に試し、これに慣れて物足りなくなってからIMをダウンロードすればいいと思います。また、"Mac #includes" フォルダの "Apple #includes" の中の "xxxx.h" ファイルをエディタで開いて眺めてみるのも良い勉強になります。もし THINK Reference を持っているなら Managers で関連ルーチンや構造体をぐるぐると見て回るとどんなルーチンや構造体があるのかがなんとなく分かってきます。ただし英語なので結構辛いですけど・・・。そして、他人様のソースは何にも増して宝物です。サンプルソース等を見つけたら片っ端からコレクションして、いつか役立つ日のためにHDのコヤシとしておきましょう。


 構造体を扱うときは & を付けてルーチンに渡すのが普通です。これは2つの意味があります。
 ひとつはCPUの構造とも絡むのですが、構造体のような大きなデータ構造は基本的にサブルーチンなどに直接渡すことが出来ません。そのため構造体のアドレスを渡して、サブルーチン(TBルーチンを含む)側ではそのアドレスから構造体の中身にアクセスします。どんなに大きな構造体であっても、そのアドレスは必ず4バイトのポインタですからね。
 もうひとつは構造体の中に返値が欲しい場合があることです。FSMakeFSSpec は FSSpec 構造体に、FSpGetFInfo は FInfo 構造体に値を返してくれるルーチンですが、これらのルーチンに構造体のアドレスを渡すことにより、呼び出し側が扱っている構造体そのものに値を書き込むことが出来る訳です。
 Point(座標:4バイト)のような直接サブルーチンに渡せる小さな構造体もありますが、「構造体はアドレス渡し」というのは頭に入れて置いて下さい。


 さて & を付けてサブルーチンに渡した構造体は、サブルーチン側ではどう扱ったらいいでしょうか。
 サンプルとしてファイルタイプをチェックするサブルーチンを作ってみます。FInfo で渡したファイルがテキストなら true を返します。

Boolean isTextFile( FInfo *myFInfoPtr ){
    if ( ( *myFInfoPtr).fdType == 'TEXT' ) return true;
    else return false;
}

呼び出し側 if( isTextFile( &myFInfo ) == false ) ・・・

 うーん。悩ましきかな参照と逆参照。
 isTextFile ルーチンで受け取るのは「 FInfo 構造体のポインタ」です。そこで変数名を myFInfoPtr としました。こうしておくと扱っているのが構造体なのか構造体のポインタなのかをハッキリさせることが出来ます。で、FInfo 構造体そのものはそのポインタの指す先(逆参照したもの)にありますから、*myFInfoPtr が FInfo ということになります。
 ( *myFInfoPtr).fdType という形は頻繁に出てくるので、便宜を計って myFInfoPtr->fdType という書き方が用意されています。

 THINK C のヘッダファイル( xxx.h )を眺めてみると、構造体の定義のところに構造体のポインタまで定義しているものがあります。例えば Files.h の FSSpec の定義の部分を見ると、構造体の定義の最後に
  typedef FSSpec *FSSpecPtr, **FSSpecHandle;
という行があります。これは
  *FSSpecPtr と **FSSpecHandle は FSSpec と一緒だよ
という意味です。
 こういう定義のあるものはこれを利用するともう少しソースが見やすくなります。例えば FSSpec で示すファイルが起動ボリュームにあるかどうかを返す関数です。

Boolean onStartupVol( FSSpecPtr myFSSpecPtr ){
    if ( myFSSpecPtr->vRefNum == 0 ) return true;
    else return false;
}

呼び出し側 if( onStartupVol( &myFSSpec ) == true ) ・・・

 関数の引数部分の定義のしかたが少し変わりましたね。ここは
  Boolean onStartupVol( FSSpec *myFSSpecPtr ){
と書いても全く同じ意味になります。C言語(に限らずコンパイラ全般)の「再定義」は初心者は面食らいますが、慣れると結構便利な概念であることが分かります。


 構造体を扱う上でもうひとつ大事なのは、その構造体の実体はどこにあるのかということです。
 上記の「ファイルを読む」サンプルソースでは、myFSSpec と myFInfo はローカル変数として定義しました。( FSSpec mySpec; FInfo myFInfo; ) こうすると構造体の実体が「そこ」に出来ます。もしもこの変数の定義を FSSpecPtr mySpecPtr; としたらどうなるでしょう。
  err = FSMakeFSSpec( myVRefNum, myDirID, myFilePath, mySpecPtr );
とすればコンパイラは通ってしまいますがこれは重大な間違いであることを理解しなければなりません。何故なら mySpecPtr そのものは「 FSSpec 構造体を指すポインタ」、つまりただの4バイトのアドレスが入っているだけの変数なのです。構造体そのものの実体がどこにもありません。この変数がちゃんと機能するのはあくまで実際にそのアドレスに FSSpec 構造体が(或いはそれ用に確保されたメモリブロックが)ある場合のみです。上記のような使い方をすると、mySpecPtr という変数領域に入っていたゴミの4バイトをアドレスと解釈し、その位置のメモリに FSSpec 構造体を書き込んでしまいます。(当然爆弾が出ます)

 この問題はコンパイラのチェックを通ってしまうだけにやっかいで、構造体やポインタ変数の理解を妨げる原因になっていると思います。逆に言いうと、ここで説明していることがすんなりと理解できるようであれば、ポインタや構造体がほぼ理解できていることになります。

inserted by FC2 system
Next



THINK C Lab.

UDI's HomePage