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
inserted by FC2 system