AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Sprachen und Entwicklungsumgebungen Object-Pascal / Delphi-Language Delphi Memoryleak oder fatale Exception bei TJSONObject.RemovePair
Thema durchsuchen
Ansicht
Themen-Optionen

Memoryleak oder fatale Exception bei TJSONObject.RemovePair

Ein Thema von t2000 · begonnen am 8. Mär 2025 · letzter Beitrag vom 10. Mär 2025
Antwort Antwort
Benutzerbild von t2000
t2000

Registriert seit: 15. Dez 2005
Ort: NRW
247 Beiträge
 
Delphi 12 Athens
 
#1

Memoryleak oder fatale Exception bei TJSONObject.RemovePair

  Alt 8. Mär 2025, 17:39
Delphi-Version: 12 Athens
Hi Leute,

ja, der Titel beschreibt meine Optionen. Entweder habe ich ein Memoryleak oder es knallt, weil mein Objekt freigegeben wurde.

Ich kann jetzt nicht den kompletten Code hier reinstellen, aber folgendes zu den Funktionen.
Problem ist im eigenen REST Server. Entwickelt mit TMS XData. Aber das spielt keine Rolle.

Ich übergebe einer Funktion Parameter. DER Parameter ist ein TJSONObject. Da der Parameter durch XData freigegeben wird, mache ich mir ein Kopie.

ALso habe ich vor dem Aufruf diesen Code: (angedeutet, wahrscheinlich irrelevant)

Delphi-Quellcode:
var
  JSONDataClone: TJSONObject;
begin
  JSONDataClone := AsngParam.JSONData.Clone as TJSONObject;
  try
    ... mach was ...

  finally
    JSONDataClone.Free;
  end;
Hier jetzt meine Hilfsfunktionen, die den Fehler machen:

Ich möchte die übergebenen Parameter auf Vollständigkeit und korrekten Inhalt überprüfen.
Ggf. werden dort Feldnamen angepasst. Dass wird mit TJSONObject.AddPair bzw. TJSONObject.RemovePair gemacht. Deswegen auch der Clone.

Diese Routine heißt: TUtilJSON.CheckJSONEntity

Dort drin werden 2 andere Funktionen aufgerufen: TUtilJSON.CheckXDataRef und TUtilJSON.CheckIDValue

Ursprünglich hatte ich einfach "RemovePair" aufgerufen. Bei Programmende wurde dann aber ein MemoryLeak angezeigt. Nach sehr langem Suchen fand ich heraus, dass TJSONObject.RemovePair das zu löschende Paar zurückliefert und dies dann noch aus dem Speicher entfernt werden muss.
Das habe ich bei allen Vorkommen eingebaut.
An einer Stelle aber knallt es jetzt.

Interessant für die Untersuchung:

TUtilJSON.CheckIDValue funktioniert einwandfrei
TUtilJSON.CheckXDataRef funktioniert nicht

Fehler: Nachdem AJSONObject.RemovePair( AName).Free aufgerufen wurde (was auch funktioniert) ist die Abfrage von AJSONObject nicht mehr möglich.
Z.B. im Debugger mit STRG-F7 AJSONObject.toString auswerten verursacht sofort ein Absturz. Zugriff auf ein freigegebenes Object. Als ob das AJSONObject freigegeben wurde.
Ich habe aber nur das Pair freigegeben. Siehe Screenshot im Anhang.

Auch eine Änderung des Quelltextes in:
Delphi-Quellcode:
var
  LPair: TJSONPair;
begin
  LPair := Obj.RemovePair( Name);
  LPair.Free;
macht den gleichen Fehler.

Hier die Sourcen:

siehe Kommentar // <=== hier nach ist der Zugriff auf AJSONObject nicht mehr möglich, da gelöscht.


Sehe ich den Fehler einfach nicht?


Delphi-Quellcode:
class function TUtilJSON.CheckXDataRef( AJSONObject: TJSONObject; AName: String): TJSONObject;
begin
  // Wenn Entity und nicht Entity@ref
  if HasValue( AJSONObject, AName) and not HasValue( AJSONObject, AName + cXDataRef) then
    // dann neuer Eintrag mit ref anhängen
    AJSONObject.AddPair( AName + cXDataRef, AJSONObject.Values[ AName]);
  // falls noch vorhanden, Entity (ohne ref) löschen
  if HasValue( AJSONObject, AName) then
    AJSONObject.RemovePair( AName).Free; // <=== hier nach ist der Zugriff auf AJSONObject nicht mehr möglich, da gelöscht.
  // und zurückgeben
  Result := AJSONObject;
