Wyciek pamięci mimo zwolnienia obiektów w tablicy statycznej

0

Witam

Mimo poprawnego zwalniania pamięci, otrzymuję przy zamykaniu programu komunikat o wycieku. Korzystam z dodatku "EurekaLog".

Poniższy kod:

uses PngImage;

  public
    function WczytajGrafikeAnimacji: Boolean;
  end;

 TAnimacja = record
    Licznik: Byte;
    Tablica: array[0..9] of TPngObject;
 end;

var
  Animacja: TAnimacja;


function TForm1.WczytajGrafikeAnimacji: Boolean;
var
 i: Byte;
 Plik, Lokalizacja: string;
begin
 {Lokalizacja grafiki}
 Lokalizacja := ExtractFilePath(Application.ExeName) + 'Images\';

 {Wczytanie grafiki}
 Result := True;
 for i := Low(Animacja.Tablica) to High(Animacja.Tablica) do
   begin
    Plik := 'anim_count_' + IntToStr(i) + '.png';

    if not FileExists(Lokalizacja + Plik) then
     begin
      Result := False;

      Break;
     end;

    {Wczytanie do tablicy}
    Animacja.Tablica[i] := TPngObject.Create;   //<--------- wskazanie na wyciek
    Animacja.Tablica[i].LoadFromFile(Lokalizacja + Plik);
   end;

 Animacja.Licznik := 0;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
 DoubleBuffered := True;
 if WczytajGrafikeAnimacji then Tim_Animacja.Enabled := True;
end;

{Animacja wczytanej grafiki}
procedure TForm1.Tim_AnimacjaTimer(Sender: TObject);
begin
 I_Animacja.Picture.Assign(Animacja.Tablica[Animacja.Licznik]);

 Inc(Animacja.Licznik);
 if Animacja.Licznik > High(Animacja.Tablica) then Animacja.Licznik := 0;
end;

{Zwalnianie}
procedure TForm1.FormDestroy(Sender: TObject);
var
 i: Byte;
begin
 for i := High(Animacja.Tablica) downto Low(Animacja.Tablica) do
    begin
     Animacja.Tablica[i] := nil;
     Animacja.Tablica[i].Free;
    end;

 //FreeAndNil(Animacja.Tablica);
end;

W czym może być problem?
Grafika w tablicy jest czyszczona i następnie TPngObject jest zwalniany.

Próbowałem też z ''FreeAndNil(Animacja.Tablica);'

2

Jak najpierw przypisujesz nil to przecież tracisz wskaźnik na obiekt, a Free wywołane na nilu po prostu nic nie robi.

2

FreeAndNil(Animacja.Tablica[i]);
Ale nil'ować nie musisz.

0

@Opi - a nie możesz utworzyć sobie wszystkich obiektów w tablicy, zanim zaczniesz ładować do nich grafiki z plików? Jakoś dziwnie mi ta metoda ładująca wygląda.

0

Free sie nie wykona bo jest tam sprawdzanie na poczatku

<> nil
1

Osobiście preferowałbym tworzenie obiektów animacji zawsze, bez względu na istnienie na dysku plików z grafikami klatek; Same obiekty klasy TPNGObject nie zajmują zbyt wiele w pamięci, więc raczej nie ma sensu aż tak oszczędzać pamięci; Co innego, gdyby takich obiektów trzeba było trzymać w pamięci tysiące, ale Ty @Opi masz tylko 10, więc spokojnie mogą sobie siedzieć w pamięci;

Po drugie - lepszym i bardziej elastycznym rozwiązaniem było by stworzenie klasy, trzymającej informacje o danej animacji, zamiast używania do tego celu rekordów; Dzięki temu klasa animacji będzie przechowywać wszelkie informacji o sobie samej w sobie (wyszło masło maślane), co przełoży się na uproszczone sterowanie taką klasą; Czyli obsługa instancji klasy animacji będzie się ograniczać do korzystania z jej właściwości i metod, bez oprogramowywania animacji na zewnątrz klasy TAnimation;

Wykonałem prostą aplikację implementującą moje sugestie - poniżej kompletny kod modułu głównego formularza, a w załączniku cała aplikacja; Dobrze się złożyło (tak przynajmniej myślę), bo najpewniej mam zainstalowaną w Delphi7 taką samą paczkę do obsługi obrazów PNG jak Ty; Kod aplikacji sprawdziłem na menedżerze FastMM i nie ma żadnych wycieków (przynajmniej nie mają prawa zaistnieć);

uses
  {...}, PNGImage, {...};

