Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Object-Pascal / Delphi-Language (https://www.delphipraxis.net/32-object-pascal-delphi-language/)
-   -   Delphi Speicherleck bei der Verwendung von anonymen Methoden (https://www.delphipraxis.net/163779-speicherleck-bei-der-verwendung-von-anonymen-methoden.html)

carlo93 15. Okt 2011 22:40

Delphi-Version: XE

Speicherleck bei der Verwendung von anonymen Methoden
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo!

Folgendes Problem:

Bei der Verwendung dieses Quelltextes entsteht ein Speicherleck:
Delphi-Quellcode:
type
  TForm1 = class(TForm)
    Memo1: TMemo;
    Panel1: TPanel;
    Button1: TButton;
    Edit1: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    FProc: TProc;
  public
    { Public-Deklarationen }
  end;

procedure TForm1.FormCreate(Sender: TObject);
var
  Func: TFunc<String>;
begin
  ReportMemoryLeaksOnShutdown:=true;
  Func:=
    function: String
    begin
      Result:=Edit1.Text;
    end;
  Memo1.Lines.Add(Func);
  FProc:=
    procedure
    begin
      Memo1.Lines.Add(Func);
    end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  FProc;
end;
Anhang 35373

Momentan kann ich leider nicht überprüfen, ob das auch bei XE2 auftritt (meine Testversion ist gerade abgelaufen).
Gibt es eine Möglichkeit, dieses Speicherleck zu verhindern?

jaenicke 16. Okt 2011 09:38

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Umgehen lässt sich das Problem leicht:
Mach aus Func auch ein privates Feld statt einer lokalen Variable.

Schöner ist aber wohl diese Lösung:
Delphi-Quellcode:
procedure TForm142.FormCreate(Sender: TObject);

  procedure DoInit(Func: TFunc<String>);
  begin
    Memo1.Lines.Add(Func);
    FProc :=
      procedure
      begin
        Memo1.Lines.Add(Func);
      end;
  end;

begin
  ReportMemoryLeaksOnShutdown := true;
  DoInit(function: String
    begin
      Result := Edit1.Text;
    end);
end;

himitsu 16. Okt 2011 09:49

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Da wird wohl die eingebetette Reference nicht mehr freigegeben.

Am Generic liegt es nicht, dann wenn man
Delphi-Quellcode:
TFunc<String>
durch einen richtigen Typen ersetzt, ändert sich nichts.


Lösungen:
- im QC melden und hoffen es wird eventuell irgendwann in den nächsten Jahrzehnten behoben
- auf verschachtelte Referencen verzichten
- oder Func ebenfalls als privates FFunc in der Form speichern [add] wie schonmal genannt
- diesen Fehler einfach ignorieren (wird ja eh nie behoben)
- schmutzige Tricks, um diesen Fehler provisiorisch zu umgehn *1




1)
Delphi-Quellcode:
IInterface(PPointer(@Func)^)._Release;
als letzen Befehl in FormCreate,
aber sollte dieser Fehler wirklich mal irgendwann behoben werden, dann raucht dir die Anwendung ab. :stupid:

jaenicke 16. Okt 2011 11:08

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Gerade getestet: Das Problem tritt auch bei XE2 noch auf.

Bernhard Geyer 16. Okt 2011 11:19

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Tritt das Speicherleck bei jeder verwendung auf oder einmalig beim ersten Aufruf?

jaenicke 16. Okt 2011 11:21

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Einmal, es wird ja schließlich die Funktion nicht freigegeben und die existiert nun einmal nur einmal...

// EDIT:
Bzw. wenn man den Code aus FormCreate mehrfach ausführt, wird natürlich bei jeder Ausführung ein Speicherleck produziert.

Stevie 16. Okt 2011 15:13

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Ist schon lange bekannt: QC #83259 und QC #78066.

Tip: Wenn ihr beschleunigen wollt, dass bestimmte Einträge gefixt werden, dann nutzt die Vote Funktion (man kann entgegen dem Irrglauben einiger pro Eintrag 10 Punkte vergeben).

carlo93 16. Okt 2011 15:29

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
@jaenicke

Vielen Dank für's Testen und die nette Antwort!

Zitat:

Zitat von Stevie (Beitrag 1130716)
Ist schon lange bekannt

@Stevie

Ich hatte nicht gefragt, ob das Problem bekannt ist, sondern
  1. ob das Problem unter XE2 auch noch auftritt und
  2. ob es dafür (schon) eine Lösung gibt.

Ohne einen überheblichen Ton kannst du wohl überhaupt nichts von dir geben? :wall:

Stevie 16. Okt 2011 15:48

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Zitat:

Zitat von carlo93 (Beitrag 1130719)
Ohne einen überheblichen Ton kannst du wohl überhaupt nichts von dir geben? :wall:

Ich habe lediglich darauf hingewiesen, dass das Problem bekannt und nicht gefixt ist.
Da der Eintrag nicht geclosed ist, tritt der Bug noch in XE2 auf (1.)
Wenn du dir die Einträge anschaust, siehst du woran das Problem liegt und auch Workarounds (2.)

Im übrigen wurde hier in diesem Thread darauf hingewiesen, einen QC Eintrag zu machen - daher habe ich mir die Mühe gemacht, das mal nachzuschauen.

Wenn du Sachlichkeit mit Überheblichkeit verwechselst, tut's mir leid für dich.

Thom 16. Okt 2011 17:10

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
@carlo93

Zitat:

Zitat von carlo93 (Beitrag 1130719)
Ohne einen überheblichen Ton kannst du wohl überhaupt nichts von dir geben?

Nö - das ist seine "nette" Art mit anderen umzugehen... Das findest Du in fast jedem seiner Beiträge. :zwinker:

Zu Deiner Frage:
Der Compiler speichert die von anonymen Methoden genutzen Variablen (Variablenbindung). Diese werden in TInterfacedObject-Derivaten mit Referenzzählung abgelegt.
Aus für mich nicht ersichtlichen Gründen besitzen diese Frames nach der Erstellung der anonymen Methode einen RefCount-Wert von 2. Nach Beendigung der Methode (in Deinem Fall TForm1.Create) wird dieser Wert wieder um zwei verringert und das Frame-Objekt aus dem Speicher entfernt. Tritt jetzt der Fall ein, das die anonyme Methode in einer anderen weiterverwendet wird, erhöht der Compiler RefCount des Frame-Objektes um eins und es wird beim Aufräumen am Ende der Methode nicht mehr gelöscht. So weit - so gut. Dummerweise verringert der Compiler in diesem Fall RefCount nur um eins und somit bleibt das Frame-Objekt auch zum Schluß erhalten. Meiner Meinung nach ist das ein Fehler im Compiler.
Da ich bei der Programmierung meines Frameworks auch auf dieses Problem gestoßen bin, habe ich dafür eine einfache Lösung entwickelt:
Delphi-Quellcode:
procedure TForm1.FormCreate(Sender: TObject);
var
  Func: TFunc<String>;
begin
  ReportMemoryLeaksOnShutdown:=true;
  Func:=
    function: String
    begin
      Result:=Edit1.Text;
    end;
  Memo1.Lines.Add(Func);
  FProc:=
    procedure
    begin
      Memo1.Lines.Add(Func);
    end;
  {$IF (CompilerVersion>=20) and (CompilerVersion<24)} //<- optimistisch ;-)
  if RefCount(Func)>2 //-> es existieren zusätzliche Referenzen
    then ReleaseMethod(Func); //<- RefCount wird um das fehlende Mal verringert
  {$IFEND}
end;
Zitat:

Zitat von himitsu (Beitrag 1130681)
Lösungen:
- [...]
- [...]
- [...]
- [...]
- schmutzige Tricks, um diesen Fehler provisiorisch zu umgehn *1

1)
Delphi-Quellcode:
IInterface(PPointer(@Func)^)._Release;
als letzen Befehl in FormCreate,
aber sollte dieser Fehler wirklich mal irgendwann behoben werden, dann raucht dir die Anwendung ab. :stupid:

