Zarządzanie pamięcią parametru funkcji

0

Witam,
chciałbym się doradzić kogoś z większym doświadczeniem.
Wywołuję funkcje, która zwraca stan rysunku i obiekt graficzny g z funkcji GetCadPartNEW.

try
 if AboutBox.GetCadPartNEW(komponent,Image1.Height,Image1.width,ver, g)=true
  then
   Image1.Picture.Graphic:= g;
except
 ..
end;
g.Free();

W funkcji są parametry

function TAboutBox.GetCadPartNew(part:string;height,width,ver:integer; var g:TGraphic):boolean;

i g zadeklarowany przez referencje.

Funkcja wykonuje swoje zadanie i tworzy grafikę obiekt g

g:=TGraphic.Create(); //i podstawiam g:=gif gif jest pobrany wcześniej z gif.LoadFromStream(MS);

Problem jest taki, że nie mogę zwolnić pamięci dla elementu gif na końcu funkcji, g nadal ma referencję na element gif, a ja bym chciał tylko pobrać rysunek z gif i podstawić go do g, a niepotrzebnie zajętą pamięć zwolnić na końcu fukcji.

gif.free //i problemy, gubimy referencje.

Pomysły nieudane to: przekopiowanie Tgraphic, assign...

Pozdrawia i dziękuje za wszelkie wskazówki
Piotr

2

Przeanalizujmy ten kod:

if AboutBox.GetCadPartNEW(komponent, Image1.Height, Image1.width, ver, g) then
  Image1.Picture.Graphic := g;

Jeśli metoda wykona swoje zadanie i zwróci True to obiekt został utworzony, jeśli False to referencja zawiera nil. Przypisanie grafiki do właściwości Graphic spowoduje, że grafika zostanie skopiowana i jej kopia wyląduje w komponencie, dlatego też obiekt będzie musiał być w dalszej części zwolniony, aby uniknąć wycieków pamięci.

Wywołanie Free na pustej referencji (zawierającej nil) nie spowoduje rzucenia wyjątku, więc jej użycie będzie bezpieczne, nawet jeśli obiekt grafiki nie został utworzony w pamięci (jeśli Twoja metoda zwróciła False). Gdybym sam miał coś z takim kodem zrobić to zmodyfikowałbym metodę GetCadPartNew, tak aby w parametrze zawsze pobierała referencję do istniejącego w pamięci obiektu:

var
  LGraphic: TGraphic;
begin
  LGraphic := TGraphic.Create();
  try
    try
      if AboutBox.GetCadPartNew({..}, LGraphic) then
        Image1.Picture.Graphic := LGraphic;
    except
      {..}
    end;
  finally
    LGraphic.Free();
  end;
end;

Wtedy nie trzeba by przekazywać obiektu przez referencję (czyli można by bez var). Jeśli nie chcesz zmieniać zawartości metody to możesz kod zapisać w ten sposób:

begin
  try
    try
      if AboutBox.GetCadPartNew({..}, LGraphic) then
        Image1.Picture.Graphic := LGraphic;
    except
      {..}
    end;
  finally
    LGraphic.Free();
  end;
end;

I też będzie dobrze, bo bezpiecznie i kod spełni swoje zadanie. Przy czym zadbaj o to, aby LGraphic (Twoje g) posiadało wartość nil przed wywołaniem metody GetCadPartNew.

0

Dziękuje, bardzo mi pomogły twoje wskazówki.
Jednak jest nowa trudność, której sie nie spodziewałem.


function TAboutBox.GetCadPartNew(part:string;height,width,ver:integer; var g:TGraphic):boolean;
label  powtorz,koniec;
 var
  MS: TMemoryStream;
  I,kod,nr,licznik: Integer;
  Cls: TGraphicClass;
  Http: TIdHTTP;
  t:real;
  gif : TGifImage;
  blad:boolean;
  im:Timage;
begin
...
part:=trim(part);
nr:=0;
Dane.Magazyn2.parameters.ParamByName('@operacja').value:=101;{Informacja o komponencie}
Dane.Magazyn2.parameters.ParamByName('@Komponent').value:=part;
{--------------------}
Dane.Magazyn2.ExecProc;
Kod:=Dane.Magazyn2.parameters.ParamByName('@Return_VALUE').value;
{--------------------}
if (kod<>1037)and(kod<>1038) then
 begin
  result:=false;
  exit;
 end;
{--------------------}
Http:= TIdHTTP.Create(nil);
MS:= TMemoryStream.Create;
gif:= TGifImage.Create();
//zmiana algorytmu dnia 2016.02.19
powtorz:
try
  http.ReadTimeout := 50000;
  blad:=false;
  Http.get('http://cz1srv-int:81/cad.ashx?part='+ part +'&height='+IntToStr(height)+'&width='+IntToStr(width)+'&ver='+IntTostr(ver),MS);
  Ms.Position := 0;
  gif.LoadFromStream(MS);
  //
  g:=TGraphic.Create();
  if histCad.Parameters.ParamByName('@Return_VALUE').Value=1 then  g:=gif ;
except
    blad:=true;
