Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi TNotifyEvent Objektbezogen in Variable speichern #2 (https://www.delphipraxis.net/165785-tnotifyevent-objektbezogen-variable-speichern-2-a.html)

berens 16. Jan 2012 13:17

Delphi-Version: 2010

TNotifyEvent Objektbezogen in Variable speichern #2
 
Hallo zusammen!

Ich hoffe, es ist laut den Regeln wunschgemäß, für eine weiterführende Frage zum selben Thema nach solch langer Zeit einen neuen Thread anzufangen.

Dabei beziehe ich mich hier auf mein vorhergehendes Thema http://www.delphipraxis.net/139677-t...speichern.html , bei dem jetzt doch (immer noch) ein Problem aufgetreten ist.

In dem unten aufgeführen Beispiel geht das darum, dass alle dynamisch erzeuten TMeinButton "in einem Rutsch" gedrückt werden sollen. Da sich die ausgeführten Prozeduren "Pressed" bzw. "OnClick" -trotz gleicher Komponente- von Button zu Buton unterschiedlich verhalten können (je nach Variable etc.), will ich natürlich in diesem Beispiel jeden Button genau einmal drücken, und nicht einfach nur das TMeinButton.Pressed Event 2x auslösen.

Die Buttons verstehen sich hier bitte nur als Sinnbild für viele verschiedene Komponenten, die alle eine TNotifyEvent Prozedur anbieten. Da ich bei Register nur das TNotifyEvent der Instanz einer Komponente übergebe, hoffe ich ansich, dass das auch funktioniert.

Ich könnte mir denken, dass die korrekte Implementierung nur und ausschließlich sich über Interfaces durchführen lässt, aber aus meiner laienhafen Sicht halte ich persönlich das für mit Kanonen nach Spatzen geschossen.

Da dieses Beispiel generell funktioniert, denke ich, dass das Problem wirklich mit dem Pointer-Problem zu tun hat:
"Blup" schrieb in seinem letzten Beitrag
Zitat:

Auch in der Funktion "IndexOfEvent" hat das @ nichts zu suchen.
Wenn ich beide @ dort entferne, kompiliert er nicht: "if THAL_ObserverItem(FListSubscriber[i]).Event = _Event then" --> "[DCC Fehler] Unit2.pas(121): E2035 Nicht genügend wirkliche Parameter"


Also mal angenommen, meine Ziele lassen sich generell mit dieser Technik erreichen, gibt es einige Fragen und Probleme:
1) Wenn ich RegisterEvent(b.Pressed) aufrufe, schlägt dies fehl, da IndexOfEvent(_Event) > -1 ( = also bereits vorhanden) ist. Darf ja so nicht sein.
2) Warum ist das so?
3) Wie korrigiert man das?


Delphi-Quellcode:
unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, contnrs;

// Aufgabenstellung: Alle dynamisch erzeuten TMeinButton sollen "in einem Rutsch" gedrückt werden

type
  // Sinnbild für irgendeine Komponente, die zurückgerufen werden soll
  // In diesem Fall soll einfach der Button gedrückt werden
  TMeinButton = class(TButton)
  public
    procedure Pressed(Sender: TObject);
  end;

  // Hilfsobjekt, um das TNotifyEvent zu speichern
  THAL_ObserverItem = class(TObject)
  public
    Event: TNotifyEvent;
  end;



  TForm2 = class(TForm)
    Button1: TButton; // dieser Button soll alle dynamisch erzeugten TMeinButton "in einem Rutsch" drücken
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    FListSubscriber: TObjectList; // hier werden die TNotifyEvent (über Hilfsobjekt THAL_ObserverItem) gespeichert
    function IndexOfEvent(_Event: TNotifyEvent): Integer; // wurde das Event bereits in der Liste gespeichert? < 0 bedeutet Nein; >= 0 bedeutet Ja
  public
    a, b: TMeinButton;
    procedure RegisterEvent(_Event: TNotifyEvent);
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}
{$o-}


procedure TMeinButton.Pressed(Sender: TObject);
begin
  // Einfach den Komponentennamen anzeigen, damit man weiß,
  // welche Instanz von TMeinButton gedrückt wurde
  ShowMessage(Name);
