[Delphi] Memory-leak

Pagina: 1
Acties:

  • maikel
  • Registratie: Januari 2001
  • Laatst online: 13:57
Ik heb een proggie geschreven waarin een stukje code zit dat om de zoveel miliseconden in de systemtray een lijn tekent.
Nu viel het me echter op dat hoe langer het programma draait, hoe meer geheugen het in beslag neemt.
Ik heb inmiddels een hoop eruit gefiltert en kwam zo tot de ontdekking dat het geheugenlek in deze functie zit:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
procedure TForm1.Timer1Timer(Sender: TObject);
begin
    if TrayNWnd <> 0 then
    begin
        ClockCanvas := TCanvas.Create();
        ClockCanvas.Handle := GetWindowDC(TrayNWnd);
        if GotMessage then
            ClockCanvas.Pen.Color := clGray
        else
            ClockCanvas.Pen.Color := clRed;//BtnFace;
        if ClockCanvas.TryLock Then
        begin
            ClockCanvas.MoveTo(0, 20);
            ClockCanvas.LineTo(320, 20);
            PostMessage(TrayNWnd, WM_PAINT, 0, 0);
            ClockCanvas.Unlock;
        end;
        ClockCanvas.Free;
    end;
    GotMessage := Not GotMessage;
end;

TrayNWnd is een globale variabele die wordt gezet dmv:
code:
1
2
TrayWnd  := FindWindow('Shell_TrayWnd', nil);
TrayNWnd := FindWindowEx(TrayWnd, 0, 'TrayNotifyWnd', nil);

Als ik dus in de taskmanager kijk, komt er bij elke aanroep van de Timer-event zo'n 4K geheugen bij.
Dit is niet het geval als ik ClockCanvas ook gelobaal definieer en bij het aanmaken van het form aanmaak.
Maar dan werkt het tekenen op het canvas niet goed.

Weet iemand hoe ik wat aan dit geheugen-lek kan doen ?

  • CyberSnooP
  • Registratie: Augustus 2000
  • Laatst online: 08-09 20:35

CyberSnooP

^^^^ schrijft --->

Het enige wat ik kan bedenken is dat GetWindowDC iets opent / reserveerd wat je zelf moet sluiten. Maar ik heb er eigenlijk geen verstand van :)

|_____vakje______|


  • maikel
  • Registratie: Januari 2001
  • Laatst online: 13:57
Op woensdag 13 maart 2002 20:08 schreef CyberSnooP het volgende:
Het enige wat ik kan bedenken is dat GetWindowDC iets opent / reserveerd wat je zelf moet sluiten. Maar ik heb er eigenlijk geen verstand van :)
GetWindowDC retourneert volgens mij een integer-waarde.
Dus dat zou geen probleem om moeten leveren.

  • Creepy
  • Registratie: Juni 2001
  • Laatst online: 10:42

Creepy

Tactical Espionage Splatterer

Na een GetWindowsDC moet je volgens mij nog een ReleaseDC of ReleaseWindowsDC doen. Een DC (device context) wordt wel degelijk bijgehouden. De integer waarde waar jij het over hebt is alleen maar een referentie naar het DC toe.

"I had a problem, I solved it with regular expressions. Now I have two problems". That's shows a lack of appreciation for regular expressions: "I know have _star_ problems" --Kevlin Henney


  • maikel
  • Registratie: Januari 2001
  • Laatst online: 13:57
Volgens mij hebben jullie inderdaad gelijk ja.
Maar volgens mij zitten er nog wel een paar memory-leaks in m'n programma. :(

  • CyberSnooP
  • Registratie: Augustus 2000
  • Laatst online: 08-09 20:35

CyberSnooP

^^^^ schrijft --->

Aan de standaard Delphi methods op verschillende objecten zal het niet snel liggen, ga dus opzoek naar handmatige Windows APIs.

|_____vakje______|


  • maikel
  • Registratie: Januari 2001
  • Laatst online: 13:57