end;

class function TUtilJSON.CheckIDValue( AJSONObject: TJSONObject; AName: String): TJSONObject;
var
  LIDValue: String;
begin
  if HasValue( AJSONObject, AName) then begin
    // Eintrag löschen und mit bereinigtem ID-Value neu erzeugen
    LIDValue := AJSONObject.Values[ AName].AsType<String>;
    AJSONObject.RemovePair( AName).Free;
    // und mit bereinigtem ID-Value neu erzeugen
    AJSONObject.AddPair( AName, DirtyStringToGuidStr( LIDValue));
  end;
  Result := AJSONObject;
end;

class function TUtilJSON.CheckJSONEntity( AObj: TJSONObject; AForInsert: Boolean; AEntity, AIDField: String;
                                          AInsertArr, AFieldArr: TStringDynArray): TJSONObject;
var
  i: Integer;
  LFeldname: String;
begin
  // *** Falls Objekt im Feld liegt ***
  if TUtilJSON.HasObject( AObj, AEntity) then
    AObj := AObj.GetValue<TJSONObject>( AEntity);

  // *** ID-neu Prüfung ***
  AObj := TUtilJSON.CheckNewID( AObj, AIDField, AForInsert);

  if AForInsert then begin

    // *** alle Insert-Felder prüfen ***
    for i := Low( AInsertArr) to High( AInsertArr) do begin
      LFeldname := AInsertArr[i];
      // falls Referenz
      if copy( LFeldname, 1, 1) = '@then begin
        delete( LFeldname, 1, 1);
        // Feldname
        AObj := TUtilJSON.CheckXDataRef( AObj, LFeldname);
        LFeldname := LFeldname + cXDataRef;
        // Value (ID bereinigen)
        AObj := TUtilJSON.CheckIDValue( AObj, LFeldname);
      end;
      // Prüfung
      if not TUtilJSON.HasValue( AObj, LFeldname) then
        raise Exception.Create('Feld ' + LFeldname + _nichtVorhanden);
    end;
  end else begin

    // *** alle Felder prüfen ***
    for i := Low( AFieldArr) to High( AFieldArr) do begin
      LFeldname := AFieldArr[i];
      // falls Referenz
      if copy( LFeldname, 1, 1) = '@then begin
        delete( LFeldname, 1, 1);
        AObj := TUtilJSON.CheckXDataRef( AObj, LFeldname);
        LFeldname := LFeldname + cXDataRef;
      end;
      // Prüfung
      if not TUtilJSON.HasValue( AObj, LFeldname) then
        raise Exception.Create('Feld ' + LFeldname + _nichtVorhanden);
    end;
  end;

  Result := AObj;
end;
Angehängte Grafiken
Dateityp: jpg Screenshot 2025-03-08 182533.jpg (98,6 KB, 15x aufgerufen)
Thomas
(Wir suchen eine(n) Entwickler(in) mit Ambitionen später ggf. die Softwarefirma zu leiten)
Aktuell nicht mehr. Aber ab vielleicht 2024/2025 wird das wieder sehr interessant!
  Mit Zitat antworten Zitat
Benutzerbild von jaenicke
jaenicke

Registriert seit: 10. Jun 2003
Ort: Berlin
9.960 Beiträge
 
Delphi 12 Athens
 
#2

AW: Memoryleak oder fatale Exception bei TJSONObject.RemovePair

  Alt 8. Mär 2025, 21:11
Ich habe mir das noch nicht genau angeschaut. Zuerst einmal die Frage:
FastMM zeigt ja auch an, wo das Element vorher freigegeben wurde. Bringt dich das nicht schon weiter?
Sebastian Jänicke
AppCentral
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.643 Beiträge
 
Delphi 12 Athens
 
#3

AW: Memoryleak oder fatale Exception bei TJSONObject.RemovePair

  Alt 8. Mär 2025, 23:11
Solltest du an dieser Stelle nicht einen Clone des TJSONValue verwenden?
Delphi-Quellcode:
  if HasValue( AJSONObject, AName) and not HasValue( AJSONObject, AName + cXDataRef) then
    // dann neuer Eintrag mit ref anhängen
    AJSONObject.AddPair( AName + cXDataRef, AJSONObject.Values[ AName]);
Immerhin wird das Pair aus dem der Value stammt ja direkt danach freigegeben:
Delphi-Quellcode:
  if HasValue( AJSONObject, AName) then
    AJSONObject.RemovePair( AName).Free; // <=== hier nach ist der Zugriff auf AJSONObject nicht mehr möglich, da gelöscht.
Mir stellt sich überhaupt die Frage: Wieso änderst du nicht einfach den Namen des Pairs von AName auf AName + cXDataRef?

Bei CheckIDValue verwendest du einen string als Speicher für den Value-Inhalt, deswegen gibt es dort kein Problem.
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
Benutzerbild von t2000
t2000

Registriert seit: 15. Dez 2005
Ort: NRW
247 Beiträge
 
Delphi 12 Athens
 
#4

AW: Memoryleak oder fatale Exception bei TJSONObject.RemovePair

  Alt 9. Mär 2025, 07:28
... Stunden später ...

ne, die entscheidende Routine extrahiert und den Fehler gefunden. JSON ist schon sehr tückisch.

Der Fehler liegt hier: AJSONObject.AddPair( AName + cXDataRef, AJSONObject.Values[ AName]); Dort wird nicht der String des alten Feldes dem neuen Feld zugewiesen, sondern eine Referenz auf den Wert des alten Wertes. (!!)
Indem ich "AJSONObject.Values[ AName]" einsetze, setze ich nur einen Link.

Richtig wäre es:
Delphi-Quellcode:
var
  tempStr: String;
begin
  // vor dem Löschen
  tempStr := AJSONObject.Values[ AName].Value;
  AJSONObject.AddPair( AName + cXDataRef, tempStr);
Nun ist alles sauber. Kein Fehler und auch kein Memoryleak.

Also: Bei TJSONObject.RemovePair IMMER das gelöschte Pair freigeben! Aber sicher gehen, dass keine Referenz mehr auf dieses Pair existiert.

Danke. Uwe hat es erkannt.
Thomas
(Wir suchen eine(n) Entwickler(in) mit Ambitionen später ggf. die Softwarefirma zu leiten)
Aktuell nicht mehr. Aber ab vielleicht 2024/2025 wird das wieder sehr interessant!

Geändert von t2000 ( 9. Mär 2025 um 07:42 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von t2000
t2000

Registriert seit: 15. Dez 2005
Ort: NRW
247 Beiträge
 
Delphi 12 Athens
 
#5

AW: Memoryleak oder fatale Exception bei TJSONObject.RemovePair

  Alt 9. Mär 2025, 07:29

Mir stellt sich überhaupt die Frage: Wieso änderst du nicht einfach den Namen des Pairs von AName auf AName + cXDataRef?
So weit ich weiß, geht das nicht, oder?

Namensänderung in (Standard-)Delphi nicht möglich.
Thomas
(Wir suchen eine(n) Entwickler(in) mit Ambitionen später ggf. die Softwarefirma zu leiten)
Aktuell nicht mehr. Aber ab vielleicht 2024/2025 wird das wieder sehr interessant!
  Mit Zitat antworten Zitat
Benutzerbild von Uwe Raabe
Uwe Raabe

Registriert seit: 20. Jan 2006
Ort: Lübbecke
11.643 Beiträge
 
Delphi 12 Athens
 
#6

AW: Memoryleak oder fatale Exception bei TJSONObject.RemovePair

  Alt 9. Mär 2025, 09:47
Klar geht das:
Delphi-Quellcode:
procedure Main;
begin
  var obj := TJSONObject.Create;
  try
    obj.AddPair('MyName', 'Uwe');
    Writeln(obj.ToJSON);
    var pair := obj.Pairs[0];
    pair.JsonString := TJSONString.Create('MyFullName');
    pair.JsonValue := TJSONString.Create('Uwe Raabe');
    Writeln(obj.ToJSON);
  finally
    obj.Free;
  end;
end;
Uwe Raabe
Certified Delphi Master Developer
Embarcadero MVP
Blog: The Art of Delphi Programming
  Mit Zitat antworten Zitat
Benutzerbild von t2000
t2000

Registriert seit: 15. Dez 2005
Ort: NRW
247 Beiträge
 
Delphi 12 Athens
 
#7

AW: Memoryleak oder fatale Exception bei TJSONObject.RemovePair

  Alt 9. Mär 2025, 14:37
Das werde ich ausprobieren.
Ich hatte die Information von 2 unterschiedlichen Stellen. (im Internet; und fragt bitte nicht wo; ich weiß es nicht mehr)

Aktuell liefert ChatGPT o3-mini-high dies hier:
Screenshot 2025-03-09 153102.jpg
Thomas
(Wir suchen eine(n) Entwickler(in) mit Ambitionen später ggf. die Softwarefirma zu leiten)
Aktuell nicht mehr. Aber ab vielleicht 2024/2025 wird das wieder sehr interessant!
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 18:57 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