想问的问题或许并非会象大家所理解的问题一样!但我实在是找不到一个能够很明确的说明我这个问题的题目了。大家请听我分析说来。
该问题起源于事件!总体说起来的问题也就是说动态的为某控件对象等设置属性。如:
在窗体中定义一个事件类型如下:
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等参数,我该如何得到这些参数呢!
该问题起源于事件!总体说起来的问题也就是说动态的为某控件对象等设置属性。如:
在窗体中定义一个事件类型如下:
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等参数,我该如何得到这些参数呢!
应该是一样的。那么通过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等值了。
但是这样的话,就很不灵活,因为事件类型有很多种!而且其参数都各不相同!
这样的话,就只针对对象和事件名就可全部来得到每个参数所对应的参数值了。
于是实现了一个 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;
以及我整理的文章:http://rabbitfox.blog.sohu.com/47419399.html与楼主研究的问题接近。
if GetPropInfo(Button2, 'Caption', [tkString, tkLString, tkWString]) <> nil then
SetPropValue(Button2, 'Caption', 'ABC');
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.
{因为是默认的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;
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楼那样,需要先指定参数个数
其实IDispatch不就在干类似的事么?
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;
很多事件都是通过同一个函数来执行,只是要根据不同的事件类型来得到不同的参数,然后传递到脚本引擎中去。
-----------------------------------------------------------------------------------------
上面的方法不是可得到参数的具体个数和类型吗,这样的话你试试用汇编能处理不能?
期待ING...