type
  TAnimationFramesArr = array [0 .. 9] of TPNGObject;

type
  TAnimation = class(TObject)
  private
    FGraphPath: TFileName;
    FFrames: TAnimationFramesArr;
    FLoaded: Boolean;
    FFrameIdx: Integer;
  private
    function GetCurrentFrame(): TPNGObject;
  public
    constructor Create();
    destructor Destroy(); override;
  public
    procedure LoadFramesGraphic();
    procedure Step();
  public
    property Loaded: Boolean read FLoaded;
    property CurrentFrame: TPNGObject read GetCurrentFrame;
  end;

type
  TMainForm = class(TForm)
    imgAnimation: TImage;
    tmrAnimation: TTimer;
    lblAnimationState: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure tmrAnimationTimer(Sender: TObject);
  private
    FAnimation: TAnimation;
  end;

var
  MainForm: TMainForm;

implementation

{$R *.dfm}

{ ----- animation ----------------------------------------------- }

constructor TAnimation.Create();
var
  intToken: Integer;
begin
  inherited Create();
  FGraphPath := ExtractFilePath(ParamStr(0)) + 'Images\';

  for intToken := Low(FFrames) to High(FFrames) do
    FFrames[intToken] := TPNGObject.Create();

  FLoaded := False;
  FFrameIdx := 0;
end;

destructor TAnimation.Destroy();
var
  intToken: Integer;
begin
  for intToken := Low(FFrames) to High(FFrames) do
    FFrames[intToken].Free();

  inherited Destroy();
end;

function TAnimation.GetCurrentFrame(): TPNGObject;
begin
  Result := FFrames[FFrameIdx];
end;

procedure TAnimation.LoadFramesGraphic();
var
  fnFrame: TFileName;
  intToken: Integer;
begin
  for intToken := Low(FFrames) to High(FFrames) do
  begin
    fnFrame := Format('anim_count_%d.png', [intToken]);

    if FileExists(FGraphPath + fnFrame) then
      FFrames[intToken].LoadFromFile(FGraphPath + fnFrame)
    else
      Exit;
  end;

  FLoaded := True;
end;

procedure TAnimation.Step();
begin
  Inc(FFrameIdx);

  if FFrameIdx > High(FFrames) then
    FFrameIdx := 0;
end;

{ ----- form ---------------------------------------------------- }

procedure TMainForm.FormCreate(Sender: TObject);
begin
  FAnimation := TAnimation.Create();
  FAnimation.LoadFramesGraphic();
end;

procedure TMainForm.FormDestroy(Sender: TObject);
begin
  FAnimation.Free();
end;

procedure TMainForm.FormShow(Sender: TObject);
begin
  if FAnimation.Loaded then
  begin
    tmrAnimation.Enabled := True;
    lblAnimationState.Caption := 'Animation frame files successfully loaded.';
  end
  else
    lblAnimationState.Caption := 'Some animation frame files missing! Animation stopped.';
end;

procedure TMainForm.tmrAnimationTimer(Sender: TObject);
begin
  imgAnimation.Picture.Assign(FAnimation.CurrentFrame);
  FAnimation.Step();
end;

end.
0

Dzięki @_13th_Dragon Twoja odpowiedź jest rozwiązaniem.

Natomiast @furious programming całkiem ciekawe, chociaż więcej kodu wyszło, ale sam schemat animacji jest zaszyty w jednej klasie. Trochę się natrudziłeś, bo i przykład dałeś. Dzięki :)
Grafik mam 72 i jest to w miarę płynne obracanie obiektu w pętli.

1

[...] ale sam schemat animacji jest zaszyty w jednej klasie.

To ważne, aby całą funkcjonalność danego elementu trzymać w jednym miejscu (w jednej klasie); Dzięki temu jeżeli dana klasa będzie działać niepoprawnie, to od razu będzie wiadomo gdzie szukać przyczyn błędów; Przynajmniej ja taką zasadę wyznaję - kod staje się czytelniejszy, łatwiejszy do opanowania i ewentualnej analizy;

Trochę się natrudziłeś, bo i przykład dałeś. Dzięki :)

Sam byłem ciekaw, jak to "po mojemu" by wyglądało, a i miałem chwilę wolną, więc takie cuś naskrobałem :]

Grafik mam 72 i jest to w miarę płynne obracanie obiektu w pętli.

No to w sumie nie tak dużo - nadal bym brał pod uwagę tworzenie wszystkich grafik, bez względu na to czy pliki istnieją na dysku, czy nie istnieją; No ale to tylko sugestia.

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