想问的问题或许并非会象大家所理解的问题一样!但我实在是找不到一个能够很明确的说明我这个问题的题目了。大家请听我分析说来。
    该问题起源于事件!总体说起来的问题也就是说动态的为某控件对象等设置属性。如:
    在窗体中定义一个事件类型如下:
    TTestEvent = procedure(Sender: TObject;x,y,z,t: integer) of object;
    然后在Published处做一 OnTest事件属性类型为TTestEvent
    众所周知,我只要窗体创建的时候指定如 self.onTest := MyTest;
    然后建立一个MyTest对应的过程则可。这个过程会包含Sender;x,y,z,t等参数。
    然而当使用SetMethod的方法来设置该属性的话,本事件方法未必会有参数,如:
  procedure SetEvent;                  |    Procedure  DoEvent
  var                                  |    begin
    Method: TMethod;                   |       //事件将跳到这里执行
  begin                                |    end;
    MeThod.Data := self;               |//现在我的问题就是,在我指定的这个DoEvent过程中,如果我需要引用,  
    Method.Code := @DoEvent;           |前面触发该事件的地方传递过来的参数的时候,我就需要从这个没有参数的
    SetMethodProp(self,'OnTest',Method)|过程中来得到前面传递过来的参数,这也就是我的题目所说的
 end;                                  |得到外部传递过来的参数值问题了。不知道大家明白没有。比如,我在窗体的一个按扭中触发该事件 self.OnTest(self,6,72,33,99);此时就会跳到DoEvent函数处来执行,我的意思
就是说,要在DoEvent用self.OnTest(self,6,72,33,99);中的self,6,72,33,99等参数,我该如何得到这些参数呢!