Ik maak in m'n programma ook runtime enkele forms aan.
Maar als ik deze weer afsluit, krijg ik niet m'n geheugen terug.
Sterker nog, het kost me nog meer geheugen !
Het aanmaken van het form:
code:
1
2
3
4
5
6
SndFrm := TSendForm.Create(self);
SndFrm.Caption := 'Send message...';
SndFrm.ComboBox1.Items := ComboBox1.Items;
SndFrm.ComboBox1.Text := ComboBox1.Text;
SndFrm.Show;
SndFrm.BringToFront();

En dan in de OnCreate van dat form:
code:
1
2
3
ShowWindow(Application.Handle, SW_HIDE);
SetWindowLong(Application.Handle, GWL_EXSTYLE, 
    GetWindowLong(Application.Handle, GWL_EXSTYLE) or WS_EX_TOOLWINDOW);

Het sluiten van het form:
code:
1
2
self.Close();
self.Release();

Wat doe ik hier verkeerd ??

  • whoami
  • Registratie: December 2000
  • Laatst online: 11-09 22:04
Op woensdag 13 maart 2002 20:40 schreef maikel het volgende:
Het sluiten van het form:
code:
1
2
self.Close();
self.Release();

Wat doe ik hier verkeerd ??
Hmm, normaal gezien is die Close toch verantwoordelijk voor het vrijgeven van het geheugen? Anders kan je eens proberen om die Release te vervangen door een Free().

Ik heb hier nu Delphi niet meer geinstalleerd staan, dus kan ik niet zelf gaan testen...

https://fgheysels.github.io/


  • maikel
  • Registratie: Januari 2001
  • Laatst online: 13:57
Op woensdag 13 maart 2002 21:20 schreef whoami het volgende:

[..]

Hmm, normaal gezien is die Close toch verantwoordelijk voor het vrijgeven van het geheugen? Anders kan je eens proberen om die Release te vervangen door een Free().

Ik heb hier nu Delphi niet meer geinstalleerd staan, dus kan ik niet zelf gaan testen...
Ik weet niet of Close ook automatisch een Release uitvoert als je het form zelf hebt aangemaakt.
Maar hij geeft er geen error op, en het zal zeker niet slecht zijn.
Uit de help:
To free a form, call its Release method, which destroys the form and releases the memory allocated for it after all its event handlers and those of the components it contains are through executing.
Dus Release zou de goede moeten zijn.

  • active2
  • Registratie: Juni 2001
  • Laatst online: 17-07 21:56

active2

Google is your friend

Op woensdag 13 maart 2002 20:40 schreef maikel het volgende:
Ik maak in m'n programma ook runtime enkele forms aan.
Maar als ik deze weer afsluit, krijg ik niet m'n geheugen terug.
Sterker nog, het kost me nog meer geheugen !
Het aanmaken van het form:
code:
1
2
3
4
5
6
SndFrm := TSendForm.Create(self);
SndFrm.Caption := 'Send message...';
SndFrm.ComboBox1.Items := ComboBox1.Items;
SndFrm.ComboBox1.Text := ComboBox1.Text;
SndFrm.Show;
SndFrm.BringToFront();

En dan in de OnCreate van dat form:
code:
1
2
3
ShowWindow(Application.Handle, SW_HIDE);
SetWindowLong(Application.Handle, GWL_EXSTYLE, 
    GetWindowLong(Application.Handle, GWL_EXSTYLE) or WS_EX_TOOLWINDOW);

Het sluiten van het form:
code:
1
2
self.Close();
self.Release();

Wat doe ik hier verkeerd ??
Je moet hem nog free'en dus:
code:
1
SndFrm.Destroy; --> mag ook SndFrm.Free; zijn!

Google, Het mirakel van de 21e eeuw!!!!


  • RickN
  • Registratie: December 2001
  • Laatst online: 14-06-2025
Op woensdag 13 maart 2002 20:40 schreef maikel het volgende:
code:
1
2
self.Close();
self.Release();

Wat doe ik hier verkeerd ??
Ik kan helemaal fout zitten hoor, maar volgens mijn kan een object zichzelf niet freeën, ik zie tenminste niet helemaal hoe dat zou werken...

