Getting Allen Bauer's TMulticastEvent working

前端 未结 2 1238
梦毁少年i
梦毁少年i 2020-12-13 03:03

I\'ve been mucking around with Allen Bauer\'s code for a generic multicast event dispatcher (see his blog posts about it here).

He gives just enough code to make me

2条回答
  •  南方客
    南方客 (楼主)
    2020-12-13 03:33

    The answer, after I have done a lot of running around on the web, is that the assembler assumes that a stack frame is present when calling in to InternalSetDispatcher.

    It seems that a stack frame was not being generated for the call to InternalSetDispatcher.

    So, the fix is as easy as turning on stack frames with the {$stackframes on} compiler directive and rebuilding.

    Thanks Mason for your help in getting me to this answer. :)


    Edit 2012-08-08: If you're keen on using this, you might want to check out the implementation in the Delphi Sping Framework. I haven't tested it, but it looks like it handles different calling conventions better than this code.


    Edit: As requested, my interpretation of Alan's code is below. On top of needing stack frames turned on, I also needed to have optimization turned on at the project level for this to work:

    unit MulticastEvent;
    
    interface
    
    uses
      Classes, SysUtils, Generics.Collections, ObjAuto, TypInfo;
    
    type
    
      // you MUST also have optimization turned on in your project options for this
      // to work! Not sure why.
      {$stackframes on}
      {$ifopt O-}
        {$message Fatal 'optimisation _must_ be turned on for this unit to work!'}
      {$endif}
      TMulticastEvent = class
      strict protected
        type TEvent = procedure of object;
      strict private
        FHandlers: TList;
        FInternalDispatcher: TMethod;
    
        procedure InternalInvoke(Params: PParameters; StackSize: Integer);
        procedure SetDispatcher(var AMethod: TMethod; ATypeData: PTypeData);
        procedure Add(const AMethod: TEvent); overload;
        procedure Remove(const AMethod: TEvent); overload;
        function IndexOf(const AMethod: TEvent): Integer; overload;
      protected
        procedure InternalAdd;
        procedure InternalRemove;
        procedure InternalIndexOf;
        procedure InternalSetDispatcher;
    
      public
        constructor Create;
        destructor Destroy; override;
    
      end;
    
      TMulticastEvent = class(TMulticastEvent)
      strict private
        FInvoke: T;
        procedure SetEventDispatcher(var ADispatcher: T; ATypeData: PTypeData);
      public
        constructor Create;
        procedure Add(const AMethod: T); overload;
        procedure Remove(const AMethod: T); overload;
        function IndexOf(const AMethod: T): Integer; overload;
    
        property Invoke: T read FInvoke;
      end;
    
    implementation
    
    { TMulticastEvent }
    
    procedure TMulticastEvent.Add(const AMethod: TEvent);
    begin
      FHandlers.Add(TMethod(AMethod))
    end;
    
    constructor TMulticastEvent.Create;
    begin
      inherited;
      FHandlers := TList.Create;
    end;
    
    destructor TMulticastEvent.Destroy;
    begin
      ReleaseMethodPointer(FInternalDispatcher);
      FreeAndNil(FHandlers);
      inherited;
    end;
    
    function TMulticastEvent.IndexOf(const AMethod: TEvent): Integer;
    begin
      result := FHandlers.IndexOf(TMethod(AMethod));
    end;
    
    procedure TMulticastEvent.InternalAdd;
    asm
      XCHG  EAX,[ESP]
      POP   EAX
      POP   EBP
      JMP   Add
    end;
    
    procedure TMulticastEvent.InternalIndexOf;
    asm
      XCHG  EAX,[ESP]
      POP   EAX
      POP   EBP
      JMP   IndexOf
    end;
    
    procedure TMulticastEvent.InternalInvoke(Params: PParameters; StackSize: Integer);
    var
      LMethod: TMethod;
    begin
      for LMethod in FHandlers do
      begin
        // Check to see if there is anything on the stack.
        if StackSize > 0 then
          asm
            // if there are items on the stack, allocate the space there and
            // move that data over.
            MOV ECX,StackSize
            SUB ESP,ECX
            MOV EDX,ESP
            MOV EAX,Params
            LEA EAX,[EAX].TParameters.Stack[8]
            CALL System.Move
          end;
        asm
          // Now we need to load up the registers. EDX and ECX may have some data
          // so load them on up.
          MOV EAX,Params
          MOV EDX,[EAX].TParameters.Registers.DWORD[0]
          MOV ECX,[EAX].TParameters.Registers.DWORD[4]
          // EAX is always "Self" and it changes on a per method pointer instance, so
          // grab it out of the method data.
          MOV EAX,LMethod.Data
          // Now we call the method. This depends on the fact that the called method
          // will clean up the stack if we did any manipulations above.
          CALL LMethod.Code
        end;
      end;
    end;
    
    procedure TMulticastEvent.InternalRemove;
    asm
      XCHG  EAX,[ESP]
      POP   EAX
      POP   EBP
      JMP   Remove
    end;
    
    procedure TMulticastEvent.InternalSetDispatcher;
    asm
      XCHG  EAX,[ESP]
      POP   EAX
      POP   EBP
      JMP   SetDispatcher;
    end;
    
    procedure TMulticastEvent.Remove(const AMethod: TEvent);
    begin
      FHandlers.Remove(TMethod(AMethod));
    end;
    
    procedure TMulticastEvent.SetDispatcher(var AMethod: TMethod;
      ATypeData: PTypeData);
    begin
      if Assigned(FInternalDispatcher.Code) and Assigned(FInternalDispatcher.Data) then
        ReleaseMethodPointer(FInternalDispatcher);
      FInternalDispatcher := CreateMethodPointer(InternalInvoke, ATypeData);
      AMethod := FInternalDispatcher;
    end;
    
    { TMulticastEvent }
    
    procedure TMulticastEvent.Add(const AMethod: T);
    begin
      InternalAdd;
    end;
    
    constructor TMulticastEvent.Create;
    var
      MethInfo: PTypeInfo;
      TypeData: PTypeData;
    begin
      MethInfo := TypeInfo(T);
      TypeData := GetTypeData(MethInfo);
      inherited Create;
      Assert(MethInfo.Kind = tkMethod, 'T must be a method pointer type');
      SetEventDispatcher(FInvoke, TypeData);
    end;
    
    function TMulticastEvent.IndexOf(const AMethod: T): Integer;
    begin
      InternalIndexOf;
    end;
    
    procedure TMulticastEvent.Remove(const AMethod: T);
    begin
      InternalRemove;
    end;
    
    procedure TMulticastEvent.SetEventDispatcher(var ADispatcher: T;
      ATypeData: PTypeData);
    begin
      InternalSetDispatcher;
    end;
    
    end.
    

提交回复
热议问题