end;


procedure TForm2.FormCreate(Sender: TObject);
begin
  FListSubscriber := TObjectList.Create; // Die Liste für die Callback-Prozeduren

  a := TMeinButton.Create(Self);
  with a do begin
    Name := 'Button_A';
    Left := 10;
    Top := 10;
    Parent := Self;
    OnClick := a.Pressed; // Beim Klick erscheint also "Button_A"
  end;

  b := TMeinButton.Create(Self);
  with b do begin
    Name := 'Button_B';
    Left := 10;
    Top := 100;
    Parent := Self;
    OnClick := b.Pressed; // Beim Klick erscheint also "Button_B"
  end;

  RegisterEvent(a.Pressed);
  RegisterEvent(b.Pressed);
end;


procedure TForm2.RegisterEvent(_Event: TNotifyEvent);
var
  tmp: THAL_ObserverItem;
begin
  // Mein Ziel: Wenn von GENAU DIESEM Object (a.Pressed, b.Pressed),
  // GENAU DIESES Event noch nicht in der Liste ist,
  // dann der Liste hinzufügen
  if IndexOfEvent(_Event) = -1 then begin
    tmp := THAL_ObserverItem.Create; // Hilfsobjekt erzeugen
    tmp.Event := _Event;             // Callback-Prozedur übergeben
    FListSubscriber.Add(tmp);        // Hilfsobjekt in Liste aufnehmen
  end;
end;


procedure TForm2.Button1Click(Sender: TObject);
var
  i: integer;
begin
  // Alle Subscriber benachrichtigen
  for i := 0 to FListSubscriber.Count - 1 do begin
    THAL_ObserverItem(FListSubscriber.Items[i]).Event(Self);
  end;

  // In meinem Beispiel ist mein Ziel dass das dann später so abläuft:
  // a.Pressed(Self);
  // b.Pressed(Self);
end;

function TForm2.IndexOfEvent(_Event: TNotifyEvent): Integer;
var
  i: Integer;
begin
  Result := -1;
  // Siehe Oben: Mein Ziel: Wenn von GENAU DIESEM Object (a.Pressed, b.Pressed),
  // GENAU DIESES Event gefunden wird, dann gebe dessen Index zurück

  // wird bei RegisterEvent(a.Pressed) NICHT durchlaufen, da hier FListSubscriber.Count noch 0 ist!
  for i := 0 to FListSubscriber.Count - 1 do begin
    if @THAL_ObserverItem(FListSubscriber[i]).Event = @_Event then
      // ^-- HIER ist das Hauptproblem: Sei _Event = b.Pressed ergibt der Vergleich True, obwohl bisher nur a.Pressed in der Liste ist!
    begin
      Result := i;
      Exit;
    end;
  end;
end;

end.
Vielen Dank für Eure Hilfe!

einbeliebigername 16. Jan 2012 17:38

AW: TNotifyEvent Objektbezogen in Variable speichern #2
 
Hallo,

ich habe bis heute Ereignisse auch mit
Delphi-Quellcode:
@Ereigniss1= @Ereigniss2
verglichen. Ich glaube das stand sogar mal in der Hilfe zu Delphi so drin. Jedenfalls wird/wurde es so auch in der VCL gemacht, wie man an einem Auszug von Controls.pas aus Delphi 2007 sehen kann:
Delphi-Quellcode:
function TControlActionLink.IsOnExecuteLinked: Boolean;
begin
  Result := inherited IsOnExecuteLinked and
    (@FClient.OnClick = @Action.OnExecute);
end;
Aber irgendwie will das nicht mehr.

In Delphi XE sieht die obige Funktion so aus.
Delphi-Quellcode:
function TControlActionLink.IsOnExecuteLinked: Boolean;
begin
  Result := inherited IsOnExecuteLinked and ((
{$IF DEFINED(CLR)}

    (not Assigned(FClient.OnClick)) and (not Assigned(Action.OnExecute))) or
     (Assigned(FClient.OnClick) and
{$IFEND}
      DelegatesEqual(@FClient.OnClick, @Action.OnExecute)));
end;
Wobei DelegatesEqual so aus sieht:
Delphi-Quellcode:
function DelegatesEqual(A, B: Pointer): Boolean;
begin
  Result := A = B;