Insofern entspricht das etwa himitsu's Lösung Nummer 5 mit vorherigem Test. :thumb:
Delphi-Quellcode:
  {$IF (CompilerVersion>=20) and (CompilerVersion<???)} 
  if IInterface(PPointer(@Func)^)._AddRef>3
    then IInterface(PPointer(@Func)^)._Release;
  IInterface(PPointer(@Func)^)._Release;
  {$IFEND}

FredlFesl 16. Okt 2011 17:30

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Zitat:

Zitat von carlo93 (Beitrag 1130719)
Ich hatte nicht gefragt...

So ziemlich jeder Beitrag liefert Antworten und Informationen jenseits deiner Fragen.

Folgerichtig wäre der einzig wahre Beitrag, bei dem Du nichts zu bemängeln hättest, dieser hier:
"Ja. Ja."

Zitat:

Zitat von carlo93 (Beitrag 1130719)
Ohne einen überheblichen Ton kannst du wohl überhaupt nichts von dir geben? :wall:

Ich glaub, ich brauch Nachhilfe. Ich find den 'überheblichen Ton' nämlich nicht.

carlo93 16. Okt 2011 18:30

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Danke himitsu und Thom! Das hilft mir weiter.

@Stevie
Danke für's Nachschauen. Dennoch macht für mich gerade der Ton die Musik. Ein "Ist schon lange bekannt" beinhaltet im Unterton ein "Ach - und dir nicht!? Wohl keine Ahnung von der Thematik?".
Auf eine sachlich gestellte Frage erwarte ich auch eine sachliche Antwort. Die meisten hier im Forum können das. Im Gegensatz dazu klingt nahezu jede Antwort von Dir überheblich. Schade - bei Deinem Wissen hättest Du das eigentlich nicht nötig.

