[ top ] [ prev ] [ up ] [ next ] Last-Modified: 2003/10/23

Apollo ドキュメントを書かなくちゃ

Apollo が何に使えるのか書いてこなかった。道具ってのは使って何ぼのもんだから,活用事例をまとめてみるのは,普及のために大事なことだ。

チュートリアルはみずほちゃんががんばってくれてるから安心して任せておけるけど, Delphi での 組み込み Ruby の説明は私の責務だな。ぼちぼち書きためよう。

Marshal

実は, Phi::Component は Marchal に対応している。

ex. apollo/sample/marshal/c_marshal.rb
(fail to include c:/apollo/sample/marshal/c_marshal.rb)

*.dfm を読み込む

Phi::Component の Marshal は TStream.WriteComponent, TStream.ReadComponent_dump, _load に対応付けることで実現している。だから _dump, _load を直接使うと *.dfm を読み書きできる。

ex. apollo/sample/marshal/c_dump.rb
(fail to include c:/apollo/sample/marshal/c_dump.rb)
ruby c_dump.rb
この結果, form.afm, btn1.afm が作られる。*.afm の書式は *.dfm と同じだが,object で指定されるクラス名が若干違う(Delphi では,例えば TForm を使うときには継承して TForm1 が作られ,Form1.dfm の頭は object TForm1 となる)。だから拡張子を *.dfm (Delphi Form) ではなくて *.afm (Apollo Form) とした。
form.afm
(fail to include c:/apollo/sample/marshal/form.afm)
btn1.afm
(fail to include c:/apollo/sample/marshal/btn1.afm)
また,見ていただくと分かるように現状の _dump はインデントへの配慮が欠けている。手抜きだが,大目に見てやってほしい。

*.afm_load で読み込むとコンポーネントを元の姿に戻せる。
ex. apollo/sample/marshal/c_load.rb
(fail to include c:/apollo/sample/marshal/c_load.rb)

イベントハンドラ

基本

Delphi イベントハンドラでは引数を変換してそれらを Ruby に渡す。Ruby 側ではイベントハンドラは特異メソッドか手続きブロックで定義する。Ruby オブジェクトに変換したイベントハンドラの引数は,特異メソッドや手続きブロックの引数になる。

例えば, TWebBrowserHandle.doCommandStateChange は TWebBrowser.OnCommandStateChange に代入されるイベントハンドラである。
procedure TWebBrowserHandle.doCommandStateChange(Sender: TObject;
  Command: Integer; Enable: WordBool);
var
  recv, data: Tvalue;
begin
  recv := TComponent(Sender).tag;
  data := rb_ary_new;
  rb_ary_push(data, rb_intern('on_command_state_change'));
  rb_ary_push(data, recv);
  rb_ary_push(data, ap_Fixnum(Command));
  rb_ary_push(data, ap_bool(Enable));
  PhiCallProtect(data);
end;
これを, WebBrowser オブジェクトの初期設定手続き WebBrowser_setup では
procedure WebBrowser_setup(obj: TValue; real: TWebBrowser);
begin
...
  if @real.OnCommandStateChange = nil then
    real.OnCommandStateChange := Handle.doCommandStateChange;
...
end;
のように使っている。

上書き

phi ライブラリでは, TPhiHandle 型の Handle という変数に代入されているひとつのオブジェクトがすべてのイベントハンドラを持つ。 また, Phi 拡張ライブラリでは,それぞれ自前の Handle を用意する。例えば, webbrowser ライブラリでは TWebBrowserHandle 型を定義している。だから,イベントハンドラを設定するために,対応する Handle のメソッドポインタを代入するわけだ。単純に考えると
  real.OnCommandStateChange := Handle.doCommandStateChange;
これでよい。でも,これでは都合が悪い。既存のアプリケーションに Apollo を組み込むことを考えてみてほしい。設計時にすでに real.OnCommandStateChange にイベントハンドラを登録していたら,それは上書きされてしまう。設計時に登録されたイベントハンドラを全て上書きされてしまってはアプリケーションが全く使えなくなってしまうよね。したがって,実際には
  if @real.OnCommandStateChange = nil then
    real.OnCommandStateChange := Handle.doCommandStateChange;
