zoukankan      html  css  js  c++  java
  • 失败的大牛事件委托,与我的委托

    看了网上大牛的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:=@m;
      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:=@Delegate;
      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:=@m;
      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了,都不知道如何改,因为太高级了。。。。

  • 相关阅读:
    ZKEACMS 的两种发布方式
    一步一步教你如何制件 ZKEACMS 的扩展组件/插件
    三张图片看懂ZKEACMS的设计思想
    ZKEACMS 模板组件扩展
    ZKEACMS 如何使用组件自定义样式/编辑样式
    关于简单的Excel多页签底层导出_电子底账导出为例(.net core)
    Javascript中,数学方法可以分成以下几类: constans(常数)、power functions(乘方函数)、trigonometic functions(三角函数)、rounding functions(舍入函数)、random numbers(随机数字)
    AutoCAD.Net/C#.Net QQ群:193522571 对字符串进行四则运算
    AutoCAD.Net/C#.Net QQ群:193522571 利用反射将父类中的属性传递到子类中,实测效率不高
    AutoCAD.Net/C#.Net QQ群:193522571 将DataTable集合 B加入到DataTable A中,应用于两个或以上的只有单行数据的无主键的datatable的合并 Merge
  • 原文地址:https://www.cnblogs.com/IDELPHI/p/DELPHI.html
Copyright © 2011-2022 走看看