Zum Thema "Workarounds"
  1. Report #: 83259 None
  2. Report #: 78066 Put anonymous method in function. (Entspricht Sebastian's Vorschlag vor Deinem Beitrag)

Beide Einträge betreffen übrigens "Version: 14.0". Dumme Frage: Gab's zu diesem Zeitpunkt schon anonyme Methoden?


Zitat:

Zitat von FredlFesl (Beitrag 1130726)
Folgerichtig wäre der einzig wahre Beitrag, bei dem Du nichts zu bemängeln hättest, dieser hier:
"Ja. Ja."

@FredlFesl
Sorry - aber das ist einfach dumm und keine sachliche Antwort wert.

Zitat:

Zitat von FredlFesl (Beitrag 1130726)
Ich glaub, ich brauch Nachhilfe. Ich find den 'überheblichen Ton' nämlich nicht.

Das ist durchaus möglich. Ich weiß nicht, wie alt Du bist - ich gehöre einer Generation an, die (noch) gelernt hat, seinem Gegenüber eine entsprechende Portion an Respekt und Achtung entgegenzubrigen. Und zwar unabhängig vom Alter, Geschlecht und Wissensstand. So etwas scheint aber leider immer mehr aus der Mode zu kommen. Wer einen derartigen Ton für angemessen hält, kann diesen gern unter denen anwenden, die das für normal halten - bei mir bitte nicht.

Stevie's Post brachten - außer mehreren Belehrungen - keinen Beitrag zur Lösung des Problems. Wozu also das Ganze?
Das es ganz anders geht, beweisen die Beiträge von Sebastian, himitsu und Thom. Nochmals vielen Dank an die drei! :thumb:

Damit beende ich die Off-Topic-Diskussion meinerseits. Wer noch Bemerkungen dazu hat - bitte per PM.

Stevie 16. Okt 2011 18:48

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Edit: egal...

FredlFesl 16. Okt 2011 20:06

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Zitat:

Zitat von carlo93 (Beitrag 1130733)
Sorry - aber das ist einfach dumm und keine sachliche Antwort wert.

Es entspricht dem, was Du erwartest. Antworten auf deine Fragen und sonst nix.
Zitat:

Das ist durchaus möglich.
Aha. Das dachte ich mir. Zwischen den Zeilen kann man übrigens alles lesen. Das lehrt mich meine Lebenserfahrung. Und lass uns nicht über Alter und Generation reden. Das könnte nach hinten losgehen ;-)
Zitat:

Stevie's Post brachten - außer mehreren Belehrungen - keinen Beitrag zur Lösung des Problems.
Doch. Aber man muss klicken und selbst im QC nachlesen. Das könnte natürlich überheblich sein ;-)

Überheblich könnte übrigens auch der sein, der frisch in einem Forum ist und gleich rumkoffert.

BUG 17. Okt 2011 08:36

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Ja, manche Mitglieder sind manchmal etwas unhöflich oder streitlustig.
Wenn du Stevies ersten Betrag anstößig findest solltest du dir aber ein dickeres Fell zulegen. Durch solche Reaktionen wie von dir eskalieren solche Situationen häufig und damit geht dir eine Menge Antworten, vernünftiger Diskussion und letztlich Wissen verloren.
Und: die Links zu den QC-Einträgen sind wertvolle Informationen und wenn nicht dir, dann helfen sie vielleicht später jemand anderem der diesen Thread liest.

