![]() |
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:
Anhang 35373
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; 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? |
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; |
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:
durch einen richtigen Typen ersetzt, ändert sich nichts.
TFunc<String>
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:
als letzen Befehl in FormCreate,
IInterface(PPointer(@Func)^)._Release;
aber sollte dieser Fehler wirklich mal irgendwann behoben werden, dann raucht dir die Anwendung ab. :stupid: |
AW: Speicherleck bei der Verwendung von anonymen Methoden
Gerade getestet: Das Problem tritt auch bei XE2 noch auf.
|
AW: Speicherleck bei der Verwendung von anonymen Methoden
Tritt das Speicherleck bei jeder verwendung auf oder einmalig beim ersten Aufruf?
|
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. |
AW: Speicherleck bei der Verwendung von anonymen Methoden
|
AW: Speicherleck bei der Verwendung von anonymen Methoden
@jaenicke
Vielen Dank für's Testen und die nette Antwort! Zitat:
Ich hatte nicht gefragt, ob das Problem bekannt ist, sondern
Ohne einen überheblichen Ton kannst du wohl überhaupt nichts von dir geben? :wall: |
AW: Speicherleck bei der Verwendung von anonymen Methoden
Zitat:
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. |
AW: Speicherleck bei der Verwendung von anonymen Methoden
@carlo93
Zitat:
Zu Deiner Frage: Der Compiler speichert die von anonymen Methoden genutzen Variablen ( ![]() 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:
Delphi-Quellcode:
{$IF (CompilerVersion>=20) and (CompilerVersion<???)}
if IInterface(PPointer(@Func)^)._AddRef>3 then IInterface(PPointer(@Func)^)._Release; IInterface(PPointer(@Func)^)._Release; {$IFEND} |
AW: Speicherleck bei der Verwendung von anonymen Methoden
Zitat:
Folgerichtig wäre der einzig wahre Beitrag, bei dem Du nichts zu bemängeln hättest, dieser hier: "Ja. Ja." Zitat:
|
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"
Beide Einträge betreffen übrigens "Version: 14.0". Dumme Frage: Gab's zu diesem Zeitpunkt schon anonyme Methoden? Zitat:
Sorry - aber das ist einfach dumm und keine sachliche Antwort wert. Zitat:
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. |
AW: Speicherleck bei der Verwendung von anonymen Methoden
Edit: egal...
|
AW: Speicherleck bei der Verwendung von anonymen Methoden
Zitat:
Zitat:
Zitat:
Überheblich könnte übrigens auch der sein, der frisch in einem Forum ist und gleich rumkoffert. |
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. |
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.
|
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. |
AW: Speicherleck bei der Verwendung von anonymen Methoden
Zitat:
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. |
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; |
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; |
AW: Speicherleck bei der Verwendung von anonymen Methoden
Zitat:
Das kann man testen, indem man einfach das hier schreibt
Delphi-Quellcode:
Weiterhin kann man über RTTI auf das Object, was dahinter steckt, zugreifen (siehe
type
TProcObject = class(TInterfacedObject, TProc) public procedure Invoke; end; ![]() 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 ![]() Mit diesem Wissen kann man eigentlich fast alles mit einer anonymen Methode machen. |
AW: Speicherleck bei der Verwendung von anonymen Methoden
Delphi-Quellcode:
Du kannst UnregisterCallback so oft aufrufen, wie du willst.
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. 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. :) |
AW: Speicherleck bei der Verwendung von anonymen Methoden
Zitat:
In dieser Hinsicht haben wir also ![]() |
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. |
AW: Speicherleck bei der Verwendung von anonymen Methoden
Zitat:
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. |
AW: Speicherleck bei der Verwendung von anonymen Methoden
Antwort von Barry:
Zitat:
|
AW: Speicherleck bei der Verwendung von anonymen Methoden
Zitat:
|
AW: Speicherleck bei der Verwendung von anonymen Methoden
Wenn du
Delphi-Quellcode:
ü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.
{$M+}
Mit folgender Routine kannst du dir die Signatur der Invoke Methode und die Felder für die captured Variablen ausgeben lassen:
Delphi-Quellcode:
Bedenke dabei, dass im Falle von mehreren Anonymen Methoden im gleichen Scope nur 1 Objekt erzeugt wird.
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; 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:
verpasst. Ich hatte Barry auch schon vor einiger Zeit darauf angesprochen das eventuell mal in die SysUtils Definitionen aufzunehmen:
{$M+}
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