He who knows only his own side of the case knows little of that.


  • maikel
  • Registratie: Januari 2001
  • Laatst online: 13:57
Hoe zou ik 'm vanuit m'n andere (main) form wel kunnen free'en dan ?

Verwijderd

Wanneer roep je deze code aan; dus op welk event?
code:
1
2
self.Close();
self.Release();

Hoe sluit je TSendForm af? Door op de X [border icon] te drukken?

  • Creepy
  • Registratie: Juni 2001
  • Laatst online: 10:42

Creepy

Tactical Espionage Splatterer

Op woensdag 13 maart 2002 22:39 schreef active2 het volgende:

[..]

Je moet hem nog free'en dus:
code:
1
SndFrm.Destroy; --> mag ook SndFrm.Free; zijn!
self.release zal de form al uit het geheugen halen (zie ook hierboven).

een form.free zal meteen het form destroyen, een form.release doet dit pas als alle event handlers van het form zijn afgehandeld.

"I had a problem, I solved it with regular expressions. Now I have two problems". That's shows a lack of appreciation for regular expressions: "I know have _star_ problems" --Kevlin Henney


Verwijderd

IPV PostMessage zou ik SendMessage gebruiken. Je blijft aan het posten als je pecht hebt. IPC is een bitch

  • maikel
  • Registratie: Januari 2001
  • Laatst online: 13:57
Het afsluiten doe ik in:
code:
1
2
3
4
5
procedure TSendForm.Button2Click(Sender: TObject);
begin
    self.Close();
    self.Release();
end;

Dit lijkt mij niet de goede manier. In de help staat namelijk ook al dat je een 'release' niet mag aanroep vanuit een eigen event-handler.
Maar hoe kan ik dat dan het beste doen ??

Verwijderd

maikel: In de help staat namelijk ook al dat je een 'release' niet mag aanroep vanuit een eigen event-handler.
Juist wel dus, daarvoor is die release bedacht.

Op die manier zoals je het nu gemaakt hebt, heb je geen memory-leak; als je op button2 drukt, wordt je TSendForm meteen gefreed; druk je op de X [border-icon] dan zal de form later worden gefreed.

Waarop baseer je het dat je een memory-leak hebt?

Als je nog steeds denkt een memory-leak te hebben, moet je maar een minimale app maken met nog hetzelfde probleem, en die dan posten. Ik kan dan wel kijken of er echt een memory-leak inzet. Of doe het zelf met bv MemCheck.

[edit]
Ik zou het btw zo doen
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
var
  SendForm: TSendForm;

class procedure TSendForm.Open(S1, S2: TStrings);
begin
  if not Assigned(SendForm) then
  begin
    SendForm := TSendForm.Create(Application);
    with SendForm do
    begin
    Caption := 'Send message...';
    ComboBox1.Items := S1;
    ComboBox2.Items := S2;
    end;
  end;
  with SendForm do
  begin
    Show; 
    BringToFront;
  end;
end;

destructor TSendForm.Destroy; 
begin
  SendForm := nil;
  inherited;
end;

procedure TSendForm.FormClose(Sender: TObject; var Action: TCloseAction); 
begin
  Release;
end;

  • maikel
  • Registratie: Januari 2001
  • Laatst online: 13:57
Ik zat inderdaad in de war met Free, daarbij staat in de Help namelijk wel:
Never explicitly free a component within one of its own event handlers or the event handler of a component it owns or contains.

Als ik de taskmanager open, zie ik dar m'n programma 2.120 K gebruikt (dit varieert overigens ook nogal eens, waardoor komt dat ??).
Als ik dan op een knop druk zodat m'n SendForm tevoorschijn komt, gebruikt m'n programma ineens 3.216 K.
Als ik dan dat form weer sluit zie ik dat m'n programma nog steeds 3.216 K in gebruik heeft.
Daarna open ik nogmaals het SendForm en komt het geheugengebruik op 3.372 K te staan.
Kortom: ik zie m'n geheugengebruik nooit echt teruglopen.

Verwijderd

Taskmanager laat niet het gebruikte geheugen, maar het geclaimde geheugen zien.

