クロージャの持つ関数ポインタを得るには @ 演算子を使う
type THogehoge = class public procedure Dummy; end; var cl: procedure of object; fp: procedure; begin fp := @cl; // 関数ポインタだけ取り出し if @cl = @THogehoge.Dummy then // クロージャの関数ポインタと THogehoge の Dummy 関数が等しかったら ... end;
以下,解説。
type TDummy = class private FMyName: string; public constructor Create(AMyName: string); procedure Greeting; end; constructor TDummy.Create(AMyName: string); begin FMyName := AMyName; end; procedure TDummy.Greeting; begin Writeln('hello, ' + FMyName + '.'); end;
みたいな単純なクラスがあったとして(ありがちな例ですけど)。
var o1: TDummy; begin o1 := TDummy.Create('hogehoge'); o1.Greeting; end.
こいつを実行すると,
hello, hogehoge.
と(当然)出力されますが,Delphi 用語でいうところのクロージャというのを使うと,
var o1: TDummy; cl: procedure of object; begin o1 := TDummy.Create('hogehoge'); cl := o1.Greeting; cl(); // クロージャの実行 end.
こいつを実行しても hello, hogehoge と表示されます。このクロージャというのはいわゆる一般的な言語の closure とはちとちがっていて,インスタンスへのポインタ+関数ポインタをまとめて熱かっているだけです。なので,この内部構造をみてみよう,というのが今回のお話。
クロージャ自身はどうせポインタだろうから,とむりやり Cardinal に変換して
var o1: TDummy; cl: procedure of object; begin o1 := TDummy.Create('hogehoge'); Writeln(Format('o1: %8.8x', [Cardinal(o1)])); cl := o1.Greeting; Writeln(Format('cl: %8.8x', [Cardinal(cl)])); end.
このようにしても,コンパイル時に,
[エラー] 正しくない型キャスト
のように怒られてしまいます。
ともかく,クロージャの示す先を @@ という表記で取り出してみます。
var o1: TDummy; cl: procedure of object; pd: PLongword; begin o1 := TDummy.Create('hogehoge'); Writeln(Format('o1: %8.8x', [Cardinal(o1)])); cl := o1.Greeting; pd := PLongword(@@cl); Writeln(Format('cl[0]: %8.8x', [Cardinal(pd^)])); Inc(pd); Writeln(Format('cl[1]: %8.8x', [Cardinal(pd^)])); end.
cl の示す先は @cl でいいんじゃないの?と一瞬思いますが,今は pd がクロージャを示したポインタ(を PLongword でキャストしたもの)と思いねえ。ともかくこいつを実行すると,
o1: 00CF0920 cl[0]: 00408174 cl[1]: 00CF0920
このように,クロージャの中身がさらされました。cl[1] はまぎれもなく o1 と同じ値を示しています。では,cl[0] は,いったい何なのか?
var o1: TDummy; cl: procedure of object; pd: PLongword; begin o1 := TDummy.Create('hogehoge'); Writeln(Format('o1: %8.8x', [Cardinal(o1)])); cl := o1.Greeting; pd := PLongword(@@cl); Writeln(Format('cl[0]: %8.8x', [Cardinal(pd^)])); Inc(pd); Writeln(Format('cl[1]: %8.8x', [Cardinal(pd^)])); Writeln(Format('TDummy.Greeting: %8.8x', [Cardinal(@TDummy.Greeting)])); end.
これを実行すると(最後の一行が追加されただけ),
o1: 00CF0920 cl[0]: 00408174 cl[1]: 00CF0920 TDummy.Greeting: 00408174
おお,cl[0] というのは実際には TDummy の Greeting という関数へのポインタだった,と。
と書くと非常にしらじらしいですが,実際にクロージャというものが「関数ポインタ」+「インスタンスへのポインタ」で構成されていたことがわかります。
これを使うことがあるのか,といわれると微妙ですが,まったくなくもない,ということにしておきます。問題は,クロージャを関数ポインタに分解することはできても,逆はできない(みたい)だということです。
var cl: procedure (Arg: string) of object; fp: procedure (Arg: string); o1, o2: THogehoge; begin cl := o1.MyProc; fp := @cl; o2.fp('fugafuga'); end.
みたいなことができればいいのですが。