Mithrandir 17. Okt 2011 11:24

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Sehe ich genau so. Und nu' kommt mal wieder auf den Boden der Tatsachen zurück. Vor dem nächsten Posting einfach dreimal durchatmen und sich fragen, ob das die Sache wirklich wert ist.

WladiD 17. Okt 2011 11:55

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Zurück zum Thema. Soweit ich mich entsinnen kann, reicht es aus, wenn die Referenz der anonymen Methode explizit auf nil gesetzt wird.

Also in deinem Fall irgendwo im Destruktor:

Delphi-Quellcode:
FProc := nil;


Und der Leak dürfte verschwinden.

Stevie 17. Okt 2011 13:24

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Zitat:

Zitat von WladiD (Beitrag 1130831)
Zurück zum Thema. Soweit ich mich entsinnen kann, reicht es aus, wenn die Referenz der anonymen Methode explizit auf nil gesetzt wird.

Also in deinem Fall irgendwo im Destruktor:

Delphi-Quellcode:
FProc := nil;


Und der Leak dürfte verschwinden.

Leider nicht, wie Thom oben schon richtig anmerkte, läuft da irgendwas bei der Code Generierung schief, was dazu führt, dass der RefCount des hinter der anonymen Methode liegenden Objects falsch gezählt wird. Das auf nil setzen geschieht auf jeden Fall implizit. Das ganze (einmal zu oft _AddRef oder einmal zu wenig _Release, wie man will) passiert beim Zusammenspiel von lokaler Variable und verschachtelter anonymer Methode (da wird zu Beginn ein _IntfCopy gemacht, was zu dem erhöhten RefCount führt)

Zusätzlich zu den in den QC Einträgen wäre der simpelste Testcase, der mir gerade einfällt, folgender:
Delphi-Quellcode:
program Test;

{$APPTYPE CONSOLE}

uses
  SysUtils;

type
  TTest = class
  private
    FProc: TProc;
  public
    constructor Create;
  end;

constructor TTest.Create;
var
  Proc: TProc;
begin
  Proc := procedure begin end;
  FProc := procedure begin end;
end;

begin
  ReportMemoryLeaksOnShutdown := True;
  TTest.Create.Free;
end.

Thom 18. Okt 2011 23:04

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Noch eine Merkwürdigkeit im Zusammenhang mit anonymen Methoden:

Eine anonyme Methode ist nach außen hin nur ein einfacher Pointer. Ein SizeOf(TProc) liefert also unter 32 Bit erwartungsgemäß 4 Byte. Im Gegensatz dazu bestehen Objekt-Methoden aus einem Daten- und einem Code-Pointer. Deshalb liefert SizeOf(TMethod)=8 (unter 32 Bit).

Jetzt sollte man natürlich annehmen, daß der doppelt so große Wert von TMethod nicht zuweisungskompatibel zum "kleineren" TProc ist.
Entgegen jeder Logik funktioniert das aber doch: Der Compiler generiert bei einer derartigen Zuweisung einfach eine anonyme Methode und bindet die Variable Self, die zu der Objekt-Methode gehört.
Ich finde dieses Verhalten genial, da damit Ereignishandler wahlweise Objekt- oder anonyme Methoden sein können.

Ein kleines Beispiel:
Delphi-Quellcode:
type
  TNotifyProc = TProc<TObject>; //anstelle von TNotifyEvent

  TMyObject = class
  private
    FValue: Integer;
    FOnChange: TNotifyProc;
    procedure SetValue(Value: Integer);
  public
    property Value: Integer read FValue write SetValue;
    property OnChange: TNotifyProc read FOnChange write FOnChange;
  end;

  TForm1 = class(TForm)
  [...]
  private
    procedure MyObjectChange(Sender: TObject);
  end;

procedure TMyObject.SetValue(Value: Integer);
begin
  if FValue<>Value then
  begin
    FValue:=Value;
    if assigned(FOnChange)
      then FOnChange(Self);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  MyObject: TMyObject;
begin
  MyObject:=TMyObject.Create;
  try
    MyObject.OnChange:=MyObjectChange;
    MyObject.Value:=1;
    MyObject.OnChange:=procedure(Sender: TObject)
                       begin
                         ShowMessage(IntToStr(TMyObject(Sender).Value));
                       end;
    MyObject.Value:=2;
  finally
    MyObject.Free;
  end;
