Entwickler-Ecke

Sonstiges (Delphi) - Icons verbrauchen alle Resourcen...


FriFra - So 25.01.04 16:00
Titel: Icons verbrauchen alle Resourcen...
Ich weiss, dass ich irgendwo etwas falsch mache, zumindest fängt bei wiederholter Ausführung der folgenden Funktion der Rechner irgendwann komplett an zu spinnen... obwohl eigentlich alles jeweils korrekt freigegeben wird (werden sollte).


Delphi-Quelltext
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:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
105:
106:
107:
108:
109:
110:
111:
112:
113:
114:
115:
116:
117:
118:
119:
120:
121:
122:
123:
124:
125:
126:
127:
128:
129:
130:
131:
132:
133:
134:
135:
136:
137:
138:
139:
140:
141:
142:
143:
144:
145:
146:
147:
148:
149:
150:
151:
152:
153:
154:
155:
156:
procedure TForm1.SetIcon(Loading, Ignoring, Stopped, Plugin:
  boolean);
  function CombineIcons(FrontIcon, BackIcon: HIcon): HIcon;
  var
    WinDC: HDC;
    FrontInfo: TIconInfo;
    FrontDC: HDC;
    FrontSv: HBITMAP;
    BackInfo: TIconInfo;
    BackDC: HDC;
    BackSv: HBITMAP;
    BmpObj: tagBitmap;
  begin
    WinDC := GetDC(0);

    GetIconInfo(FrontIcon, FrontInfo);
    FrontDC := CreateCompatibleDC(WinDC);
    FrontSv := SelectObject(FrontDC, FrontInfo.hbmMask);

    GetIconInfo(BackIcon, BackInfo);
    BackDC := CreateCompatibleDC(WinDC);
    BackSv := SelectObject(BackDC, BackInfo.hbmMask);

    GetObject(FrontInfo.hbmMask, SizeOf(BmpObj), @BmpObj);
    BitBlt(BackDC, 00, BmpObj.bmWidth, BmpObj.bmHeight, FrontDC, 00,
      SRCAND);

    SelectObject(BackDC, BackInfo.hbmColor);
    DrawIconEx(BackDC, 00, FrontIcon, 0000, DI_NORMAL);

    Result := CreateIconIndirect(BackInfo);

    SelectObject(FrontDC, FrontSv);
    DeleteDC(FrontDC);
    SelectObject(BackDC, BackSv);
    DeleteDC(BackDC);
    ReleaseDC(0, WinDC);
    DeleteObject(FrontInfo.hbmColor);
    DeleteObject(FrontInfo.hbmMask);
    DeleteObject(BackInfo.hbmColor);
    DeleteObject(BackInfo.hbmMask);
    DeleteObject(FrontSv);
    DeleteObject(BackSv);
  end;