end;
//
if blad then
 begin
  g:=Image6.Picture.Graphic;
end;
//
 gif.free;
 ms:=nil;
 http:=nil;
 result:=true;
end;


Nie mogę zrozumieć, dlaczego nie podoba mu się konstrukcja zwolnienia pamięci dla gif.free
Przecież obiekt gif nie jest już mi potrzebny.
Działa ok dla gif:=nil.
Wg mnie g:=gif tworzy referencję, gdy gif ginie to g ma problem i dostaję Acces violation.

Zarządzanie pamięcią to fascynujące zagadnienie któremu zamierzam się oddać jednak na razie są problemy

Pozdrawia
Piotr

0

Nie mogę zrozumieć, dlaczego nie podoba mu się konstrukcja zwolnienia pamięci dla gif.free

Co to znaczy "nie podoba się"? Kompilator nie jest kobietą z humorami, więc jeśli dostajesz jakiś hint, ostrzeżenie czy błąd kompilacji, to kompilator słusznie go podaje. Tak więc opisz sensownie o co chodzi z tą linijką.

if not (Kod in [1037, 1038]) then
  Exit(False);

Krócej i czytelniej, prawda?

g:=TGraphic.Create();
if histCad.Parameters.ParamByName('@Return_VALUE').Value=1 then  g:=gif ;

Tutaj masz spory problem i pewnie jego dotyczy wspomniany problem z kompilatorem. Tworzysz obiekt klasy TGraphic i referencję wpisujesz do g. Jeśli waunek zostanie spełniony to nadpisujesz referencję z g - tracisz wskazanie na nowo utworzony obiekt, czego efektem jest wyniek pamięci. Nie możesz tak robić.

except
    blad:=true;
end;
//
if blad then
 begin
  g:=Image6.Picture.Graphic;
end;

A po co to? Nie potrzebujesz przekazywać stanu przez dodatkową flagę (blad) - w sekcji except wykonaj to przypisanie.

Zarządzanie pamięcią to fascynujące zagadnienie któremu zamierzam się oddać jednak na razie są problemy

Zasada zarządzania pamięcią jest bardzo prosta - jak sam alokujesz pamięć to musisz ją sam zwolnić. Poza tym musisz pilnować, aby nie zgubić referencji do dynamicznie zaalokowanej pamięci. W zależności od przypadku, możesz posiadać jeden wskaźnik albo kilka wskaźników na te same dane - najważniejsze, aby mieć co najmniej jeden.

I co tam do cholery robi ten label? Nie dość że to paskudztwo, to jeszcze w ogóle tych etykiet nie używasz... :P

0

Jeszcze raz dzięki!
Teraz już wiem co go boli, niestety próby wyeliminowania kłopotu nie wypaliły.


function TAboutBox.GetCadPartNew(part:string;height,width,ver:integer; var g:TGraphic):boolean;

  http.ReadTimeout := 50000;
  blad:=false;
  Http.get('http://cz1srv-int:81/cad.ashx?part='+ part +'&height='+IntToStr(height)+'&width='+IntToStr(width)+'&ver='+IntTostr(ver),MS);
  Ms.Position := 0;
  gif.LoadFromStream(MS);
  //
  g:=TGraphic.Create();
  if histCad.Parameters.ParamByName('@Return_VALUE').Value=1 then  g:=gif ;

Ze streama ciągnę obrazek gif i tylko gif.LoadFromStream potrafi go odczytać.
Próbowałem zrobić to samo z TGrapic, i miałem nadzieje, że w ten sposób w kodu całkowicie wyeliminuję gif : TGifImage;
TGraphic ma metodę LoadFromStream ale nie podoba mu się to co jest w tym streamie.
Muszę wyeliminować obiekt gif z mojego kodu ale jak to zrobić to nie wiem?

--dopisane w ostatniej chwili
To mi się udało:

  g:=TGifImage.Create();
  g.LoadFromStream(MS);

Pozdrawia
Piotr

0

Ale ale - TGraphic to klasa abstrakcyjna, więc używanie jej bezpośrednio samo w sobie jest błędem.

0

Dziś będę obserwować wycieki, wszystko ładnie się skompilowało i wszystkie wydruki i podglądy działają jak nalezy...

function TAboutBox.GetCadPartNew(part:string;height,width,ver:integer; var g:TGraphic):boolean;

Mam pytanie jesli nie klasa abstrakcyjna g:TGraphic to co bys w tym miejscu widział?
Pozdrawia
Piotr

2

Musisz rozróżnić dwie rzeczy - pierwsza to tworzenie instancji klasy, a druga to pobieranie referencji w parametrze.

Jeśli chcesz utworzyć obiekt w pamięci to nie powinieneś używać klasy abstrakcyjnej, bo taka z definicji jest niekompletna, po części niezdefiniowana. Nie wiem jak w Delphi, ale pod Lazarusem stworzenie instancji klasy abstrakcyjnej powoduje wyświetlenie szeregu ostrzeżeń związanych z jej niekompletnością. Dla przykładu weźmy bazową klasę TStrings:

with TStrings.Create() do
try

finally
  Free();
end;