Als er genoeg geheugen vrij is, zal een applicatie niet [snel :?] geclaimde geheugen vrij geven.

Maar waarom zit je te kijken naar memory-leaks? Geeft het programma problemen? Gebruik iig niet taskmanager maar bv MemCheck, MemProof, Memory Sleuth;

Zelf gebruik ik MemCheck.
[edit]
Ik heb nog s naar MemProof gekeken en dat is eigenlijk wel een aanrader. Je runt je programma; en ziet meteen hoeveel er gealloceerd/vrijgegeven wordt als je een window opent/sluit.

  • maikel
  • Registratie: Januari 2001
  • Laatst online: 13:57
Op dinsdag 19 maart 2002 00:13 schreef DiFool het volgende:
Taskmanager laat niet het gebruikte geheugen, maar het geclaimde geheugen zien.

Als er genoeg geheugen vrij is, zal een applicatie niet [snel :?] geclaimde geheugen vrij geven.

Maar waarom zit je te kijken naar memory-leaks? Geeft het programma problemen? Gebruik iig niet taskmanager maar bv MemCheck, MemProof, Memory Sleuth;

Zelf gebruik ik MemCheck.
[edit]
Ik heb nog s naar MemProof gekeken en dat is eigenlijk wel een aanrader. Je runt je programma; en ziet meteen hoeveel er gealloceerd/vrijgegeven wordt als je een window opent/sluit.
Ik merkte bij m'n eerste versie dat m'n hele systeem wat trager liep nadat m'n proggie was opgestart.
Ik heb toen wel een paar fouten eruit gehaald.
Maar ik zal wel eens met MemProof gaan checken dan.
Is deze gewoon op www.download.com te vinden of zo ??

  • jelmervos
  • Registratie: Oktober 2000
  • Niet online

jelmervos

Simple user

Op dinsdag 19 maart 2002 10:16 schreef maikel het volgende:

[..]

Is deze gewoon op www.download.com te vinden of zo ??
http://www.automatedqa.com/downloads/memproof.asp

"The shell stopped unexpectedly and Explorer.exe was restarted."


  • Tomatoman
  • Registratie: November 2000
  • Laatst online: 15:07

Tomatoman

Fulltime prutser

Drie opmerkingen. Ten eerste is het goed om te zorgen dat je geheugen hoe dan ook wordt vrijgegeven door een try...finally constructie te gebruiken:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  if TrayNWnd <> 0 then
  begin
    ClockCanvas := TCanvas.Create();
    try
    ClockCanvas.Handle := GetWindowDC(TrayNWnd);
    ...
    finally
    ReleaseDC(ClockCanvas.Handle); // of iets dergelijks
    ClockCanvas.Free;
    end;
    GotMessage := Not GotMessage;
  end;
end;

Ten tweede het verschil tussen Close, Release, Destroy en Free. Destroy roep je NOOIT zelf aan om een object op te ruimen. EenObject.Destroy is dus niet goed! In plaats daarvan gebruik je Free, dat Destroy aanroept en nog wat meer doet. Je kunt een object altijd vernietigen met Free, dat gaat zelfs goed als het object nil is. EenObject := nil; EenObject.Free; is dus zonder meer goed en geeft geen exceptions.

Met Close sluit je een Form, maar blijft het in het geheugen. Je kunt het vervolgens weer openen met Open.

Release gebruik je binnen een form om datzelfde form op te ruimen. Uit de help: 'Release does not destroy the form until all event handlers of the form and event handlers of components on the form have finished executing. Any event handlers of the form should use Release instead of Free. Failing to do so could lead to an access violation.' Buiten het form gebruik je gewoon Free, niet Release.

Dan de derde opmerking:
code:
1
2
SetWindowLong(Application.Handle, GWL_EXSTYLE, 
    GetWindowLong(Application.Handle, GWL_EXSTYLE) or WS_EX_TOOLWINDOW);

kun je ook schrijven als SendFrm.BorderStyle := bsToolWindow;

Een goede grap mag vrienden kosten.

Pagina: 1