end;
Das funktioniert in deinem Fall aber auch nicht. :?: Die Hilfe zu DelegatesEqual ist ja auch interessant:
Zitat:

DelegatesEqual überprüft, ob zwei Delegaten gleich sind.

DelegatesEqual wird aus Gründen der Kompatibilität mit .NET bereitgestellt. DelegatesEqual hat in Win32-Anwendungen keinen echten Nutzen.
Wird aber bei Win32 kräftig benutzt.

So ein Ereignis ist ja ein Methodenzeiger, der aus zwei Zeigern besteht. Einer zeigt auf die Methode in der Klasse und der andere auf das Objekt, unter dessen Kontext die Methode aufgerufen werden soll. Im Hintergrund wird das über den Type TMethod behandelt, der so aussieht:
Delphi-Quellcode:
  TMethod = record
    Code, Data: Pointer;
  end;
Der Vergleich
Delphi-Quellcode:
@Ereigniss1= @Ereigniss2
scheint nur den ersten Zeiger zu vergleichen, der bei dir ja gleich ist, und den Zweiten zu ignorieren.

Wenn man in deinem Fall den Vergleich so macht:
Delphi-Quellcode:
(TMethod(THAL_ObserverItem(FListSubscriber[i]).Event).Data = TMethod(_Event).Data) and (TMethod(THAL_ObserverItem(FListSubscriber[i]).Event).Code = TMethod(_Event).Code)
funktioniert das unter Delphi 2007 und XE. Die Frage ist bloß, funktioniert das auch ab XE2 unter jeder Plattform so und wie muss man das unter .NET machen? Die nächste Frage wäre, wieso bei TMethod die Vergleichsoperatoren Equal und NotEqual nicht überschrieben wurden, damit folgendes reichen würde?
Delphi-Quellcode:
TMethod(THAL_ObserverItem(FListSubscriber[i]).Event) = TMethod(_Event)
Ich habe auch bei XE getestet ob der Compiler-Schalter
Delphi-Quellcode:
{$T}
Auswirkungen hat, was nicht der Fall ist.

@All: Nun die Preisfrage, wieso wird in der VCL an entscheidenden Stellen Ereignisse mittels @ verglichen?

Einbeliebigername.

berens 16. Jan 2012 21:28

AW: TNotifyEvent Objektbezogen in Variable speichern #2
 
Scheint auf den ersten Blick einwandfrei zu funktionieren. Vielen Dank erstmal hierfür :)

Das mit
Delphi-Quellcode:
 TMethod = record
    Code, Data: Pointer;
  end;
wurde ja schon im vorhergehenden Thread glaube ich erwähnt, aber das habe ich jetzt nicht weiter beachtet, denn:

Wenn mein "Event" ja in Wirklichkeit (nur) ein Pointer auf eine Instanz von TMethod ist, müssen die Pointer von a.Pressed und b.Pressed ja zwangsläufig verschiedene Speicherbereiche (also PointerAdressen) haben, da beide auf ein anderes Objekt verweisen. Sonst würde ja im regulären Programmablauf b.Pressed tatsächlich a.Pressed aufrufen.

Als muss irgendwas mit dem @ nicht stimmen. Ich habe zwar schon mehrere Tutorials zu Pointer gelesen, aber gerade auch wieder aufgrund der aktuellen Problematik blicke ich da (wieder) nicht durch.

Ich könnte mir denken, dass der einfache @ Vergleich nur dann zulässig ist (bzw. funktioniert) denn der Pointer auf eine einfache Variable verweist (Int, String, ...).

Wenn mir jetzt einfach jemand sagt, dass @Pointer oder Klassen(de?)referenz^ mit Dach^ eh nur noch Überbleibsel aus früheren Zeiten sind, die in aktuellen Programmen gar nichts mehr verloren haben, wäre ich Euch echt sehr dankbar! :lol:

Vielen Dank für Hilfe, Thema einstweilen erledigt. :thumb:
N8

einbeliebigername 17. Jan 2012 00:37

AW: TNotifyEvent Objektbezogen in Variable speichern #2
 
Hallo,

Zitat:

