X作成講座 on TC(2) HyperTalk との値のやりとり
Xと HyperTalk との値のやりとりは「C文字列ハンドル」で行います。
HyperTalk から tasu( 123,456 ) を実行すると、引数はHC特有の「パラメータブロック」という構造体に収められて、最終的に "123" と "456" という2つの文字列としてXに渡されます。
このパラメータブロックの構造は製品版の HyperCard スクリプトランゲージガイド(以下略して「マニュアル」)の 512 ページにあります。
paramCount: INTEGER;
params: ARRAY[1..16] OF Handle;
returnValue: Handle;
passFlag: BOOLEAN;
entryPoint: ProcPtr;
request: INTEGER;
inArgs: ARRAY[1..8] OF LongInt;
outArgs: ARRAY[1..4] OF LongInt;
マニュアルは Pascalの表記で書かれているのでC言語に読み換える必要がありますが、まぁなんとなく意味は分かると思います。試しにC風に書き直してみましょう。
typedef struct XCmdBlock *XCmdPtr;
struct XCmdBlock {
short paramCount; // HyperTalk から受け取った引数の数
Handle params[16]; // 引数の値の入っているC文字列ハンドルの配列
Handle returnValue; // HyperTalk に返すC文字列ハンドルを入れる場所
Boolean passFlag; // ここを true にするとメッセージを pass 出来る
// ここから先はコールバックルーチンが内部的に使う
Ptr entryPoint; // コールバックエントリーポイント
short request; // コールバックリクエスト番号(関数番号)
short result; // コールバックの返値
long inArgs[8]; // コールバックへの引数配列
long outArgs[4]; // コールバックへからの返値配列
};
Xが呼び出されると、このパラメータブロックへのポインタ( XCmdPtr )が渡されるので、引数を読む時はこのポインタからたどって値を読み出し、値を返す時はこのパラメータブロックへ値を書き込みます。
さて tasu.c のリストを追ってみましょう。
>> pascal void main( XCmdPtr paramPtr ) {
メイン関数で「パラメータブロックへのポインタ」を paramPtr という変数に受け取っています。このポインタが指す先に、上記の「パラメータブロック」があります。Xの中では何をするにも(ちょっとオーバー)このポインタが必要になるので、グローバル変数に入れなおしているプログラムも多いようです。
>> ZeroToPas( paramPtr, *paramPtr->params[0] , theStr );
ZeroToPas はハイパカ特有のルーチン(グルールーチン)です。ハイパカに問い合わせることから「コールバックルーチン」とも呼ばれます。(「コールバック」はその関数が呼び出された親プログラムに対して、関数側から逆に問い合わせをする関数の総称です)
コールバック関数はマニュアル p515 から並べて書かれていますが、すごく見づらい上に Pascal 表記になっているのでちょっと難儀します。アタマに FUNCTION とか PROCEDURE とか付いているのが関数名ですね。FUNCTION は文字通りの関数、PROCEDURE はC言語では「返値に意味のない関数」として扱います。
ZeroToPas は p520 の一番下にあります。
PROCEDURE ZeroToPas( paramPtr: XCmdPtr; zeroStr: Ptr; VAR pasStr: Str255 )
このルーチンは「C文字列をP文字列にする」ものです。
第1引数にパラメータブロックのポインタを渡します。第2引数にC文字列のポインタを渡します。すると、第3引数に渡したP文字列変数に、変換済みの文字列が入ります。第1引数に paramPtr を渡すのはコールバックではオヤクソク。
私のソースでは第2引数に *paramPtr->params[0] を渡していますが、これは paramPtr(パラメータブロックのポインタ)から取り出した params[0]( params 配列の1番目/Pascalでは配列は 1 からだがCでは 0 から)を逆参照( * )したもの、という意味です。
HyperTalk から渡されたC文字列ハンドルが欲しい場合は paramPtr->params[n]、C文字列ハンドルの中のC文字列ポインタを欲しい場合は *paramPtr->params[n]、1つ目の引数を取り出すなら n は 0、2つ目は 1、以後 15 まで。これは丸暗記してしまって下さい。
結局 ZeroToPas( paramPtr, *paramPtr->params[0] , theStr ); という行は
HyperTalk から渡された第1引数を theStr というP文字列に格納
していることになります。なぜC文字列をわざわざP文字列に変換したかというと、これは次の行で使いたいからです。
>> n1 = StrToNum( paramPtr, theStr );
第1引数に paramPtr が来るのはコールバックですね。 StrToNum はマニュアル p520 の中程にあります。このルーチンは、第2引数に渡されたP文字列を数値に変換して返します。例えば "123" という文字列は 123 という数値として n1 に入ります。
>> ZeroToPas( paramPtr, *paramPtr->params[1] , theStr );
>> n2 = StrToNum( paramPtr, theStr );
同様にして HyperTalk から渡された第2引数を数値として n2 に入れています。ZeroToPas の第2引数が params[1] となっていることに注意。theStr は使い回ししています。
ここまでで、HyperTalk から渡された2つの引数を数値に変換して、n1、n2 という2つの数値変数に入れました。
>> NumToStr( paramPtr, n1 + n2, theStr );
NumToStr は数値をP文字列に変換するルーチンです。マニュアル p519 の中程にあります。このルーチンは、第2引数に渡した値( n1 + n2 )を文字列に変換して、第3引数のP文字列に格納します。 n1 + n2 が 579 だった場合は文字列 "579" が theStr に入ります。
最後に処理の結果を HyperTalk に返します。
>> paramPtr->returnValue = PasToZero( paramPtr, theStr );
PasToZero はP文字列をC文字列ハンドルに変換して返す関数です。マニュアルでは NumToStr の下にありますね。 theStr が "579" だった場合の返値は、C文字列としての "579" を持ったハンドルです。
なぜわざわざC文字列ハンドルにするかと言うと、HyperTalk から引数を受け取るときと同じく、Xから HyperTalk に返す値も必ずC文字列ハンドルである必要があるからです。C文字列ハンドルを作っておいてそれを paramPtr->returnValue に書き込むと、HyperTalk はその値を読みとってXの返値と解釈します。
これがXのおおまかな流れです。
引数を受け取る必要がない場合もあるし、返値を返す必要のないものもありますが、その場合はそれらの処理を省略すればいいだけです。
返値は XFCN の場合はそのままXの返値として HyperTalk で受け取れます。 XCMD の場合は返値は返す必要はありませんが、もし返した場合は、HyperTalk からは the result で取り出すことが出来ます。
結局Xに特有なのはパラメータブロックの構造とコールバックルーチンだけで、この2つを覚えてしまえばあとはTBと格闘するだけ(笑)の普通のCプログラムとなんら変わりません。
Next
THINK C Lab.
UDI's HomePage