var
  RS: TResourceStream;
  sI: string;
  Ico: array[0..8of TIcon;
  n: integer;
begin
  for n := 0 to High(Ico) do
    Ico[n] := TIcon.Create;

  try
    StrPLCopy(IconData.szTip, '[' + Label2.Caption + ']'#13 + 'WAN-IP: ' +
      Edit3.Text + #13 + 'LAN-IP: ' + Edit4.Text, 63);

    if G_IP = '0.0.0.0'#0 then
      sI := 'I_Inactive'
    else if G_IP = '0.0.0.0' then
      sI := 'I_Offline'
    else
      sI := 'I_Online';
    try
      RS := TResourceStream.Create(0, sI + GetIconSufix, RT_RCDATA);
      RS.Position := 0;
      Ico[0].LoadFromStream(RS);
    finally
      RS.Free;
    end;

    { Ladeanzeige }
    if Loading then
      sI := 'I_Loading'
    else
      sI := 'I_Blank';
    try
      RS := TResourceStream.Create(0, sI + GetIconSufix, RT_RCDATA);
      RS.Position := 0;
      Ico[1].LoadFromStream(RS);
    finally
      RS.Free;
    end;
    Ico[2].Handle := CombineIcons(Ico[1].Handle, Ico[0].Handle);

    if Ignoring then
      sI := 'I_Ignoring'
    else
      sI := 'I_Blank';
    try
      RS := TResourceStream.Create(0, sI + GetIconSufix, RT_RCDATA);
      RS.Position := 0;
      Ico[3].LoadFromStream(RS);
    finally
      RS.Free;
    end;
    Ico[4].Handle := CombineIcons(Ico[3].Handle, Ico[2].Handle);

    if Stopped then
      sI := 'I_Stop'
    else
      sI := 'I_Blank';
    try
      RS := TResourceStream.Create(0, sI + GetIconSufix, RT_RCDATA);
      RS.Position := 0;
      Ico[5].LoadFromStream(RS);
    finally
      RS.Free;
    end;
    Ico[6].Handle := CombineIcons(Ico[5].Handle, Ico[4].Handle);

    if Plugin then
      sI := 'I_Plugin'
    else
      sI := 'I_Blank';
    try
      RS := TResourceStream.Create(0, sI + GetIconSufix, RT_RCDATA);
      RS.Position := 0;
      Ico[7].LoadFromStream(RS);
    finally
      RS.Free;
    end;
    Ico[8].Handle := CombineIcons(Ico[7].Handle, Ico[6].Handle);

    DestroyIcon(TNA_Icon.Handle);
    TNA_Icon.Assign(Ico[8]);
    DestroyIcon(IconData.hIcon);
    IconData.hIcon := TNA_Icon.Handle;

    { Statusanzeige }
    DestroyIcon(Image1.Picture.Icon.Handle);
    Image1.Picture.Icon.Handle := IconData.hIcon;
    Image1.Repaint;
    Edit3.Text := G_IP;
    Label7.Caption := G_IT;

    if G_IP = '0.0.0.0'#0 then
      Label2.Caption := 'undefined'
    else if G_IP = '0.0.0.0' then
      Label2.Caption := 'Offline'
    else
      Label2.Caption := 'Online';

    if IsMinimized = True then
      Shell_NotifyIcon(NIM_MODIFY, @IconData);

  finally
    for n := 0 to High(Ico) do
    begin
      Ico[n].ReleaseHandle;
      DestroyIcon(Ico[n].Handle);
      FreeAndNil(Ico[n]);
    end;
    IconReady := True;
  end;
end;

:nixweiss:

Update: Ich habe auf einen Array of Icon umgestellt ;)


MaxiTB - So 25.01.04 18:13

Ähm - Frage so nebenbei - warum verwendest den kein Array für die Icons ! Da blickt ja keiner mehr durch und die Fehleranfälligkeit steigt ins astronomische ... nein, fast intergalaktische Sphären die sich überdimensional auszudehen scheinen im Raumzeitkontinuum ! :wink:


FriFra - So 25.01.04 18:17

Ähm... sooo viele Icons sind es nun auch wieder nicht, und da jedes der temporären Icons nur einmal belegt wird, sollte es noch überschaubar sein... Array oder nicht Array, da ist hier nicht die Frage, gesucht wird der Fehler bei der Ressourcenfreigabe :roll:


MaxiTB - So 25.01.04 18:46

Schön - Frage: Wo gibst du FrontSv frei ? Und wo BackSv ?

Und ich bleibe dabei, du sagst selber du hast den Überblick verloren, also ist ein Array angesagt. Bei alles was mehr als 2 ist, ist ein Array nämlich angesagt. Da schrumpfen in deinem Fall 9 Zeilen auf zwei - und das in 3 Fällen ... wirst ja nicht nach Codezeilen bezahlt, oder ? :D

Edit
Moment - SelectObject ... in dem Fall braucht man das RegionObject nnicht freizugeben. *puh* Ich blick da gerade nicht durch. Vielleicht tut sich ein anderer leichter bei deinem Code !


FriFra - So 25.01.04 19:02

Ich habe nirgends gesagt, dass ich den Überblick verloren habe...

Danke für den Tip ;) . Die Funktion CombineIcons habe ich vom SwissDelphicenter und wende sie auch schon längere Zeit ohne Probleme an, deshalb habe ich dort keinen Fehler vermutet. Aber Du hast recht, ich habe am Ende der Funktion noch folgendes eingebaut:

Delphi-Quelltext
1:
2:
    DeleteObject(FrontSv);
    DeleteObject(BackSv);

Allerdingt kommt immernoch nach wenigen Minuten (einige hundert Updates) ein EOutOfResources :?

Ich habe die Funktion CombineIcons auch noch nie so häufig genutzt, aber da der Tip schon länger veröffentlicht ist und offenbar noch keiner solche Probleme hatte denke ich nicht, das es an dieser Funktion liegt. Der Fehler müsste eigentlich wo anders liegen...


FriFra - So 25.01.04 19:14

@MaxiTB: Ich habe Deinem Wunsch entsprochen und auf Array umgestellt ;)


MaxiTB - So 25.01.04 19:19

Der Absatz kommt mir noch ein bisserl merkwürdig vor ... was pssiert hier genau ?