end;

procedure TForm1.MyObjectChange(Sender: TObject);
begin
  ShowMessage(IntToStr(TMyObject(Sender).Value));
end;

himitsu 18. Okt 2011 23:48

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Referenzen bestehen intern aus einem geheimgehalten Interface.
Und ein Interfacezeiger ist nunmal auch nur ein Pointer.

Schade eigentlich, denn durch die Geheimhaltung und mangels entsprechender öffentlicher Methoden kann man Referenzen dadurch nicht vergleichen, bzw. prüfen was sich darin befindet.


Wobei die Referenzen keine Methoden als anonyme Methoden "binden", sondern sie können einfachen alles "aufnehmen" ... vermutlich besitzen sie intern Speicher (eventuell überladen) und eine Typ-Variable, welche die Art des aufgenomenen "Zeigers" angibt. Bei einem Aufruf wird dann einfach der entsprechende Typ ausgewertet und aufgerufen.


Delphi-Quellcode:
property OnChange: TNotifyProc read FOnChange write FOnChange;
.
Hierfür wollte ich mir mal eine TList<> erstellen, welche mehrere Events verwalten kann, aber leider war es mir nicht mehr möglich einmal registrierte Events "geziehlt" wieder aus der Liste zu entfernen. :cry:

PS: Im Zusammenhang mit anonymen Methoden, nimmt diese Referenz auch noch ganze Sätze von geshareten Variablenzeigern mit in sich auf.

Delphi-Quellcode:
    MyObject.OnChange:=procedure(MySender: TObject)
                       begin
                         ShowMessage(MySender.ClassName + ' ' + IntToStr(TMyObject(MySender).Value) + ' ' + Sender.ClassName);
                       end;

Stevie 19. Okt 2011 07:16

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Zitat:

Zitat von himitsu (Beitrag 1131175)
Referenzen bestehen intern aus einem geheimgehalten Interface.
Und ein Interfacezeiger ist nunmal auch nur ein Pointer.

Schade eigentlich, denn durch die Geheimhaltung und mangels entsprechender öffentlicher Methoden kann man Referenzen dadurch nicht vergleichen, bzw. prüfen was sich darin befindet.

Anonyme Methoden sind ein Interface mit einer Invoke Methode, welche die passenden Parameter hat.
Das kann man testen, indem man einfach das hier schreibt
Delphi-Quellcode:
type
  TProcObject = class(TInterfacedObject, TProc)
  public
    procedure Invoke;
  end;
Weiterhin kann man über RTTI auf das Object, was dahinter steckt, zugreifen (siehe dieser Artikel).

Vergleichen und in einer Liste speichern müsste somit auch kein Problem sein. Man kann auch andersrum eine anonyme Methode an ein Event hängen (man muss sich nur klar machen, dass ein Event kein managed type ist, dementsprechend muss man sich da dann selber drum kümmern) Wie das geht, hat Barry in diesem Artikel beschrieben.

Mit diesem Wissen kann man eigentlich fast alles mit einer anonymen Methode machen.

himitsu 19. Okt 2011 09:39

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Delphi-Quellcode:
unit Unit1;

interface

uses
  SysUtils, Classes, Graphics, Controls, Forms,
  Dialogs, Generics.Defaults, Generics.Collections, StdCtrls;

type
  TCallback = TProc<TObject>;
  TForm1 = class(TForm)
    RegisterButton: TButton;
    UnregisterButton: TButton;
    CallButton: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure RegisterButtonClick(Sender: TObject);
    procedure UnregisterButtonClick(Sender: TObject);
    procedure CallButtonClick(Sender: TObject);
    procedure MyCallback(Sender: TObject);
  private
    FList: TList<TCallback>;
    procedure RegisterCallback(CB: TCallback);
    procedure UnregisterCallback(CB: TCallback);
    procedure DoCallbacks(Sender: TObject);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  FList := TList<TCallback>.Create;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FList.Free;
end;

procedure TForm1.RegisterButtonClick(Sender: TObject);
begin
  RegisterCallback(MyCallback);
end;

procedure TForm1.UnregisterButtonClick(Sender: TObject);
begin
  UnregisterCallback(MyCallback);
end;

procedure TForm1.CallButtonClick(Sender: TObject);
begin
  DoCallbacks(Self);
end;

procedure TForm1.MyCallback(Sender: TObject);
begin
  ShowMessage('Hallo');