Kompilacja powyższego kodu spowoduje wyrzucenie ostrzeżeń o metodach abstrakcyjnych:

project1.lpr(9,26) Warning: Constructing a class "TStrings" with abstract method "Get"
project1.lpr(9,26) Warning: Constructing a class "TStrings" with abstract method "GetCount"
project1.lpr(9,26) Warning: Constructing a class "TStrings" with abstract method "Clear"
project1.lpr(9,26) Warning: Constructing a class "TStrings" with abstract method "Delete"
project1.lpr(9,26) Warning: Constructing a class "TStrings" with abstract method "Insert"

To są ostrzeżenia, a nie błędy, więc kod można uruchomić. Problem w tym, że z takiego obiektu nie będzie się dało normalnie skorzytać. Np. metoda Insert używana jest zawsze do dodania nowej pozycji do listy (metoda Add z niej korzysta), więc takiej listy nie będzie można nawet uzupełnić. Próba dodania pozycji skończy się wyjątkiem - w tym przypadku będzie to RunError(211), czyli błąd Call to abstract method.

Podobnie jest z klasą TGraphic - też zawiera mnóstwo metod abstrakcyjnych i bezpośrednie skorzystanie z instancji takiej klasy (nie wliczając konstruktora i destruktora) zakończy się wyjątkiem. Dlatego też powinieneś sobie wybrać klasę, która z tej bazowej dziedziczy i która to posiada nadpisane wszystkie abstrakcyjne metody. Jeśli TGifImage dziedziczy po TGraphic to z tej klasy skorzystaj - o ile masz zamiar operować na grafikach w formacie GIF.


Druga sprawa to pobieranie instancji klasy w parametrze. Metoda może posiadać parametr typu klasy abstrakcyjnej (bazowej), dlatego że klasa abstrakcyjna posiada informacje o metodach, które klasy dziedziczące będą miały zdefiniowane.

Dlatego też jeśli metoda posiada parametr typu TGraphic:

public
  procedure DoStuff(AGraphic: TGraphic);

można do niej przekazać instancję dowolnej klasy z niej dziedziczącej. W ten sposób działa metoda Canvas.Draw - w trzecim parametrze pobiera grafikę do namalowania na płótnie. Możesz przekazać w nim dowolną grafikę, którą opisuje klasa dziedzicząca z TGraphic, czyli np. TBitmap, TPortableNetworkGraphic, TGIFImage, TTiffImage, TJPEGImage (nazwy tych klas wziąłem z Lazarusa).

Samo dziedziczenie skonkretyzowanej klasy nie musi być bezpośrednie - ważne, aby w ogóle zachodziło. Np. klasa TTiffImage nie dziedziczy bezpośrednio z TGraphic, bo ścieżka dziedziczenia wygląda tak:

TGraphic -> TRasterImage -> TCustomBitmap -> TFPImageBitmap -> TTiffImage

Wewnątrz metody posiadającej parametr typu TGraphic, będziesz miał dostęp do wszystkiego co ta klasa zawiera, i równocześnie nie będziesz miał dostępu do elementów zawartych w klasie TTiffImage, które w bazowej nie są określone. I o to właśnie chodzi - używając klasy abstrakcyjnej w parametrze, ogranicza się działania metody tylko do współnej funkcjonalności. Jeśli interesuje Cię specyficzna dla danej klasy funkcjonalność, która w bazowej klasie nie istnieje, to siłą rzeczy parametr nie może być typu abstrakcyjnego.


Mam nadzieję, że nie namieszałem aż tak bardzo i że całość jest w miarę zrozumiała :]

0

Dziękuje! jestem pełen podziwu dla twojej wiedzy oraz chęci jej udostępnienia. Bardzo mi to jest miło i przyjemnie spotkać w necie taką niezwykłą osobę.
Temat bardzo mi się spodobał, proszę doradź mi co jest warte przeczytania na temat obiektowości w pascal/delphi...
Pozdrawia
Piotr

0

Jeśli znasz w miarę j. angielski (wystarczy, że umiesz czytać ze zrozumieniem) to podstawą jest dokumentacja języka:

W tych materiałach znajdziesz wszystko na temat samego języka, używania kompilatora i IDE. A jeśli chcesz coś po polsku to też możesz skorzystać z książki Kompendium Programisty, która znajduje się w tym serwisie w wersji elektronicznej (jest przepisana).

Co prawda dotyczy ona starego Delphi7, jednak większa część podstaw języka nie zmieniła się. Ja od tej książki zaczynałem - tyle że w wersji papierowej :]

0
furious programming napisał(a):

Co prawda dotyczy ona starego Delphi7, jednak większa część podstaw języka nie zmieniła się. Ja od tej książki zaczynałem - tyle że w wersji papierowej :]

Tak trochę się zgadzam, a trochę nie...
Może dla kogoś, kto nie rozumie OOP i nie pisze obiektowo, a używa obiektów, to faktycznie może to tak wyglądać.

Natomiast całkiem udatna lista zmian jest tu:
http://stackoverflow.com/questions/8460037/list-of-delphi-language-features-and-version-in-which-they-were-introduced-depre

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