Delphi-Quelltext
1:
2:
3:
4:
DestroyIcon(TNA_Icon.Handle); 
    TNA_Icon.Assign(Ico[8]); 
    DestroyIcon(IconData.hIcon); 
    IconData.hIcon := TNA_Icon.Handle;


FriFra - So 25.01.04 19:28

MaxiTB hat folgendes geschrieben:
Der Absatz kommt mir noch ein bisserl merkwürdig vor ... was pssiert hier genau ?


Delphi-Quelltext
1:
2:
3:
4:
DestroyIcon(TNA_Icon.Handle); 
    TNA_Icon.Assign(Ico[8]); 
    DestroyIcon(IconData.hIcon); 
    IconData.hIcon := TNA_Icon.Handle;



1. Zeile: Ich habe in meinem Programm eine globale Icon Variable TNA_Icon, deren Icon wird hier freigegeben.
2. Zeile: Meinem globalen Icon wird der Inhalt des gerade berechneten Icons (Ico[8]) zugewiesen
3. Zeile: Das Icon der globalen Variable IconData (Typ: TNotifyIconData) wird hier freigegeben.
4. Zeile: Das TNA_Icon wird dem eigentlichen NotifyIcon zugewiesen (noch nicht in den TNA upgedatet)


MaxiTB - So 25.01.04 19:32

Jetzt fällt mir nix mehr auf ... ists sicher, daß es an dieser Stelle passiert ? Immerhin wird hier wesentlich mehr freigegeben als überhaupt reserviert wird ...


FriFra - So 25.01.04 19:44

MaxiTB hat folgendes geschrieben:
Immerhin wird hier wesentlich mehr freigegeben als überhaupt reserviert wird ...


Eben deshalb frage ich ja hier... Ich bin mir aber 100% sicher, dass es an dieser Procedure liegt. Denn wenn ich direkt am Procedure-beginn ein Exit platziere, läuft das Programm Fehlerfrei... nur eben sinnlos, weil das Icon nicht geändert wird :?


MaxiTB - So 25.01.04 21:38

Oki - kann dir nur noch einen Tip geben ... klammert per Kommentare soviel Code in der Prozedure aus wie möglich und dann schrittweise weniger ... bis der Fehler wieder auftritt. :roll:

Mehr fällt mir nicht ein ... so kannst du vielleicht die Stelle besser eingrenzen ...


FriFra - So 25.01.04 21:49

Ich habe jetzt festgestellt, dass bei Auskommentierung aller Zuweisungen über CombineIcon kein Fehler mehr auftritt :shock: ... Allerdings ist die Kombination der Icons elementar für mein Programm :?

Wenn ich das nicht gelöst bekomme, muss ich die Anzahl der Icons in der Resource vervierfachen, um alle Kombinationen abzudecken :(


MaxiTB - So 25.01.04 21:52

Okay - jetzt wirds ernst - gucken wir mal, was das Ding wirklich macht und wo da der Saft verloren geht ... Moment ...

Jetzt habe ich gerade X-Men 2: United hinter mir, jetzt bin ich wieder motiviert :wink:


MaxiTB - So 25.01.04 22:01

Haha ... ist doch klar. *lol* Warum ists mir nicht gleich aufgefallen ...

Mit CreateIconIndirect erstellst du eine neue Resource, welche du mit DestroyIcon ja wieder freigibst, oder ? ODER ? Ja was ist ? Wo wird die jetzt freigegeben ? *g*

Das ist die Frage, nicht ?

Schauen wir uns diese Zeile mal genau an:

Delphi-Quelltext
1:
Ico[2].Handle := CombineIcons(Ico[1].Handle, Ico[0].Handle);                    


Die Frage ist: Was macht sie ? Ändert sie nur eine Referenz, wird die alte Resource freigegeben - wird vielleicht intern wieder ein CreateIconIndirect gemacht um eine Kopie zu bekommen - da hilft nur ein Blick in die Source ;-) Moment ...


toms - So 25.01.04 22:03

Zitat:
Remember: The icon created with the CombineIcons function must be destroyed with DestroyIcon() function when finished using it.


FriFra - So 25.01.04 22:09

MaxiTB hat folgendes geschrieben:
Haha ... ist doch klar. *lol* Warum ists mir nicht gleich aufgefallen ...

Mit CreateIconIndirect erstellst du eine neue Resource, welche du mit DestroyIcon ja wieder freigibst, oder ? ODER ? Ja was ist ? Wo wird die jetzt freigegeben ? *g*


Schau mal in den finally Abschnitt... da gebe ich alles frei. Zwischendurch ändere ich auch nichts mehr an den bettr. Icons.


MaxiTB - So 25.01.04 22:12