end;

procedure TForm1.RegisterCallback(CB: TCallback);
begin
  if FList.IndexOf(CB) < 0 then
    FList.Add(CB);
end;

procedure TForm1.UnregisterCallback(CB: TCallback);
begin
  //if FList.IndexOf(CB) >= 0 then
  //  ShowMessage('ist drin');
  FList.Remove(CB);
end;

procedure TForm1.DoCallbacks(Sender: TObject);
var
  i: Integer;
begin
  for i := FList.Count - 1 downto 0 do
    FList[i](Sender);
end;

end.
Du kannst UnregisterCallback so oft aufrufen, wie du willst.
Bei jedem Aufruf wird ein neues Interface aus Callback erstellt.
Also kann dieser Callback nicht mehr in der Liste gefunden werden.


Aber danke für den Link, womöglich läßt sich das ja im einer schönen Vergleichsfunktion verbauen. :)

Stevie 19. Okt 2011 12:36

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Zitat:

Zitat von himitsu (Beitrag 1131210)
Aber danke für den Link, womöglich läßt sich das ja im einer schönen Vergleichsfunktion verbauen. :)

Leider nicht (zumindest nicht mit dem mir verfügbaren Wissen aus den beiden genannten Blogs). Ich weiß keine Möglichkeit, an den Code Pointer der gewrappten Methode zu kommen. Ich hab es geschafft an das gecapturte Self Feld und über die interface VMT an die Invoke Methode zu kommen. Diese ist aber unterschiedlich zwischen RegisterCallback und UnregisterCallback. Das einzige, was ich darüber verhindern konnte, war, dass man mehrfach die gleiche Methode registriert.

In dieser Hinsicht haben wir also dasselbe Problem, wie in C#. Dort wird als Lösung eine Variable vorgeschlagen. Wenn du dir also eine Feldvariable vom Typ TCallback machst und dort dein MyCallback zuweist, kannst du im weiteren Verlauf mit dieser Variable arbeiten und das von dir erwartete Verhalten bekommen (dass z.b. TList.Contains oder IndexOf funktioniert).

himitsu 19. Okt 2011 14:09

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
:cry:

Tja, wäre die Interfacedeklration öffentlich, und hätte Emba entsprechende (Vergleichs)Methoden verbaut,
oder hätte man den Vergleichsoperator entsprechend ausgelegt, dann gäbe es das Problem nicht.

Stevie 19. Okt 2011 16:05

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Zitat:

Zitat von himitsu (Beitrag 1131272)
:cry:

Tja, wäre die Interfacedeklration öffentlich, und hätte Emba entsprechende (Vergleichs)Methoden verbaut,
oder hätte man den Vergleichsoperator entsprechend ausgelegt, dann gäbe es das Problem nicht.

Wenn du $M+ setzt, kannst du dir die RTTI der anonymen Methode anschauen. Hilft dir nur nicht weiter bei dem Problem mit der gewrappten regulären Methode.
Ich werd mal Barry fragen, ob es eine Möglichkeit gibt, diese herauszufinden. Das ist atm das Wissen, was mit noch fehlt, um eine Vergleichsroutine zu bauen.

Stevie 20. Okt 2011 08:04

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Antwort von Barry:
Zitat:

When you assign a regular method to a method reference it is actually identical to writing an anonymous method that wraps the method call:

Delphi-Quellcode:
TFooType = reference to procedure(fooArg: TFooArg);
foo: TFooType;

foo := Self.Bar;

foo := procedure(fooArg: TFooArg)
  begin
    Self.Bar(fooArg);
  end;
This captures 'Self' - so you could theoretically find out the instance to which the method reference is referring to, using RTTI tricks - but you would need to disassemble the code of the anonymous method to get to the method itself. If the method is virtual or dynamic, it will be using a different calling mechanism than if it is non-virtual, and similarly it will be more or less obscured.

himitsu 4. Nov 2011 07:23

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Zitat:

Zitat von himitsu (Beitrag 1134417)
Nee, es geht einfach darum rauszufinden was darin ist ... man kann schlecht prüfen ob es sich um eine Referenz auf eine Prozedur, auf eine Methode, eine Klassenmethode oder eine anonyme Methode handelt und wenn, auf welche gezeigt wird
und dementsprechend kann man diese referenzen auch nicht direkt vergleichen. (außer man hat zwei gleiche Referenzen, aber eben nicht "zeigt Referenz auf meine Methode?")

