Event<T> Dont remove reference to procedure.

Issue #146 invalid
Michel Moreira created an issue

Hi,

Using a reference to procedure as the event type, the method Remove don't remove the event handler. No issues firing and invoking the added events.

Using procedure ... of object the removal works fine, but it doesn't work for anonymous methods.

Comments (5)

  1. Stefan Glienke repo owner

    Show some code please - I have a guess why it does not work.

    To explain what my guess is - I assume you have code that looks like this:

    program Project1;
    
    {$APPTYPE CONSOLE}
    
    uses
      Spring,
      SysUtils;
    
    type
      {$M+}
      TMyProc = reference to procedure;
    
    var
      e: Event<TMyProc>;
    
    procedure MyHandler;
    begin
      Writeln('this should not be shown');
    end;
    
    procedure AddHandler;
    begin
      e.Add(MyHandler);
    end;
    
    procedure RemoveHandler;
    begin
      e.Remove(MyHandler);
    end;
    
    begin
      try
        AddHandler;
        RemoveHandler;
    
        e.Invoke();
    
      except
        on E: Exception do
          Writeln(E.ClassName, ': ', E.Message);
      end;
      Readln;
    end.
    

    Now to answer the question why it does not remove the procedure again we have to look at how the compiler actually implements this.

    It basically does this when adding:

    e.Add(procedure begin MyHandler end);
    

    and the same for the Remove call. So the anonymous methods created inside the AddHandler and RemoveHandler are different. You can actually see that in the debugger when stopping inside of MyHandler:

    Project1.MyHandler
    Project1.AddHandler$xx$ActRec.$0$Body // <- this is the compiler generated anonymous method that wraps the call to MyHandler
    Spring.Events.InvokeMethod(???,???,???)
    

    Now if you step into the Event<T>.Remove method you can see this in the local variables:

    handler = RemoveHandler$xx$ActRec($xxxxxxxx) as TMyProc
    
  2. Michel Moreira reporter

    No, isn't that.

    Something like:

      TMyObject = class
      public
         procedure Handler;
      end;
    
      x := TMyObject.Create;
      e.Add(x.handler);
      e.Invoke();
      e.Remove(x.handler);
      x.Free;
    

    It only works if I make

      TMyProc = procedure of object;
    

    But when I make that, I cannot do anymore:

    e.Add(procedure begin MyHandler end);
    

    I agree and know what you explained, but it isn't the issue.

  3. Stefan Glienke repo owner

    Yes, it is exactly that problem just that it even appears within the same routine.

    Look at the generated asm:

    Project1.dpr.32: e.Add(x.handler);
    004D986F 8B1548344E00     mov edx,[$004e3448]
    004D9875 85D2             test edx,edx
    004D9877 7403             jz $004d987c
    004D9879 83EAF0           sub edx,-$10 // <-
    004D987C B844344E00       mov eax,$004e3444
    004D9881 E8FEE7FFFF       call {Spring}Event<Project1.TMyProc>.Add
    Project1.dpr.33: e.Invoke();
    004D9886 8D55EC           lea edx,[ebp-$14]
    004D9889 B844344E00       mov eax,$004e3444
    004D988E E829E7FFFF       call {Spring}Event<Project1.TMyProc>.GetInvoke
    004D9893 8B45EC           mov eax,[ebp-$14]
    004D9896 8B10             mov edx,[eax]
    004D9898 FF520C           call dword ptr [edx+$0c]
    Project1.dpr.34: e.Remove(x.handler);
    004D989B 8B1548344E00     mov edx,[$004e3448]
    004D98A1 85D2             test edx,edx
    004D98A3 7403             jz $004d98a8
    004D98A5 83EAF4           sub edx,-$0c // <-
    004D98A8 B844344E00       mov eax,$004e3444
    004D98AD E8FAE7FFFF       call {Spring}Event<Project1.TMyProc>.Remove
    

    Step into Add and Remove and see for yourself that the passed values differ. If you want to use it that way then assign x.Handler to a local variable of the anonymous method type and Add/Remove that one.

  4. Michel Moreira reporter

    Got it.

    It appears to be a bug on delphi side then, because it is the same method, and it works correctly if you change the type to "procedure of object".

    It is like the compiler is generating:

    e.Add(procedure begin x.Handler end);
    

    What is different to what we wrote.

  5. Log in to comment