2011年10月22日星期六

匿名方法转换为对象方法

前面说TDSServerClass是提到一个问题,就是匿名方法如何转换为对象方法

比如我想写的代码是
procedure TForm11.FormCreate(Sender: TObject);
begin
  Button1.OnClick := procedure(Sender: TObject)
    begin
      ShowMessage('Button1 Clicked');
    end;
end;
这样直接编译不过去,因为一个是函数引用,是个是对象方法
函数引用 reference to procedure(Sender: TObject)
对象方法 procedure(Sender: TObject) of object;

带着疑问拜google,得到下面的解决方法,连接地址
procedure TForm11.FormCreate(Sender: TObject);
begin 
  @Button1.OnClick := PPointer(Cardinal(PPointer(
    procedure(Sender: TObject)
    begin
      ShowMessage('Button1 Clicked');
    end
    )^) + $0C)^;
end;

很神奇,原理是说 匿名函数被编译器编译成了一个接口类,$0C的地址所指,也就是接口类的VMT IUnknow第4个函数的入口(前三个是QueryInterface,_AddRef,_Release,TInterfacedObject实现)。因为都是对象方法,所以不用转换,只是将函数入口地址赋值即可。
我写代码测试了几次,能得出一个结论,在一个函数内定义的函数引用类型变量,会编译成“函数名$ActRec”的类(姑且叫ActRec),并只产生一个ActRec的实例,每个函数引用变量就对应着一个接口,有几个函数变量,就会有几个接口,然后这个ActRec都会实现这几个接口。


但是如果匿名函数太长,单独定义写呢
procedure TForm11.FormCreate(Sender: TObject);
type
  TNotifyProc = reference to procedure(Sender: TObject); 
  TNotifyXXX = reference to procedure(a: Integer; b: Integer); 
var
  B1,B2: TNotifyPro;
  Bi: Interface;
  Bo: TObject;
  xxx: TNotifyXXX; 
begin
  B1 := procedure(Sender: TObject)
    begin
      ShowMessage('Button1 Clicked'); 
    end;
  B2 := procedure(Sender: TObject)
    begin
      ShowMessage('Button1 Clicked'); 
    end;
  xxx := procedure(a: Integer; b: Integer)
    begin
      ShowMessage('xxx'); 
    end;

  Bi := PUnknown(@B1)^;    //函数引用其实是个接口来的
  Bo := Bi as TObject;       //将接口转为为实体类
  showmessage(Bo.ClassName); //TForm11.FormCreate$ActRec,该类实现了3个接口

  @Button1.OnClick := PPointer(Cardinal(PPointer(
    Integer(Bi) //可行
    )^) + $0C)^;

  @Button1.OnClick := PPointer(Cardinal(PPointer(
    PInteger(@B1)^ //可行
    )^) + $0C)^;

  @Button1.OnClick := PPointer(Cardinal(PPointer(
   Bo.GetInterfaceTable^.Entries[0].VTable //这样不行,为啥????
    )^) + $0C)^;end;

难道接口变量已经不是指向VTable所指了????
不过这种靠编译器的,毕竟不是好方法,继续找找看。

没有评论:

发表评论