解决方案 »

  1.   

    具体的想了一下,想想,在触发OnTest事件的时候,由于该事件是有参数的,所以很显然的和普通函数的调用
    应该是一样的。那么通过Delphi的函数调用时候,参数的传递规则,可知道
    Self---->EDX ,6---->ECX,然后后面的三个参数都将压入堆栈中!
    那么当事件函数得以触发进入DoEvent过程的时候,其堆栈内容和寄存器的值应是没变的
    那么这个时候通过寄存器和堆栈的操作是应该可以得到参数的值的。所以针对上面的写了一个函数用来得到
    OnTest在DoEvent中的参数
    procedure DoEvent;
    var
      x,y,z,t: integer;
    begin
       asm
        push  esi
        mov   esi,[ebp+8]//最后一个参数
        mov   t,esi
        mov   esi,[ebp+12]//倒数第二个参数
        mov   z,esi
        mov   esi,[ebp+16]//倒数第三个参数
        mov   z,esi
        mov   x,ecx//第二个参数
        pop   esi
      end;
      ShowMessage(IntToStr(x));
      ShowMessage(IntToStr(y));
      ShowMessage(IntToStr(z));  asm //为保持栈平衡,进行循环出栈
        mov      ecx,ebp
        sub      ecx,esp
        @PopUp:
           pop  eax
           sub  ecx,4
           jnz  @PopUp
           pop  ebp
           ret   12
      end;
    end;
    这样则得到了对应的x,y,z等值了。
    但是这样的话,就很不灵活,因为事件类型有很多种!而且其参数都各不相同!
      

  2.   

    针对上面的不灵活性,我想到使用Variant动态数组来实现得到参数值
    这样的话,就只针对对象和事件名就可全部来得到每个参数所对应的参数值了。
    于是实现了一个 TEvent事件类如
    TEvent = class
      private
        FEventName: string;
        FEventObj: TObject;
        FParCount: integer;
        procedure SetEventName(const Value: string);
        procedure SetEventObj(const Value: TObject);
      public
         property EventName: string read FEventName write SetEventName;//事件名
         property EventObj: TObject read FEventObj write SetEventObj; //事件所属的对象
         property ParCount: integer read FParCount;//事件参数个数
      end;
    那么则将开头提出问题的那个SetEvent函数修改成
    procedure SetEvent;
    var
      Method: TMethod;
      E: TEvent;
    begin
       E := TEvent.Create;
       E.EventName := 'OnTest';
       E.EventObj := Self;
       MeThod.Data := E; 这里用来传递给事件过程的EAX中。
       Method.Code := @DoEveryEvent;
       SetMethodProp(self,'OnTest',Method)
    end;
    然后将DoEvent带一个 TEvent类型的参数,也就是通过SetEvent中传递过来的事件对象E了
    那么,在DoEvent的时候,我就可根据传递过来的TEvent得到参数个数来得到各个参数了。
    于是DoEvent改成
    procedure DoEveryEvent(Event: TEvent);
    var
      ParCount: integer;
      parValues: array of Variant;
    begin
       ParCount := Event.ParCount;
       SetLength(parValues,ParCount);
       asm
          cmp     ParCount,2
          jbe     @InReg
          push    esi
          push    edi
          push    eax
          mov     esi,0
        @loop:
          mov     edi,[ebp+8][esi]
          lea     eax, parvalues
          sub     eax,16
          mov     [eax][esi],edi
          add     esi,4
          dec     parcount
          cmp     parcount,2
          jnz     @loop
          pop     edi
          pop     esi
          pop     eax
        @InReg:
          push    eax
          lea     eax,parvalues
          cmp     parCount,2
          jb      @one
          sub     eax,4
          mov     [eax],ecx
          lea     eax,parvalues
        @One:
          mov     [eax][0],ecx
          pop     eax
       end;
       ShowMessage('sdf');
       ParCount := Event.ParCount;
       if parValues[0] = 6 then     //加了这句代码程序就出错!不知道为什么写入到内存中的值他怎么找到另
          ShowMessage('sdfsdfsdfs');//一个地方去了    
       asm
         mov      ecx,ebp
         sub      ecx,esp
         lea      eax,ParCount
         mov      eax,[eax]
       @popUp:
         pop      edx
         sub      ecx,4
         cmp      ecx,0
         jnz      @popUp
         pop      ebp
         cmp      eax,2
         jg       @1
         ret
       @1:
         cmp      eax,3
         jg       @2
         ret      4
       @2:
         cmp      eax,4
         jg       @3
         ret      8
       @3:
         cmp      eax,5
         jg       @4
         ret      12
       @4:
         cmp      eax,6
         jg       @6
         ret      16
       @5:
         cmp      eax,7
         jg       @6
         ret      18
       @6:
         cmp      eax,8
         jg       @7
         ret      20
       @7:
         ret      24
       end;
    end;
      

  3.   

    说错了,是一楼以上的代码源程序。二楼的代码就不要了。发邮箱也可:[email protected]谢谢
      

  4.   

    ?今明两天暂时没时间与楼主一起研究了。楼主看到回复后,如愿意,请加我QQ。QQ号在站内短消息里。这是一个老贴:http://topic.csdn.net/u/20070521/21/4ee74dda-5627-4ee4-a67c-bb9559111690.html
    以及我整理的文章:http://rabbitfox.blog.sohu.com/47419399.html与楼主研究的问题接近。
      

  5.   

    楼主可以考虑Format函数的参数类型,array of const
      

  6.   

    能理解你的意思,搜了半天,不知道这句话对你有用不。
    if GetPropInfo(Button2, 'Caption', [tkString, tkLString, tkWString]) <> nil then
      SetPropValue(Button2, 'Caption', 'ABC');
      

  7.   

    {向楼主汇报我的“研究成果”}unit Unit1;interfaceuses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls;type
      TVarArr = array of variant;
      TTestEvent = procedure(Sender: TObject; Vars: integer) of object; {只需显式声明两个形参}
      TForm1 = class(TForm)
        Button1: TButton;
        Button2: TButton;
        procedure Button1Click(Sender: TObject);
        procedure Button2Click(Sender: TObject);
        procedure FormCreate(Sender: TObject);
      private
        FonTest: TTestEvent;
        FVarArr: TVarArr;
        { Private declarations }
      public
        { Public declarations }
      published
        property OnTest: TTestEvent read FonTest Write FOnTest;
      end;var
      Form1: TForm1;implementation
    uses TypInfo;
    {$R *.dfm}procedure Test;
    var
      Count, I : integer;
      P : Pointer;
    begin
       asm
        mov   P,   ecx  {取得Vars的地址}
        mov   ecx, ebp  {下面这部分汇编指令,是你原先的,我没有精简}
        sub   ecx, esp
        push  ecx
      end;  Count := TVarArr(P)[0]; {取得有效参数个数}
      for I := 1 to Count do
        Showmessage(VartoStr(TVarArr(P)[I])); {循环显示Count个有效参数}  asm
        pop ecx
        @PopUp:
           pop  eax
           sub  ecx,4
           jnz  @PopUp
           pop  ebp
           ret  {这里的跳转指令就不必带偏移字节数了}
      end;
    end;procedure TForm1.Button1Click(Sender: TObject);
    var
      Method: TMethod;
    begin
       MeThod.Data := Self;
       Method.Code := @Test;
       SetMethodProp(Self, 'OnTest', Method);
       Button2.Enabled := True;
       Button1.Enabled := False;
    end;procedure TForm1.Button2Click(Sender: TObject);
    begin
      SetLength(FVarArr, 7);  {假设我们需要传递6个有效参数}
      FVarArr[0] := 6;        {6个有效参数}
      FVarArr[1] := 'TestString 1';
      FVarArr[2] := 'TestString 2';
      FVarArr[3] := 'TestString 3';
      FVarArr[4] := 'TestString 4';
      FVarArr[5] := 'TestString 5';
      FVarArr[6] := 'TestString 6';  Self.OnTest(Self, integer(@FVarArr[0]));end;procedure TForm1.FormCreate(Sender: TObject);
    begin
      Button1.Caption := 'Set Method';
      Button2.Caption := 'Test';
      Button2.Enabled := False;
    end;end.
      

  8.   

    续楼上:当然,用array of variant还存在数据类型转换的问题,但这应该是另一个问题了。传多个参数(不固定)的问题应该是解决了。这样的问题很有趣味!^_^
      

  9.   

    {汗....Test过程,再简化如下}procedure Test; 
    {因为是默认的Register传参方式,前三个参数通过eax、edx、ecx传递
    ,所以,只要参数少于三个(注意,除Sender和Vars外还有一个Self指针)
    ,就不必理会栈顶处理}
    var
      Count, I : integer;
      P : Pointer;
    begin
      asm
        mov   P,   ecx  {取得Vars的地址}
      end;  Count := TVarArr(P)[0]; {取得有效参数个数}
      for I := 1 to Count do
        Showmessage(VartoStr(TVarArr(P)[I])); {循环显示Count个有效参数}end;
      

  10.   

    To lihuasoft刀友:
        TVarArr这样的用法,我第一次见到,感觉实用不少!
    但是这样的写法没有解决根本问题啊!通过我的意思,我想你应该还没有理解我要这样的主要用途了!
    其实我是实现一个脚本引擎的通用事件过程。
    但是你这样写的话,还是需要自己通过传递的参数来得到结果,这样的话,你写与不写这个就没有什么实际意义
    了,我的目的就是在我根本不知道别人将要触发什么样的事件过程了,而是通过RTTI在程序触发了这样的事件的
    时候才得到这个事件是个什么事件,该事件将会传递多少个参数,然后通过参数的个数来得到这些参数的值,从
    而将这些值提供给脚本中的事件方法使用。
    总体说来,我的目的就是实现一个通用的事件过程,所有的事件触发都通过该过程来执行
    代码简写如下:
    procedure DoEveryEvent(EventDes: TEventDes);
    begin
       //这里的EventDes就是我的脚本事件描述类了,在开始脚本注册的时候,读到某些对象的事件的时候就会将 
       //创建一个事件描述类型了。该事件描述类中有一个该事件所指向的脚本中的函数过程Func
       //我这里的目的就是要先将触发该事件时候传递进入的参数读取出来,然后传递到我的脚本引擎中的脚本
       //事件触发过程函数中,所以这里就有设置临时变量值,然后脚本中才可用该传递过来的值了
       asm
         //这里就得到参数值
       end;
         //然后传递参数值到脚本事件函数中去,然后执行
       EventDes.Func.Run();  //执行脚本过程
       asm
          //出栈保持堆栈平衡
       end; 
    end;-----------------------------看看脚本如下--------------------
    procedure  FrmClose(Sender: TObject;Var Action: TCloseAction);
    begin
      MessageBox('这是脚本中的代码,而不是Delphi中的代码');
      Action := 0;//如果有参数传递进来的话,我这里才可以使用
    end;
    -------------这里脚本程序入口-----------------------
    begin
      Frm := TForm.Create(self);
      Frm.Caption := '脚本中的窗体,动态窗体!';
      Frm.OnClose := @FrmClose;//指定窗体的关闭事件,这样,窗体关闭的时候就执行我上面的FrmClose脚本函数了
    end;
    -----------------脚本完成------------
        
       然后听我的解释脚本执行原理,首先脚本程序编译,碰到FrmClose过程,然后解析代码成半解析代码。完成之后进入到脚本程序入口,先创建窗体Frm然后指定窗体标题,然后碰到了Frm.OnClose事件指定,这时候就会将FrmClose脚本函数的事件引用计数增加1,同时添加一个事件描述,来描述该事件的一些信息,这些信息中一个主要信息就是Func,他指向脚本函数FrmClose,然后开始设置Frm的OnClose事件,该事件指定过程就是我在2楼说的
    了。
    我想我这样一说,你应该就明白是什么意思了吧!想想你那样的方式的话,确实是能够传递非固定参数!呵呵,但是你想想和我的这个还是有一定的差距吧,很简单的举例如:
       很多事件都是通过同一个函数来执行,只是要根据不同的事件类型来得到不同的参数,然后传递到脚本引擎中去。
      

  11.   

    我觉得如果想从一个普通的TMethod中知道所有的参数和参数类型是不可能的
    事件为什么要定义类型就是因为要匹配某个方法入口参数的类型、顺序以及返回值(似乎该信息并为额外存储)
    类似于楼主的要求感觉必须进行多一层次的封装,就如11楼那样,需要先指定参数个数
    其实IDispatch不就在干类似的事么?
      

  12.   

    不得闲兄,在窗体上放个ListBox,运行下试试,看看是否是你想要的
    type
      TestEvent = procedure(Sender: TObject;x,y,z,t: integer) of object;
    procedure GetMethodTypeInfo(ATypeInfo: PTypeInfo; AStrings: TStrings);
    type
      PParamData = ^TParamData;
      TParamData = record       // 函数参数的数据结构
        Flags: TParamFlags;     // 参数传递规则
        ParamName: ShortString; // 参数的名称
        TypeName: ShortString;  // 参数的类型名称
      end;
      function GetParamFlagsName(AParamFlags: TParamFlags): string;
      var
        I: Integer;
      begin
        Result := '';
        for I := Integer(pfVar) to Integer(pfOut) do begin
          if I = Integer(pfAddress) then Continue;
          if TParamFlag(I) in AParamFlags then
            Result := Result + ' ' + GetEnumName(TypeInfo(TParamFlag), I);
        end;
      end;
    var
      MethodTypeData: PTypeData;
      ParamData: PParamData;
      TypeStr: PShortString;
      I: Integer;
    begin
      MethodTypeData := GetTypeData(ATypeInfo);
      AStrings.Add('---------------------------------');
      AStrings.Add('Method Name: ' + ATypeInfo^.Name);
      AStrings.Add('Method Kind: ' + GetEnumName(TypeInfo(TMethodKind),
        Integer(MethodTypeData^.MethodKind)));
      AStrings.Add('Params Count: '+ IntToStr(MethodTypeData^.ParamCount));
      AStrings.Add('Params List:');
      ParamData := PParamData(@MethodTypeData^.ParamList);
      for I := 1 to MethodTypeData^.ParamCount do
      begin
        TypeStr := Pointer(Integer(@ParamData^.ParamName) +
          Length(ParamData^.ParamName) + 1);
        AStrings.Add(Format('  [%s] %s: %s',[GetParamFlagsName(ParamData^.Flags),
          ParamData^.ParamName, TypeStr^]));
        ParamData := PParamData(Integer(ParamData) + SizeOf(TParamFlags) +
          Length(ParamData^.ParamName) + Length(TypeStr^) + 2);
      end;
      if MethodTypeData^.MethodKind = mkFunction then
        AStrings.Add('Result Value: ' + PShortString(ParamData)^);
    end;procedure TForm1.Button1Click(Sender: TObject);
    begin
      GetMethodTypeInfo(TypeInfo(TTestEvent), ListBox1.Items);
    end;
      

  13.   

    很简单的举例如: 
       很多事件都是通过同一个函数来执行,只是要根据不同的事件类型来得到不同的参数,然后传递到脚本引擎中去。
    -----------------------------------------------------------------------------------------
    上面的方法不是可得到参数的具体个数和类型吗,这样的话你试试用汇编能处理不能?
    期待ING...