ここまで何回か述べてきたように、Mopsでの
動的オブジェクト生成はハンドルによって行われ、ハンドルは、場合によっては、かえって不便なことがあります。そこで、ポインタを用いたヒープオブジェクトのリストのクラスを自分で作ってみようというわけです。GUIオブジェクト、主としてマルチウィンドウの登録用に使えると思います。
前提の話ですが、MopsにはPtrListというクラスがあります。これはその名に期待される通り、ポインタのリストで、事後的に要素を自由に追加削除できます。ではそのまま使えばいいかというと、残念ながらそうはいきません。実は、このリストはヒープオブジェクトを前提としたものではありません。むしろ、静的なオブジェクトのベースアドレスをリストにして、一括して扱う目的のものといえます。それでも、ポインタのリストを操作するという機構を備えているので、これを継承して、サブクラスで動的オブジェクトをあつかうメソッドを追加する方法が考えられます。これがもっとも合理的なやり方でしょう。ここでは、その方法を説明します。一応、実際にサンプルを書いてみました。最適なやり方というつもりはありませんが、問題なく動くと思います。いくつか、おまけもつけましたが、シリアライズ(オブジェクトをファイルなどに書き出して永続化する方法)の機能はつけませんでした。サンプルコードはここからダウンロードできます。この中身について、主要な部分をここで説明しようと思います。全部は引用しませんので、全貌についてはサンプルをダウンロードして合わせてご参照ください。コードにあまり
コメントはありません。すみません。ブラウザで見たい方はこちら。かんたんでしょ(^_^)
PtrListクラスは、Ptrクラスと(PHLIST)クラスを、この順に二重継承しています。後者は、おそらくPointerとHandleのリスト、という意味で、それ用にカスタマイズされたSEQUENCEクラスのサブクラスです。このPtrListクラスを継承して、ObjPtrListというクラス名にしました。
メッセージは、できるだけHandleListクラスと互換性があるようにすることを目指します。
最初の難関は新しいオブジェクトを生成するところです。NewObj:
メソッドの定義をご覧下さい。
:m newobj: { ^CLASS -- }
nilp add: super
^CLASS CL>LEN Obj_hdr_Length + new: self
0 get: self !
^CLASS obj: self make_obj
;m
引数であるClassXTはnamed parameterに格納しておきます。インデックス付きオブジェクトを追加するときの懸念が不要になります。PtrListに新しい項目を追加するメッセージは、ポインタを引数にしてAdd:です。そこで、とりあえず、nilPをAdd:して、要素を追加しておきます。
それから、オブジェクトのサイズ+オブジェクトのヘッダデータ長(obj_hdr_length)分の大きさのヒープ領域を獲得して、そのポインタを格納します。オブジェクトのサイズは、ワード"CL>LEN"に(オプショナルにインデックスオブジェクトの要素数と)ClassXTを与えて得られます。ヒープ領域の確保にはPtrクラスのNew:メソッドを使います。ヘッダデータを追加する前に、最初の4バイト分をクリアしておく必要があります。そして、オブジェクトの(インデックスオブジェクトには要素数と)classXT、および、実際にオブジェクトデータが始まる部分のアドレス(ヒープ領域のはじめからヘッダの長さの分取り除いたところ)を引数にして,ワードmake_objを呼びます。これで、確保したヒープ領域にオブジェクトのヘッダデータが記録されるとともに、オブジェクトが初期化されます。ヒープオブジェクトを生成するにはこのようにすればよいことは、ObjHandleクラスのメソッド定義を参照すれば明らかです。
オブジェクトのベースアドレスは、いつも、ヒープ領域の頭からヘッダデータの分だけ後方にずれたところにあります。ちなみに、PowerMopsでのヘッダデータの長さは、現時点では12バイトです。ですから、オブジェクトのベースアドレスを採るときは、いつもポインタ値に12足した値を返すことになります。ですから、上でも利用されているObj:メソッドは次のように定義されています。
:m obj:
inline{ ^base @ obj_hdr_length + }
;m
このメソッドは頻繁に呼び出されるであろうという想定の下、
インライン定義をしています。呼び出しをコンパイルするのでなく、呼び出し部分にこのコード内容が直接書き込まれる、という意味らしいです。あまり効果はないかもしれません。
上のobj:メソッドでは、このリストクラスでは、^BASEもSELFの、第一次的にはリスト中の現行(current)要素のオブジェクトベースアドレスを意味しているということを利用しています。つまり、現行要素が変わるたびに、これらが意味するもの(値も)変わるわけです。ここでは^baseは現行要素をなすポインタオブジェクトのベースアドレスですから、そこには、ヒープ領域を指し示すポインタ、つまりその頭のメモリーアドレスが格納されているわけです。そこから、ヘッダデータ分をとばしたところがこのヒープオブジェクトのベースアドレス、というわけです。
もうひとつ、説明しておきたいのは、First?:メソッドとNext?:メソッドをオーバーライドしている点です。これはBEGIN each: ... WHILE REPEAT
ループの挙動を変えるのが目的です。
:m First?: ( -- ^obj T | F )
First?: super NIF false EXIT THEN
obj_hdr_length + true
;m
:m next?: ( -- ^obj T | F )
next?: super NIF false EXIT THEN
obj_hdr_length + true
;m
もう少し特定すれば、First?:とNext:は、(PHLIST)クラスのeach:メソッドの定義の中で、Late Bindで用いられているのです。each:は、はじめはFirst?:を送り、次からはリストが尽きるまでNext?:を送ります。名前からわかる通り、First:はリストの最初の要素(0番)をセレクトし、Next?:は、次の要素をセレクトしていくわけです。これらのメソッドは、また、単にセレクトするだけでなく、対応する要素が存在しなかった場合にはFalseを返し、存在する場合には、"その要素に関わる値"とTrueの二つの値を返します。この真偽値をWHILEが受けてループを制御するわけです。
(PHLIST)クラスでは、"要素に関わる値"としては、格納されているポインタが返されるようになっています。PtrListクラスでもオーバーライドはなく、そのまま継承されています。ところが、そうなると、いま作ろうとしているObjPtrListクラスからみると、この値はヒープ領域そのもののポインタになってしまい、オブジェクトのアドレスにはなりません。いまリスト要素として重要なのは、そこに格納されているオブジェクトの方でしょう。そういうわけで、オブジェクトのベースアドレスが取れるように変形したわけです。
上の変形は、デバッグ用のメソッドであるPRINTALL:とDUMPALL:の定義に役立っています。これらは、PtrListクラスにはなく、HandleListクラスを参考に定義したものです。
:m printAll:
nil?: theList IF ." (not open) " EXIT THEN
get: pos
BEGIN each: self WHILE print: ** cr REPEAT
(SEL): self
;m
:m dumpAll:
nil?: theList IF ." (not open) " EXIT THEN
dump: super cr ." current: " current: self dup . cr
." elements:" cr
BEGIN each: self WHILE dump: ** cr REPEAT
select: self
;m
HandleListクラスは、":Mops source:PPC source:pStruct"というファイルに(もちろん":"はフォルダ階層を意味していて、Mopsの最上位フォルダの中から書いてあります。この期に及んで念のため(^^;;)。)あります。ご参照ください。
関連項目:
最終更新:2018年12月23日 21:54