このように,上書きを禁止している。こうすると今度は,イベントハンドラを書き換えたくても Ruby 側からは書き換えられなくなってしまった。作者としては,これくらいの制限があるほうがアプリケーションの保守が楽になって良いと思っている。どうしても書き換えたり Ruby 側からもイベントを捕捉したいときには,その Delphi 側のイベントハンドラの中に
  Handle.OnCommandStateChange(Sender, Command, Enable);
と記述すればよい。

引数

見ていただくとわかるように,イベントハンドラの引数は Ruby オブジェクトに変換されたあと,data という Array オブジェクトに push され, data を引数にして PhiCallProtect を呼ぶ。data への push 順は固定されている。

id
Ruby 側でのイベントハンドラ名を intern した値
recv
Sender に対応する Ruby オブジェクト

基本的には,recv に続いて Delphi イベントハンドラの引数をそのままの順で push していく。
ただし,変数パラメータ (var) が存在する場合は,変数パラメータを recv の直後に持ってくる。

ex. TPhiHandle.MouseWheelOnMouseWheel
procedure TPhiHandle.MouseWheelOnMouseWheel(Sender: TObject;
  Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint;
  var Handled: Boolean);
var
  recv, data, ret: Tvalue;
  pointp: PPoint;
  pointv: Tvalue;
begin
  recv := TComponent(Sender).tag;
  if recv = 0 then Exit;
  data := rb_ary_new;
  rb_ary_push(data, rb_intern('on_key_up'));
  rb_ary_push(data, recv);
  rb_ary_push(data, ap_bool(Handled));
  rb_ary_push(data, ap_set_to_ary(Shift));
  rb_ary_push(data, INT2FIX(WheelDelta));

  new(pointp);
  pointv := rb_data_object_alloc(ap_cPoint, pointp,
    nil, @ap_dispose);
  rb_ary_push(data, pointv);
  pointp^.X := MousePos.X;
  pointp^.Y := MousePos.Y;

  ret := PhiCallProtect(data);
  if ret <> Qnil then handled := RTEST(ret);
end;
変数パラメータが複数ある場合はそれらをひとつの Array オブジェクトに push して,その Array オブジェクトを recv の直後に push しなければならない。

ex. TPhiHandle.CanResizeOnCanResize
procedure TPhiHandle.CanResizeOnCanResize(Sender: TObject;
  var NewWidth, NewHeight: Integer; var Resize: Boolean);
var
  recv, data: Tvalue;
  ary, ret, val: Tvalue;
begin
  recv := (Sender as TComponent).tag;
  data := rb_ary_new;
  rb_ary_push(data, rb_intern('on_can_resize'));
  rb_ary_push(data, recv);
  ary := rb_ary_new;
  rb_ary_push(ary, INT2FIX(NewWidth));
  rb_ary_push(ary, INT2FIX(NewHeight));
  rb_ary_push(ary, ap_bool(Resize));
  rb_ary_push(data, ary);
  ret := PhiCallProtect(data);
  if RTYPE(ret) <> T_ARRAY then Exit;
  val := rb_ary_shift(ret);
  NewWidth := FIX2INT(val);
  val := rb_ary_shift(ret);
  NewHeight := FIX2INT(val);
  val := rb_ary_shift(ret);
  Resize := RTEST(val);
end;

戻り値

変数パラメータが存在する場合は PhiCallProtect の戻り値を処理して変数パラメータに反映させなければならない。

設定の自動化

ここでは,手続きの中身でなく,手続きの名前に注目してほしい。
  KeyPressOnKeyPress
これはイベント型名とメソッド名に由来している。
  TKeyPressEvent + OnKeyPress
この命名規則が設定自動化の肝である。
uProp.pas の AssignPropMethod で,この命名規則を用いている。
  ATypeInfo := APropInfo.PropType^;
  name := ATypeInfo^.Name;
  name := Copy(name, 2, Length(name)-6) + APropInfo.Name;
これで,メソッドポインタの代入先を機械的に決めることができる。
author: Kazuhiro Yoshida
[ top ] [ prev ] [ up ] [ next ]