構造体と共有変数



ここですること:
 ・簡単な構造体の例としての Rect を触ってみる
 ・構造体の入れ物、共有変数を使う
 ・QuickDraw で遊んでみる




(1)構造体 "Rect"

 HyperTalk では文字列も数値も Rect でさえも文字列で構成されていますが、ToolBox の世界では全てのデータに「型」があり、全ては厳密に区別されます。
ここでは「 Rect型」の変数を扱ってみましょう。

 構造体というのはいくつものデータをまとめて扱うのに便利な概念です。例えば、氏名、年齢、住所、電話番号、というデータをまとめてひとつの構造体として定義すると、ひとりのデータをまとめて扱えて便利です。
     aPerson ---> name
                 age
                 address
                 phone

 構造体はポインタ(やハンドル)を介して扱います。何故なら、CPUはこんなに大きなデータをいっぺんに扱うことが出来ないからです。プログラム上では構造体の先頭を指すポインタ( aPerson )だけを変数に持ち、ここから構造体の中身( name, age, address, phone )にアクセスします。

 ここでひとつ用語についての注意。古来マックは Pascal を使うマシンだったので CompileIt! のマニュアルは Pascal 風の表記になっていますが、最近はC言語が主流になり、InsideMac もC言語で書かれていることがあります。「構造体」はCの用語で、Pascalでは「レコード」です。構造体とレコードは全く同義です。また「メンバ」はCの用語で、Pascalでは「フィールド」です。参考書の多くがCで書かれていると思われるので、ここではCの用語を使いますが、マニュアルを読む時は適宜読み変えて下さい。

 では一番簡単な構造体、Rect を使ってみましょう。 Rect の定義は以下のようになっています。

typedef struct Rect{
    short top;    //左上の垂直座標
    short left;   //左上の水平座標
    short bottom; //右下の垂直座標
    short right;  //右下の水平座標
    } Rect, *RectPtr;

 なんか馴染み深い言葉が並んでいますね。Rect は top、left、bottom、right と名付けられた4つのメンバからなり、それぞれのメンバは short(2バイト整数)です。メモリ上では単に以下のように各メンバが並んでいるだけです。
   [top][left][bottom][right]
 このように Rect は short (2バイト整数)が4つ合わさったものですから、合計で8バイトの構造体です。これを CompileIt! で扱うには8バイトのメモリエリアを確保する必要があります。

-- CompileIt! Script
on drawRect
  put NewPtr( 8 ) into myRect
  put 10 into myRect@.top
  put 10 into myRect@.left
  put 100 into myRect@.bottom
  put 100 into myRect@.right
  FrameRect myRect@
  DisposPtr myRect
end drawRect

 put NewPtr( 8 ) into myRect で8バイトのメモリエリアを確保しました。この時点ではメモリの中には何が入っているか分かりません。HyperTalk のように0や empty を入れてはくれませんので注意して下さい。この8バイトのメモリエリアの先頭を指しているのがポインタ変数 myRect です。
 put 10 into myRect@.top は「 myRect の指しているメモリの中の top という構造体メンバに 10 を入れる」という作業をしています。この top というのはアップルによってちゃんと定義されていて、「オフセット0の2バイト整数」と決まっています。つまり myRect@.top と書くだけで「 myRect というポインタが指しているメモリから0バイト先の2バイト」にアクセス出来るわけです。同様に、
 put 10 into myRect@.left で使っている left は「オフセット2、2バイト整数」と定義されています。

 FrameRect myRect@ の FrameRect は ToolBox ルーチンです。このルーチンに Rect 構造体を渡してやると、その Rect の四角を描きます。マックの描画を一手に引き受ける、いわゆる QuickDraw ルーチンのひとつですね。CompileIt! では @ を付けて Rect を指すポインタを渡します。
 DisposPtr myRect は確保した8バイトを破棄する ToolBox ルーチンです。これを忘れると、このXを実行するたびにフリーメモリが8バイトづつ減っていくことになります。

 このスクリプトをコンパイルするには ToolBox シンボルの登録が必要です。top や left は HyperTalk としても使われる単語なので少々困るのですが、普通は ToolBox のシンボルとして登録しておいて良いでしょう。HyperTalk としての top や left を使いたい場合は、これらのシンボルにマークを付けて、Remove ボタンを押します。すると、以後そのシンボルは HyperTalk の用語として機能します。(もちろん、このスクリプトをコンパイルするには ToolBox ルーチンとして登録することが必要です)

 top や left などのシンボルが CompileIt! のどこで定義されているかを調べるには、スクリプトフィールドで top を選択した状態で Command-? キーを押します。するとそのシンボルを定義しているシンボルテーブルに飛び、シンボルをハイライトしてくれます。 top や left などは ToolBox Variables & Fields というシンボルテーブルにありますね。もちろん同様にして ToolBox ルーチンの定義を検索することも出来ます。

 top や left などを変えながらコンパイルして、いろんな四角を描いてみて下さい。この四角はカードよりも上の層に描かれるので、カードピクチャーなどとは無関係です。カード移動やアプリの切替などで画面が更新されると消えてしまいます。



(2)構造体の入れ物、「共有変数」

 CompileIt! には「共有変数」と呼ばれる面白い変数があります。共有変数はプログラムの先頭で global を使って宣言されますが、いわゆるグローバル変数とはちょっと動作が異なっています。まずは下のスクリプトを見て下さい。
-- CompileIt! Script
global myRect: R[8]
on drawRect
  put 10 into myRect.top
  put 10 into myRect.left
  put 100 into myRect.bottom
  put 100 into myRect.right
  FrameRect myRect
end drawRect
 スクリプトの先頭(そしてハンドラの外)に global 宣言が付きました。それから NewPtr と DisposPtr が無くなり、メンバの指定や FrameRect の引数 myRect から @ が無くなりました。
 global で宣言する共有変数は明示的に変数の「型」を指示します。ここでは R、つまり「レコード」(=構造体)として宣言し、そのサイズを8バイトと指定しています。これだけで8バイトのメモリブロックを持つ myRect という変数が使えるようになります。
 この構造体のメンバにアクセスする時は @ が不要です。 NewPtr でメモリを確保していた時は myRect@.top と書いていたものが、myRect.top だけで済むようになります。また共有変数として確保したメモリは明示的に破棄する必要が無く、放っておいてもちゃんとメモリを解放してくれます。いや実に便利便利。

 共有変数はスクリプト中のどのルーチンからもアクセス出来るのでグローバル変数に近いものがありますが、その使用目的は明らかに異なります。
 面白いことに、CompileIt! では HyperTalk のグローバル変数も利用できます。共有変数は「ハンドラの外で」宣言しますが、「ハンドラの中で」 global 宣言した変数は、HyperTalk と全く同じ文字列型のグローバル変数となります。このグローバル変数は HyperTalk と共有することが出来ます。つまり HyperTalk とXとで同じ変数が使えると言うことです。プログラミングスタイルとしてはあまりスマートではありませんが、場合によっては便利に使えるでしょう。
 共有変数には型がありますが、グローバル変数は HyperTalk と同じく「文字列」です。ループ中などで頻繁に呼ばれる場所にグローバル変数を置くと、文字列と数値などの変換のためにスピードに影響が出ることがあります。ループに入る前に一旦ローカル変数に保存し直した方が良いでしょう。



(3)Rect を引数から受け取る

 このままでは四角の大きさを変える度にコンパイルし直さないとなりません。どうせなら HyperTalk から引数としてレクトを渡し、四角の大きさを変えられるようにしましょう。
-- CompileIt! Script
global myRect: R[8]
on drawRect theRect
  StrToRect theRect, myRect
  FrameRect myRect
end drawRect
 これを使う HyperTalk 側はこんな感じです。
on mouseUp
  put "10,10,100,100" into theRect
  drawRect theRect
end mouseUp
 レクトを直接指定する場合は
  drawRect "10,10,100,100"
 のようにレクトをダブルクォートで囲って下さい。そうしないとXは4つの引数を受け取ったと判断して、おかしな動作をします。

 StrToRect theRect, myRect の StrToRect は、レクトを表す文字列から Rect 構造体を作る HyperCard のサービスルーチンです。第1引数にレクトを表す文字列、第2引数に Rect 構造体の(と言うか8バイトのメモリブロックの)アドレスを渡すと、第2引数の指しているメモリに Rect 構造体を作ってくれます。