Zitat von berens (Beitrag 1146243)
Wenn mein "Event" ja in Wirklichkeit (nur) ein Pointer auf eine Instanz von TMethod ist

Ne, ein Event ist direkt das TMethod. Habe zur Veranschaulichung deine Methode um par Debug-Hilfen ergänzt:
Delphi-Quellcode:
function TForm1.IndexOfEvent(const _Event: TNotifyEvent): Integer;
var
  i: Integer;
  a: Integer;
  E: TNotifyEvent;
  b: Integer;
begin
  Result := -1;
  // Siehe Oben: Mein Ziel: Wenn von GENAU DIESEM Object (a.Pressed, b.Pressed),
  // GENAU DIESES Event gefunden wird, dann gebe dessen Index zurück

  // wird bei RegisterEvent(a.Pressed) NICHT durchlaufen, da hier FListSubscriber.Count noch 0 ist!
  for i := 0 to FListSubscriber.Count - 1 do begin
    a:= SizeOf(TNotifyEvent);
    E:= THAL_ObserverItem(FListSubscriber[i]).Event;
    b:= SizeOf(TMethod);
    OutputDebugString(PChar(Format('TMethod(T.Event).Code= %p; TMethod(T.Event).Data= %p; @E= %p; Int64(@E)= %d; @@E= %p; SizeOf(TNotifyEvent)= %d; SizeOf(TMethod)= %d; @a= %p; @b= %p; @i= %p ', [TMethod(E).Code, TMethod(E).Data, @E, Int64(@E), @@E, a, b, @a, @b, @i])));
    if (TMethod(THAL_ObserverItem(FListSubscriber[i]).Event).Data = TMethod(_Event).Data) and (TMethod(THAL_ObserverItem(FListSubscriber[i]).Event).Code = TMethod(_Event).Code) then
      // ^-- HIER ist das Hauptproblem: Sei _Event = b.Pressed ergibt der Vergleich True, obwohl bisher nur a.Pressed in der Liste ist!
    begin
      Result := i;
      Exit;
    end;
  end;
end;
Raus kommt dann das:
Code:
Debug-Ausgabe: TMethod(T.Event).Code= 004CBE40; TMethod(T.Event).Data= 028D3870; @E= 004CBE40; Int64(@E)= 5029440; @@E= 0018FE5C; SizeOf(TNotifyEvent)= 8; SizeOf(TMethod)= 8; @a= 0018FE4C; @b= 0018FE48; @i= 0018FE50  Prozess dp165785Project1.exe (10976)
Da sieht man, dass eine Variable vom Typ TNotifyEvent bereits so groß ist wie zwei Pointer. Bei dem @ und Methoden-Zeigern greift wiedermal ein wenig Compiler-Magie. Eigentlich gibt das @ die Adresse des dahinter stehenden Ausdrucks zurück. Bei @a bekommt man im obigen Beispiel die Adresse, wo die Variable a auf dem Stack liegt, zurück. Bei Methoden-Zeigern gibt das @ den Inhalt der Variable zurück. Die Adresse der Variable bekommt man erst mit einem @@ zurück. Wieso jetzt aber das einfache @ bei Methoden-Zeigern nur die Hälfte des Inhaltes wiedergibt, bleibt mir ein Rätsel.

Leider habe ich gerade kein lauffähiges Delphi 7 zur Verfügung. Denn ich bin der Meinung das der Vergleich mit dem @ da noch funktioniert hat. Vielleich sind ja andere so nett und Testen das mal mit anderen Versionen. Ich habe das mit Delphi 2007, XE und jetzt sogar mit XE2 Update 3 in 32 und 64bit getestet. Immer mit dem gleichen Ergebnis. Nur das sich bei 64bit die Größen verdoppeln. Und da liegt vermutlich auch das Problem. Laut Hilfe von XE2 gibt es unter 64bit kein 128bit großen Datentyp äquivalent zu Int64 bei 32bit, so das gar nicht beide Pointer bei dem @ zurückgegeben werden können.

Auch TControlActionLink.IsOnExecuteLinked und DelegatesEqual sehen bei XE2 so aus wie bei XE.

Einbeliebigername.


Alle Zeitangaben in WEZ +1. Es ist jetzt 03:18 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz