TJSONMarshal – naprawa wycieku pamięci

0

Potrzebuję skonwertować objekt do JSON no to niby nic prostszego napisałem sobie metodę Serialize korzystającą z TJSONMarshal i wszystko jest pięknie, działa wynik poprawny ale każde wywołanie powoduje wyciek pamięci (kilkadziesiąt nawet kilkaset KB w zależności od rozmiarów obrazka). Metoda będzie wywoływana tysiące razy podczas działania programu więc nie mogę twego zlekceważyć mimo, że po kilku godzinach męki naprawdę mam ochotę.
Mój kod odtwarzający problem wygląda tak:

unit Unit4;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, System.JSON, Data.DBXJSON, Data.DBXJSONReflect, Data.DBXJSONCommon,
  Vcl.StdCtrls, Vcl.Imaging.jpeg;

type
  TFS = class helper for TFileStream
     function Serialize: string;
  end;

  TTestObj = class
  private
    fName: string;
    fImg: TFileStream;
  public
    property Name: string read fName write fName;
    property Img: TFileStream read fImg write fImg;

    function Serialize: string;

    constructor Create(AName: string; AFileName: string);
    destructor Destroy; override;
  end;

  TForm4 = class(TForm)
    Memo1: TMemo;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form4: TForm4;

implementation

{$R *.dfm}

function TFS.Serialize;
var
  jArray: TJSONArray;
begin
  result:= '';
  Self.Position:= 0;
  jArray:= TDBXJSONTools.StreamToJSON(Self, 0, Self.Size);
  try
    if Assigned(jArray) then
      result:= jArray.ToJSON;
  finally
    Self.Position:= 0;
    jArray.Free;
  end;
end;


function TTestObj.Serialize: string;
var
  m: TJSONMarshal;
begin
  result:= '';
  m:= TJSONMarshal.Create(TJSONConverter.Create);
  try
    m.RegisterConverter(TTestObj, 'fImg', function(Data: TObject; Field: String): string
    var
      sJArray: string;
    begin
      sJArray:= TTestObj(Data).Img.Serialize; //zwraca jako JSON tablicę bajtów [255,216,255,224,0,16,...,185,255,217]
      if Length(sJArray) > 0 then
        result:= sJArray
      else
      //jezeli wykomentuje jak ponizej to tego zaznaczonego dużego wycieku NIE MA! Tylko jak funkcja zwróci ten długi ciąg :/
      {if Length(sJArray) > 0 then
        result:= sJArray
      else}
        result:= '[]';
    end);

    result:= m.Marshal(Self).ToJSON;
  finally
    m.Free;
  end;
end;

constructor TTestObj.Create(AName: string; AFileName: string);
begin
  inherited Create;
  fName:= AName;
  fImg:= TFileStream.Create(AFileName, fmOpenRead);
end;

destructor TTestObj.Destroy;
begin
  fImg.Free;
  inherited Destroy;
end;


procedure TForm4.Button1Click(Sender: TObject);
var
  Test: TTestObj;

  jStr: string;
begin
  Test:= TTestObj.Create('TEST', 'd:\test.jpg');
  try
    jStr:= Test.Serialize;
    Memo1.Text:= jStr;
  finally
    Test.Free;
  end;
end;

end.

Na wyjściu w Memo ładnie dostaję to co chcę czyli:

{"type":"Unit4.TTestObj","id":1,"fields":{"fName":"TEST","fImg":"[255,216,255,224,0,16,74,70,73,70,0,1,1,1,0,96,0,96,0,0,255,219,0,67,0,2,1,1,2,1,1,2,2,2,2,2,2,2,2,3,5,3,3,3,3,3,6,4,4,3,5,7,6,7,7,7,6,7,7,8,9,11,9,8,8,10,8,7,7,10,13,10,10,11,12,12,12,12,7,9,14,15,13,12,14,11,12,12,12,255,219,0,67,1,2,2,2,3,3,3,6,3,3,6,12,8,7,8,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12...
84,201,217,165,40,201,65,217,181,207,185,255,217]"}}

Ale jak zapodam sobie raportowanie błędów to jestem w czarnej dupie

  ReportMemoryLeaksOnShutdown:= DebugHook <> 0;

raport.jpg

Ma ktoś pomysł co robię nie tak, bo pół soboty zamiast na piwie siedzę przy kompie i już nie mam pomysłów?

0

może tutaj masz wyciek pamięci

 m:= TJSONMarshal.Create(TJSONConverter.Create);

tworzysz instancję obiektu klasy TJSONConverter ale nigdzie go nie zwalniasz

0

Niestety nie drugim domyślnym parametrem metody Create jest OwnConverter: Boolean = true więc TJSONMarshal odpowiada za jego zwolnienie zresztą nawet jak jawnie go stworzę ustawię parametr na False i sam zwolnię to jest to samo.
Coś mi się wydaje a nawet jestem prawie pewien, że nie zwalnia zwracanego stringa w funkcji będącej parametrem metody RegisterConverter tylko nie ma jak się do niego dobrać :/
Samo wywołanie (testowane osobno) na TFileStream dodanej przez helper funkcji Serialize nie powoduje wycieku więc ona na pewno działa poprawnie.

4

Zamień

result:= m.Marshal(Self).ToJSON;

na

with m.Marshal(Self) do
begin
	result:= ToJSON;
	Free;
end;

i powinno śmigać

0

Ja pierniczę nie wierzę a ja pół dnia straciłem i to jeszcze soboty. Wielkie dzięki!

1 użytkowników online, w tym zalogowanych: 0, gości: 1