Delphi-Quelltext
1:
2:
3:
4:
5:
procedure TIcon.SetHandle(Value: HICON);
begin
  NewImage(Value, nil);
  Changed(Self);
end;


Aha ... weiterstöbern ...


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
procedure TIcon.NewImage(NewHandle: HICON; NewImage: TMemoryStream);
var
  Image: TIconImage;
begin
  Image := TIconImage.Create;
  try
    Image.FHandle := NewHandle;
    Image.FMemoryImage := NewImage;
  except
    Image.Free;
    raise;
  end;
  Image.Reference;
  FImage.Release;
  FImage := Image;
end;



Delphi-Quelltext
1:
2:
3:
4:
5:
procedure TIconImage.FreeHandle;
begin
  if FHandle <> 0 then DestroyIcon(FHandle);
  FHandle := 0;
end;


Okay ... Mist ... schaut so aus, wie wenn das auch richtig täte ...

Aber so wie es aussieht, ists vielleicht gar nicht soo gut, wenn du am Schluß auf diese Referenz ein DestroyIcon machst eben wegen den Code da oben ...

Wenn mans nämlich genau betrachtet, wird zweimal ein DestroyIcon durchgeführt ... und ich weiß nicht, ob das nicht zu Nebeneffekten führen kann. Obwohl FHandle:=nil ... daher müßten die schon alle nil sein zu dem Zeitpunkt ...


MaxiTB - So 25.01.04 22:25


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
function TIcon.ReleaseHandle: HICON;
begin
  with FImage do
  begin
    if FRefCount > 1 then NewImage(CopyIcon(FHandle), nil);
    Result := FHandle;
    FHandle := 0;
  end;
  Changed(Self);
end;


Schau mal, ob zufällig ReleaseHandle ein Icon zurückgibt; das solltest du nämlich freigeben. Man beachte das CopyIcon !


FriFra - So 25.01.04 23:11

Also, nach ReleaseHandle bleibt die Handlenummer immer erhalten, genau wie nach DestroyIcon...
Ohne DestroyIcon schaufelt sich merklich der Arbeisspeicher zu. So wie es jetzt ist, bleibt der Speicherverbrauch relativ konstant, bis plötzlich die Ressourcen verbraucht sind.

Ich habe übrigens etwas merkwürdiges im Debugger bemerkt:
user defined image


MaxiTB - So 25.01.04 23:20

Dann hats aber was - weil da oben siehst du ja, das FHandle auf 0 gesetzt wird. Nach ReleaseHandle muß also Handle=nil sein.

Nochmals der ...

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
function TIcon.ReleaseHandle: HICON; 
begin 
  with FImage do 
  begin 
    if FRefCount > 1 then NewImage(CopyIcon(FHandle), nil); 
    Result := FHandle; 
    FHandle := 0
  end
  Changed(Self); 
end;


Irgendwie scheints da ordentlich den Speicher durcheinander zu wirbeln ...


toms - So 25.01.04 23:23

There's a bug in TIcon.ReleaseHandle. It reads...


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
function TIcon.ReleaseHandle: HICON;
begin
  with FImage do
  begin
    if FRefCount > 1 then NewImage(CopyIcon(FHandle), nil);
    Result := FHandle;
    FHandle := 0;
  end;
  Changed(Self);
end;


It should read...


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
function TIcon.ReleaseHandle: HICON;
begin
  with FImage do
    if FRefCount > 1 then NewImage(CopyIcon(FHandle), nil);

  with FImage do
  begin
    Result := FHandle;
    FHandle := 0
  end;
  Changed(Self);
end;


You get the problem when you have two TIcons that share the same
TIconImage. Suppose you want to change one of the icon's handles, you
say:


Delphi-Quelltext
1:
2:
3:
oldIcon := icon1.ReleaseHandle;
if oldIcon <> 0 then DestroyIcon (oldIcon);
icon1.Handle := newIcon;


.. but if you use the (buggy) version of ReleaseHandle on Icon1, it,

1. Creates a separate image for icon1 with a ref count of 1, and
decrements the ref count for the original image (which is still used by
icons2). (so far so good)

2. It returns the handle of the *original image* and sets the handle
to 0. The original image is still used by icon2, which goes mad.

The 'fixed version'..

1. Creates a separate image for icon1 with a ref count of 1, and
decrements the ref count for the original image (which is still used by
icons2). (so far so good)

2. It returns the handle of the *new* and sets it's handle to 0. The
original image is still used by icon2, which continues to work ok.

von http://homepages.borland.com/efg2lab/Library/UseNet/1999/0114b.txt