(4)応用編

 例えばレクトをそのまま指定せずに、top、left、bottom、right を別個に指定するようにしてみましょう。
-- CompileIt! Script
global myRect: R[8]
on drawRect theTop, theLeft, theBottom, theRight
  SetRect myRect, theTop, theLeft, theBottom, theRight
  FrameRect myRect
end drawRect
 SetRect は Rect 構造体に値を収めるのに便利なルーチンです。これ一発で4つの値を Rect 構造体に収めてくれます。このXは4つの値を引数として受け取ります。

 或いは topLeft と bottomRight から Rect を作ることも出来ます。
-- CompileIt! Script
global myRect: R[8], myTopLeft: R[4], myBottomRight: R[4]
on drawRect theTopLeft, theBottomRight
  StrToPt theTopLeft, myTopLeft
  StrToPt theBottomRight, myBottomRight
  Pt2Rect myTopLeft, myBottomRight, myRect
  FrameRect myRect
end drawRect
 StrToPt は座標を表す文字列から Point型の構造体を作ってくれるルーチンです。こうして出来た2つの Point型から Rect型を作るのが Pt2Rect です。


 FrameRect と同様に Rect 構造体で範囲指定出来る他のルーチンも使ってみましょう。四角の中を黒く塗る PaintRect、白く塗る EraseRect、四角く反転させる InvertRect ルーチンが使えます。また楕円を描く FrameOval も引数として Rect を受け取るので、同じように使うことが出来ます。
 一例としてランダムな図形を描くスクリプトを挙げます。
-- -- CompileIt! script

global myRect: R[8], cdRect: R[8]
on drawRandom
  -- カードの大きさを得て、水平垂直の最大値を得る
  -- カードレクト自体は HyperTalk で得ています
  StrToRect the rect of this card, cdRect
  put cdRect.bottom into vMax -- 垂直方向の最大値
  put cdRect.right into hMax -- 水平方向の最大値
  --
  EraseRect cdRect -- 一旦カードを真っ白にする
  --
  repeat 1000
    put random( vMax ) - trunc( vMax/3 ) into myTop
    put random( hMax ) - trunc( hMax/3 ) into myLeft
    put myTop + random( vMax ) - trunc( vMax/2 ) into myBottom
    put myLeft + random( hMax ) - trunc( hMax/2 ) into myRight
    SetRect myRect, myLeft, myTop, myRight, myBottom
    put random( 8 ) into myJob
    if myJob = 1 then FrameRect myRect -- 四角の枠を描く
    if myJob = 2 then PaintRect myRect -- 四角く塗りつぶす
    if myJob = 3 then EraseRect myRect -- 四角くクリアする
    if myJob = 4 then InvertRect myRect -- 四角く反転する
    if myJob = 5 then FrameOval myRect -- 丸い枠を描く
    if myJob = 6 then PaintOval myRect -- 丸く塗りつぶす
    if myJob = 7 then EraseOval myRect -- 丸くクリアする
    if myJob = 8 then InvertOval myRect -- 丸く反転する
  end repeat
end drawRandom
 このスクリプトでは ramdom を HyperTalk として使っています。ToolBox にも ramdom というルーチンがありますが、全く違う動作をするのでシンボル ramdom を登録から外しています。(シンボルテーブルに移動して ramdom にマークを付けたあと、Remove ボタンを押します)

 さあ、コンパイルして実行しましょう。カードスクリプトに以下のスクリプトを書き込んで、カードをクリックしてみて下さい。Xを実行して、それにかかった時間( ticks )を表示します。我が家のマシンでは 37ticks 前後でした。 1000回ループの結果がこれですから、QuickDraw はやっぱり Quick なんですねぇ。
on mouseUp
  put the ticks into T
  drawRandom
  put the ticks - T
end mouseUp

inserted by FC2 system
Next



CompileIt! Lab.

UDI's HomePage