Schade ist aber, daß
- dieses Interface wirklich nur 4 Funktionen, bzw. eine Funktion exportiert ... die Call-Methode, _AddRef, _Release, QueryInterface
(hätte ja sein können, daß es da noch geheime "Vergleichs-/Prüffunktionen" gibt)
- daß zu dem Interface zwar eine TypeInfo zu finden ist, aber dort nur deklariert ist, daß es keine näheren Infos (RTTI) zu den Methoden gibt, also weder Name noch Parameterliste (aber das läßt sich halbwegs leicht rausfinden)
Delphi-Quellcode:
methCnt := ReadU16(P); // 4 = 3 des Vorfahren + 1 Eigene
hasRtti := ReadU16(P); // $FFFF = hab nix weiter
- und das Objekt innerhalb dieses Interfaces besitzt nur ein Feld, das ebefalls nicht in der RTTI aufgelistet wird und welches (vermutlich) ein Pointer ist ... aber was genau in dem Pointer liegt, bleibt ebenfalls geheim und man kann nur Vermutungen anstellen.
Genauere Infos wären schon schön gewesen. :cry:
- Zu dem Objekt in dem Interface gibt es zwar eine kleine RTTI, aber dort fehlen viele Dinge, das Feld wird nicht erwähnt und auch nicht die Call-Methode, bzw. ob es weitere Methoden gibt.

Nja, von den Call-Methoden gibt es drei Arten ... da müßte man jetzt analysieren was die machen.
Darüber hatte wohl jemand schonmal rausgefunden, wie man den Methodenzeiger wieder extrahiert (wenn man genau weiß, daß eine Methode ist in der Referenz steckt).

aus'm anderen Thread hier herverschoben

Stevie 4. Nov 2011 10:15

AW: Speicherleck bei der Verwendung von anonymen Methoden
 
Wenn du
Delphi-Quellcode:
{$M+}
über die Definition der anonymen Methode schreibst, bekommst du RTTI für diesen Typ. Wie ja schon gesagt, sind anonyme Methoden nix anderes als Interfaces.

Mit folgender Routine kannst du dir die Signatur der Invoke Methode und die Felder für die captured Variablen ausgeben lassen:

Delphi-Quellcode:
procedure ListProcedureRefRtti(var MethodRef;
  const TypeInfo: Pointer; const Lines: TStrings);
var
  i: IInterface absolute MethodRef;
  o: TObject;

  c: TRttiContext;
  t: TRttiType;
  f: TRttiField;
  m: TRttiMethod;
begin
  o := i as TObject;

  t := c.GetType(TypeInfo);
  Lines.Add('Interface: ' + t.ToString);
  Lines.Add('Method:');

  for m in t.GetMethods do
  begin
    if m.Parent = t then
      Lines.Add(m.ToString);
  end;

  t := c.GetType(o.ClassInfo);
  Lines.Add('Object: ' + t.ToString);
  Lines.Add('Fields:');

  for f in t.GetFields do
  begin
    if f.Parent = t then
      Lines.Add(f.ToString);
  end;
end;
Bedenke dabei, dass im Falle von mehreren Anonymen Methoden im gleichen Scope nur 1 Objekt erzeugt wird.

Daher hast du bei folgendem Aufruf jedesmal Self und i als Felder. Ich habe hierzu TProc und TFunc lokal redefiniert und ihnen das
Delphi-Quellcode:
{$M+}
verpasst. Ich hatte Barry auch schon vor einiger Zeit darauf angesprochen das eventuell mal in die SysUtils Definitionen aufzunehmen:

Delphi-Quellcode:
var
  p: TProc;
  f: TFunc<Integer, Boolean>;
  i: Integer;
begin
  Memo1.Clear;

  p := procedure begin ShowMessage(Caption) end;
  ListProcedureRefRtti(p, TypeInfo(TProc), Memo1.Lines);

  f := function(Arg: Integer): Boolean begin Result := Arg mod 2 = 0 end;
  ListProcedureRefRtti(f, TypeInfo(TFunc<Integer, Boolean>), Memo1.Lines);

  p := procedure begin ShowMessage(IntToStr(i)) end;
  ListProcedureRefRtti(p, TypeInfo(TProc), Memo1.Lines);


Alle Zeitangaben in WEZ +1. Es ist jetzt 08:25 Uhr.

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