失败的大牛事件委托,与我的委托
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了失败的大牛事件委托,与我的委托相关的知识,希望对你有一定的参考价值。
看了网上大牛的DELPHI事件委托,实际用起来是有BUG的。代码如下:
unit faDelegate;
interface
uses
Generics.collections, TypInfo, ObjAuto, SysUtils;
type
Event = class
private
FMethods : TList<TMethod>;
FInternalDispatcher: TMethod;
//悲催的是泛型类的方法不能内嵌汇编,只能通过一个非泛型的父类来实现
procedure InternalInvoke(Params: PParameters; StackSize: Integer);
public
constructor Create;
destructor Destroy; override;
end;
Event<T> = class(Event)
private
FObj:TObject;
FProName:string;
FEntry : T;
function ConvertToMethod(var Value):TMethod;
procedure SetEntry(var AEntry);
public
constructor Create(Obj:TObject;ProName:String );
destructor Destroy; override;
procedure Add(AMethod : T);
procedure Remove(AMethod : T);
function IndexOf(AMethod: T): Integer;
// property Invok : T read FEntry;
end;
implementation
{ Event<T> }
procedure Event<T>.Add(AMethod: T);
var
m : TMethod;
begin
m := ConvertToMethod(AMethod);
if ((m.Code<>nil) and (FMethods.IndexOf(m) < 0)) then
FMethods.Add(m);
end;
function Event<T>.ConvertToMethod(var Value): TMethod;
begin
Result := TMethod(Value);
end;
constructor Event<T>.Create(Obj:TObject;ProName:String );
var
MethInfo: PTypeInfo;
TypeData: PTypeData;
m:TMethod;
p:Pointer;
begin
MethInfo := TypeInfo(T);
if MethInfo^.Kind <> tkMethod then //检测T的类型
raise Exception.Create(‘T only is Method(Member function)!‘);
TypeData := GetTypeData(MethInfo);
Inherited Create();
FInternalDispatcher := CreateMethodPointer(InternalInvoke, TypeData); //把InternalInvoke的函数地址转为TMethod
SetEntry(FEntry); //FEntry是入口地址,设为FInternalDispatcher
FObj:=Obj;
FProName:=ProName;
m:=GetMethodProp(FObj,FProName);
p:[email protected];
Add(T(p^)); //先添加对象原有的方法
SetMethodProp(FObj,FProName,FInternalDispatcher); //设定对象的入口
end;
destructor Event<T>.Destroy;
begin
ReleaseMethodPointer(FInternalDispatcher); //和CreateMethodPointer是一对的,正好相反
inherited Destroy;
end;
function Event<T>.IndexOf(AMethod: T): Integer;
begin
Result := FMethods.IndexOf(ConvertToMethod(AMethod));
end;
procedure Event<T>.Remove(AMethod: T);
begin
FMethods.Remove(ConvertToMethod(AMethod));
end;
procedure Event<T>.SetEntry(var AEntry);
begin
TMethod(AEntry) := FInternalDispatcher;
end;
{ Event }
constructor Event.Create;
begin
FMethods := TList<TMethod>.Create;
end;
destructor Event.Destroy;
begin
FMethods.Free;
inherited Destroy;
end;
procedure Event.InternalInvoke(Params: PParameters; StackSize: Integer);
var
LMethod: TMethod;
begin
for LMethod in FMethods do
begin
//如果用到了栈(也就是Register约定参数大于2或者stdcall,cdecl约定)就把栈内所有数据都拷贝参数栈里面
if StackSize > 0 then
asm
MOV ECX,StackSize //Move的第三个参数,同时为下一步Sub ESP做准备
SUB ESP,ECX //把栈顶 - StackSize(栈是负向的)
MOV EDX,ESP //Move的第二个参数
MOV EAX,Params
LEA EAX,[EAX].TParameters.Stack[8] //Move的第一个参数
CALL System.Move
end;
//Register协议填写三个寄存器,EAX肯定是Self,如果是其他协议寄存器被填写也没啥影响
asm
MOV EAX,Params //把Params读到EAX
MOV EDX,[EAX].TParameters.Registers.DWORD[0] //EDX
MOV ECX,[EAX].TParameters.Registers.DWORD[4] //EAX
MOV EAX,LMethod.Data//把Method.Data给到EAX,如果是Register约定就是Self.否则也没影响
CALL LMethod.Code//调用Method.Data
end;
end;
end;
BUG体验在对TDBGridEh中的列的事件OnupdateData做委托时,对Value参数赋值会有错误!晕,不知道怎么办好!所以只好用自己的方法解决!
我的事件委托:
Delegate<T>=class
private
i:integer;
FEntrance:TMethod;
protected
Delegates:array of TMethod;
procedure AddMethod(m:TMethod);
function GetRunEof():Boolean;
function GetRun():T;
public
constructor Create(C: TObject;ProName:string);virtual;
destructor Destroy; override;
procedure Add(Delegate:T);
end;
DeNotify=class(Delegate<TNotifyEvent>)
published
procedure DoRun(Sender:TObject);
end;
implementation
procedure Delegate<T>.Add(Delegate: T);
var m:TMethod;
p:Pointer;
begin
p:[email protected];
m:=Tmethod(p^);
AddMethod(Tmethod(p^));
end;
procedure Delegate<T>.AddMethod(m: TMethod);
begin
if ((m.Code=nil) or (m.Data=nil)) then exit;
if (m.Code<>FEntrance.Code) then begin
SetLength(Delegates,High(Delegates)+2);
Delegates[High(Delegates)]:=m;
end;
end;
constructor Delegate<T>.Create(C: TObject; ProName: string);
begin
FEntrance.Data:=Self;
FEntrance.Code:=MethodAddress(‘DoRun‘);
AddMethod(GetMethodProp(c,ProName));
SetMethodProp(c,ProName,FEntrance);
i:=0;
// if Assigned(lstDelegates)=false then begin
// lstDelegates:=TList.Create;
lstDelegates.Add(Self);
// end;
end;
destructor Delegate<T>.Destroy;
begin
Dec(iTotal);
// if lstDelegates.Count=0 then
// lstDelegates.Free
// else
lstDelegates.Delete(lstDelegates.IndexOf(self));
inherited;
end;
function Delegate<T>.GetRun: T;
var m:TMethod;
p:Pointer;
begin
m:=Delegates[i-1];
p:[email protected];
Result:=T(p^);
end;
function Delegate<T>.GetRunEof: Boolean;
begin
Result:=not (i<=High(delegates));
if Result=false then
Inc(i)
else
i:=0;
end;
procedure DeNotify.DoRun(Sender: TObject);
begin
while not GetRunEof() do
GetRun()(Sender);
end;
这个方法有很大的缺点,就是一种事件类型要派生一个类!但实在,没有什么问题。
看来事物都有两面性,浓缩很大的代码,做起来很有技巧,很高难度,而且会比较容易出错。
如果浓缩不大的代码,所需要的技巧不多,容易理解,但是冗余又比较多。不爽。
不过,无论如何,正确是第一的。技巧再高,不正确也没有用。第一种方法好象很强大,但有BUG了,都不知道如何改,因为太高级了。。。。
以上是关于失败的大牛事件委托,与我的委托的主要内容,如果未能解决你的问题,请参考以下文章