![]() |
Systemressourcen erschöpft beim VirtualStringTree.AfterCellPaint
Liste der Anhänge anzeigen (Anzahl: 1)
Im Rahmen einer Planungssoftware stelle ich RTF-Text formatierte Text in einer Spalte eines VirtualStringTree dar.
Die Daten werden im Objekt in einem MemoryStream gespeichert. Weiterhin hat das Object ein TPicture, wo ich in die Bitmap mir das Bild für die Anzeige im VST ablegen. Während des Ladevorgangs wird also ein JvRichEdit erzeugt, dort wird aus dem Stream die RTF-Information geladen, mittels TJvRichEdit.SaveToImage das RTF in das TPicture ausgegeben und das JvRichEdit anschließen wieder gelöscht. Soweit kein Problem. Im AfterCellPaint sieht's dann so aus:
Delphi-Quellcode:
Der Fehler tritt nicht regelmäßig auf. Aber doch sooft, dass nicht wirklich damit arbeiten kann. Die Objekte sind alle das, das habe ich geprüft.
procedure TfrmMain.vstTermineAfterCellPaint(Sender: TBaseVirtualTree;
TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex; CellRect: TRect); procedure AddSymbol(SymbolIndex: Integer; AddText: string; var DestBMP: TBitmap); var bmp: TBitmap; textwidth: Integer; textleft: Integer; destleft: Integer; begin bmp:=TBitmap.Create; try ilInformation.GetBitmap(SymbolIndex, bmp); if AddText<>'' then begin bmp.Canvas.Font.Size:=vstTermine.Font.Size; bmp.Canvas.Font.Style:=[fsBold]; textwidth:=bmp.Canvas.TextWidth(AddText); textleft:=bmp.Width; bmp.Width:=bmp.Width+textwidth+4; bmp.Canvas.TextOut(textleft+2, Trunc((bmp.Height-bmp.Canvas.TextHeight(AddText))/2), AddText); end; destleft:=DestBMP.Width; DestBMP.Width:=DestBMP.Width+bmp.Width; //<--- Manchmal tritt der Fehler hier auf BitBlt(DestBMP.Canvas.Handle, Destleft, 0, bmp.Width, bmp.Height, bmp.Canvas.Handle, 0, 0, SRCCOPY); finally bmp.Free; end; end; var t: TTermin; bmp: TBitmap; begin t:=TTermin(vstTermine.GetNodeData(Node)^); if Assigned(t) then begin case Column of 11: begin if t.RTFTextList.Items(ttArbeiten)<>Nil then begin top:=Trunc((CellRect.Height-t.RTFTextList.Items(ttArbeiten).pic.Bitmap.Height)/2); BitBlt(TargetCanvas.Handle, CellRect.Left, CellRect.Top+top, CellRect.Width, CellRect.Height, t.RTFTextList.Items(ttArbeiten).pic.Bitmap.Canvas.Handle, 0, 0, SRCAND); //<---- Manchmal aber auch hier end; bmp:=TBitmap.Create; bmp.Height:=ilInformation.Height; bmp.Width:=0; try try if t.TB then AddSymbol(SymTB, '', bmp); if t.TD then AddSymbol(SymTD, '', bmp); if t.FD then AddSymbol(SymFD, '', bmp); if t.HB then AddSymbol(SymHB, '', bmp); if t.PlainTextList.Items(ttBemerkung)<>nil then if t.PlainTextList.Items(ttBemerkung).text<>'' then AddSymbol(SymBemerkung, '', bmp); if t.REgeschrieben then AddSymbol(SymRE, '', bmp); if t.dmsAuftragNr<>'' then AddSymbol(SymAB, '', bmp); top:=Trunc((CellRect.Height-bmp.Height)/2); BitBlt(TargetCanvas.Handle, CellRect.Left+CellRect.Width-bmp.Width, CellRect.Top+top, bmp.Width, bmp.Height, bmp.Canvas.Handle, 0, 0, SRCCOPY); finally bmp.Free; end; end; end; end; end; Vor allem der Fehler in der Procedure AddSymbol, wo das Bitmap für die überlagerten Symbol gezeichnet wird, sollte doch eigentlich nicht auftreten. Hat jemand eine Idee, wie ich dem Fehler auf die Spurkommen kann. Bild vom MADExcept habe ich mal als Anhang beigefügt. |
AW: Systemressourcen erschöpft beim VirtualStringTree.AfterCellPaint
Hallo,
du erzeugst Bitmaps und gibst sie nicht mehr frei. FastMM4 benutzen oder ReportMemoryLeaksAtShutDown. Dann einen VST mit 1-2 Einträgen und laufen lassen. Danach siehst Du, wo das entsprechende Objekt erzeugt, aber nicht freigegeben wurde. ilInformation.GetBitmap(SymbolIndex, bmp); Wird in GetBitmap das Bitmap vielleicht auch erzeugt? |
AW: Systemressourcen erschöpft beim VirtualStringTree.AfterCellPaint
ilInformation Ist eine TImageList. Die wird ja in in bmp kopiert, welches nach dem finally auch wieder freigegeben wird.
FastMM und ReportMemoryLeaksOnShutdown habe ich schon mit mäßigem Erfolg ausprobiert. Leider liefern die mir auch (nicht wenige) MemoryLeaks von UniDAC, zu denen ich aber keine Sources habe. Früher hatte ich mal Zeos verwendet. uniDAC verwende ich im dem Projekt das erste mal. |
AW: Systemressourcen erschöpft beim VirtualStringTree.AfterCellPaint
Zitat:
|
AW: Systemressourcen erschöpft beim VirtualStringTree.AfterCellPaint
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:
Und da mach ich dann noch nicht mal viel. Die Connection wird geöffnet, es werden 2-3 Query abgefragt, 2 Threads laufen durch. Das wars. Da sind ein paar Einträge, über deren Klassennamen kann meine Klassen erkennen. Das meiste stammt von UniDAC. Ich will aber nicht ausschließen, dass es mein Fehler ist. Ich arbeite grundsätzlich über Klassen, in denen die Datenbankabfragen laufen. Alles ist über Try..Finally abgesichert, dass jedes object auch wieder freigegeben wird (kann höchstens sein, dass ich es mal an einer Stelle übersehen habe. Aber soweit ich das bisher gesichtet habe, sollte das nicht der Fall sein). Die Connection halte in einer globalen Klasse, die beim Programmstart erzeugt wird und übergebe die halt immer an die Querys. Hier mal ein Beispiel für eine Query-Klasse:
Delphi-Quellcode:
Ungefähr so sehen alle Abfragen aus.
procedure TZeitenList.LoadFromDB(Con: TUniConnection; Betriebguid: TGUID);
var q: TUniQuery; z: TZeiten; begin q:=TUniQuery.Create(nil); try self.Clear; q.Connection:=Con; q.SQL.Text:='Select * from zeiten where betriebguid=:betriebguid order by ID'; q.Params.ParamValues['betriebguid']:=Betriebguid.ToString; q.Active:=True; while not q.Eof do begin z:=TZeiten.Create; z.ID:=q.FieldByName('ID').AsInteger; z.guid:=StringToGUID(q.FieldByName('guid').AsString); z.betriebguid:=StringToGUID(q.FieldByName('betriebguid').AsString); z.zeitstr:=q.FieldByName('zeitstr').AsString; self.Add(z); q.Next; end; q.active:=False finally q.Free; end; end; Und meine globale Klasse sieht so aus:
Delphi-Quellcode:
Diese wird im Create der Mainform erzeugt und in deren Destroy auch wieder freigegeben.
unit Data.tpdbaccess;
interface uses Classes, System.SysUtils, Data.DB, DBAccess, Uni, Tools.globalTypes, MySQLUniProvider, Local.ConnectionList, Tools.globalConst, MemData; type TOnConnect=procedure(Verindungsname: string) of object; TOnDisconnect=procedure of object; TOnSendDBUpdateMessage=procedure(Msg: string; Append: Boolean) of object; TDBAccess=class private class var FCon: TUniConnection; class var FConnectionList: TConnectionList; class var FVerbindungsname: string; class var FOnConnect: TOnConnect; class var FOnDisconnect: TOnDisconnect; class var FOnSendDBUpdatemessage: TOnSendDBUpdateMessage; class constructor Create; overload; class procedure SetConnectionList(const Value: TConnectionList); static; class procedure SetVerbindungsname(const Value: string); static; class procedure DoConnect(Verbindungsname: string); class procedure DoDisconnect; class procedure AfterConnect(Sender: TObject); class procedure BeforeDisconnect(Sender: TObject); class procedure ConnectionLost(Sender: TObject; Component: TComponent; ConnLoseCouse: TConnLostCause; RetryMode: TRetryMode); public constructor Create(ConnectionItem: TConnectionItem); overload; destructor Destroy; override; class function open: Boolean; overload; class function open(ConnectionItem: TConnectionItem): Boolean; overload; class procedure Close; class property ConnectionList: TConnectionList read FConnectionList write SetConnectionList; class property Verbindungsname: string read FVerbindungsname write SetVerbindungsname; class procedure DoSendDBUpdateMessage(Msg: string; Append: Boolean = False); class property OnConnect: TOnConnect read FOnConnect write FOnConnect; class property OnDisconnect: TOnDisconnect read FOnDisconnect write FOnDisconnect; class property OnSendDBUpdateMessage: TOnSendDBUpdateMessage read FOnSendDBUpdatemessage write FOnSendDBUpdatemessage; class procedure GetTables(Strings: TStrings); class function ExecuteStatement(Statement: string): Variant; overload; class function ExecuteStatement(Statement: string; Params: array of Variant): Variant; overload; class function GetNewGUID: TGUID; class property Con: TUniConnection read FCon; end; const NullGUID='{00000000-0000-0000-0000-000000000000}'; implementation { TDBAccess } class constructor TDBAccess.Create; begin inherited; FCon:=TUniConnection.Create(nil); FCon.AfterConnect:=AfterConnect; FCon.BeforeDisconnect:=BeforeDisconnect; // FCon.OnConnectionLost:=ConnectionLost; FConnectionList:=TConnectionList.Create(true); end; class procedure TDBAccess.AfterConnect(Sender: TObject); begin DoConnect(FVerbindungsname); end; class procedure TDBAccess.BeforeDisconnect(Sender: TObject); begin DoDisconnect; end; class procedure TDBAccess.Close; begin FCon.Disconnect; FVerbindungsname:=''; end; class procedure TDBAccess.ConnectionLost(Sender: TObject; Component: TComponent; ConnLoseCouse: TConnLostCause; RetryMode: TRetryMode); begin RetryMode:=rmReconnect; end; constructor TDBAccess.Create(ConnectionItem: TConnectionItem); begin self.Create; FCon.ProviderName:=TTPConnectionTypeStr[integer(ConnectionItem.ConnectionType)]; FCon.Server:=ConnectionItem.Hostname; FCon.Port:=ConnectionItem.Port; FCon.Database:=ConnectionItem.Database; FCon.Username:=ConnectionItem.UserName; FCon.Password:=ConnectionItem.Password; FVerbindungsname:=ConnectionItem.ConnectionName; open; end; destructor TDBAccess.Destroy; begin if FCon.Connected then FCon.Disconnect; FConnectionList.Free; FCon.Free; inherited; end; class procedure TDBAccess.DoConnect(Verbindungsname: string); begin if Assigned(FOnConnect) then FOnConnect(Verbindungsname); end; class procedure TDBAccess.DoDisconnect; begin if Assigned(FOnDisconnect) then FOnDisconnect; end; class procedure TDBAccess.DoSendDBUpdateMessage(Msg: string; Append: Boolean); begin if Assigned(FOnSendDBUpdatemessage) then FOnSendDBUpdatemessage(Msg, Append); end; class function TDBAccess.ExecuteStatement(Statement: string; Params: array of Variant): Variant; begin Result:=FCon.ExecSQL(Statement, Params); end; class function TDBAccess.ExecuteStatement(Statement: string): Variant; begin Result:=FCon.ExecSQL(Statement); end; class function TDBAccess.GetNewGUID: TGUID; begin createGUID(Result); end; class procedure TDBAccess.GetTables(Strings: TStrings); begin FCon.GetTableNames(Strings); end; class function TDBAccess.open: Boolean; begin try Try FCon.Connect; Except FVerbindungsname:=''; End; finally Result:=FCon.Connected; end; end; class function TDBAccess.open(ConnectionItem: TConnectionItem): Boolean; begin FCon.ProviderName:=TTPConnectionTypeStr[integer(ConnectionItem.ConnectionType)]; FCon.Server:=ConnectionItem.Hostname; FCon.Port:=ConnectionItem.Port; FCon.Database:=ConnectionItem.Database; FCon.Username:=ConnectionItem.UserName; FCon.Password:=ConnectionItem.Password; FVerbindungsname:=ConnectionItem.ConnectionName; Result:=open; end; class procedure TDBAccess.SetConnectionList(const Value: TConnectionList); begin FConnectionList := Value; end; class procedure TDBAccess.SetVerbindungsname(const Value: string); begin FVerbindungsname := Value; end; end. (Die Threads erzeugen sich natürlich eine eigene TUniConnection). Bisher hat das auch recht gut funktioniert. Leider habe ich nicht von Anfang an mit ReportMemoryLeaksOnShutDown gearbeitet. Die größe des Projekt macht es jetzt natürlich umso schwieriger. |
AW: Systemressourcen erschöpft beim VirtualStringTree.AfterCellPaint
In vstTermineAfterCellPaint wird eine bmp-Variable verwendet, die nicht lokal deklariert ist.
|
AW: Systemressourcen erschöpft beim VirtualStringTree.AfterCellPaint
Hallo,
zumindestens wird ja was angezeigt, aber leider keine Bitmaps. Die "Ausrede", Projekt ist zu gross, lasse ich nicht gelten ;) Du kannst es ja abspecken (schrittweise ausklammern). FastMM4 würde dir die Zeile zeigen, wo die jeweilige Klasse erzeugt wird, die nicht freigegeben wird. Was schon mal komisch ist, dass er 2 Connections anzeigt. Kann es sein, das Du deine globale Connection beim Programmende nicht freigibst? Breakpoint auf destructor TDBAccess.Destroy; setzen und nachschauen |
AW: Systemressourcen erschöpft beim VirtualStringTree.AfterCellPaint
Die zwei Connections wundern mich auch etwas. Das sollte eigentlich schon die Ursache für die ganzen nachfolgenden Memory Leaks sein.
Aber zurück zur eigentlichen Frage: Kompiliert denn der SourceCode so überhaupt wie du ihn hier geschrieben hast? Mir fehlt zu dem zweiten
Delphi-Quellcode:
ein
try
Delphi-Quellcode:
.
except
Delphi-Quellcode:
11: begin
if t.RTFTextList.Items(ttArbeiten)<>Nil then begin top:=Trunc((CellRect.Height-t.RTFTextList.Items(ttArbeiten).pic.Bitmap.Height)/2); BitBlt(TargetCanvas.Handle, CellRect.Left, CellRect.Top+top, CellRect.Width, CellRect.Height, t.RTFTextList.Items(ttArbeiten).pic.Bitmap.Canvas.Handle, 0, 0, SRCAND); //<---- Manchmal aber auch hier end; bmp:=TBitmap.Create; bmp.Height:=ilInformation.Height; bmp.Width:=0; try try if t.TB then AddSymbol(SymTB, '', bmp); if t.TD then AddSymbol(SymTD, '', bmp); if t.FD then AddSymbol(SymFD, '', bmp); if t.HB then AddSymbol(SymHB, '', bmp); if t.PlainTextList.Items(ttBemerkung)<>nil then if t.PlainTextList.Items(ttBemerkung).text<>'' then AddSymbol(SymBemerkung, '', bmp); if t.REgeschrieben then AddSymbol(SymRE, '', bmp); if t.dmsAuftragNr<>'' then AddSymbol(SymAB, '', bmp); top:=Trunc((CellRect.Height-bmp.Height)/2); BitBlt(TargetCanvas.Handle, CellRect.Left+CellRect.Width-bmp.Width, CellRect.Top+top, bmp.Width, bmp.Height, bmp.Canvas.Handle, 0, 0, SRCCOPY); finally bmp.Free; end; end; Dinge die du mal noch ausprobieren kannst: Dann würde ich an deiner Stelle mal Teile der Paint Procedure auskommentieren und schauen, ob der Fehler immer noch auftritt. Erkennst du den Fehler nur daran, dass die CPU Auslastung nach oben geht? Oder läuft dein RAM irgendwann voll? Kannst du mal testen was passiert, wenn du die AddSymbol Procedure direkt integrierst und das Kopieren des Bitmaps in den var Parameter auslässt und stattdessen direkt auf das Canvas malst? |
AW: Systemressourcen erschöpft beim VirtualStringTree.AfterCellPaint
Zitat:
Tatsächlich wird die bmp lokal deklariert. (Hab das oben korrigiert) Zitat:
Zitat:
Zitat:
Also im Mainthread verwende ich definitiv nur 1 Connection. Ich muss mal die Threads deaktivieren. Möglicherweise stammt die zweite Connection ja von einem der Threads (die eigentlich alle vor Programmende korrekt beendet werden). Zitat:
Zitat:
|
AW: Systemressourcen erschöpft beim VirtualStringTree.AfterCellPaint
Hmm..
Zitat:
Wieso ist deine TDBAccess eigentlich komplett 'class xxx'. Nimm doch mal hier das 'class' vor allen Funktionen weg und mach Dir ein 'echtes' Objecte im Create vom MainForm... Vielleicht bringt dass das Speichermanagment etwas durcheinander und somit auch FMM.. (Nur so eine Idee von jemandem der 'class var .. ' nicht mag ;) ) |
AW: Systemressourcen erschöpft beim VirtualStringTree.AfterCellPaint
Zitat:
Wenn die korrekt ist, wundert mich das nicht. Klassen-Konstruktoren werden bei Bedarf automatisch ausgeführt. Aber in dieser Klasse wird der Klassen-Konstruktor nochmal manuell aufgerufen. Die Folge sind dann offensichtlich zwei Connections. Ich habe mit Klassen-Konstruktoren noch nicht gearbeitet und komme nur durch die Delphi-Doku auf diese Idee. ![]() |
AW: Systemressourcen erschöpft beim VirtualStringTree.AfterCellPaint
Ich bin jetzt ein kleines Stück weiter. Habe mal alles deaktiviert, alle Threads, so dass am Ende nur der Mainthread über bleibt. Sprich die DB wird geöffnet und bei Programmende wieder geschlossen, ohne dass DB-Abfragen dazwischen laufen. Ergebnis: Keine MemoryLeaks.
Jetzt fange ich an, alles Stück für Stück wieder zu aktivieren, und teste jedes Mal ob und wo ein MemoryLeak auftritt. Ich bin auch gleich fündig geworden in folgende Methode:
Delphi-Quellcode:
Kommentiere ich die Zeile SaveToStream aus, so habe ich kein MemoryLeak. Lass ich sie mit ausführe, habe ich ein MemoryLeak.
procedure TStamminfos.LoadFromDBByRev(Con: TUniConnection; Betriebguid,
Refguid: TGUID; Ref: string); var q: TUniQuery; begin q:=TUniQuery.Create(nil); try q.Connection:=Con; if not Con.Connected then exit; q.SQL.Text:='Select * from stamminfos where betriebguid=:betriebguid and (Refguid=:refguid or ref=:ref)'; q.Params.ParamValues['betriebguid']:=Betriebguid.ToString; q.Params.ParamValues['Refguid']:=Refguid.ToString; q.Params.ParamValues['Ref']:=Ref; q.Active:=True; if q.RecordCount>0 then begin Self.ID:=q.FieldByName('ID').AsInteger; Self.guid:=StringToGUID(q.FieldByName('guid').AsString); Self.betriebguid:=StringToGUID(q.FieldByName('betriebguid').AsString); Self.Refguid:=StringToGUID(q.FieldByName('Refguid').AsString); Self.Ref:=q.FieldByName('Ref').AsString; Self.RTFText.Position:=0; (q.FieldByName('RTFText') as TBlobField).SaveToStream(Self.RTFText); //<<--Hier muss der Übeltäter liefen. Self.RTFText.Position:=0; Self.Text:=q.FieldByName('Text').AsString; Self.Level:=TInfosLevel(q.FieldByName('Level').AsInteger); end; q.active:=False finally q.Free; end; end; Ist das Auslesen eines BlobFields denn so falsch? (Self.RTFText ist TMemoryStream, welches auch korrekt erzeugt und freigegeben wird). Gibt es noch eine andere (bessere) Möglichkeit den Inhalt von BlobFiels auszulesen? Ich kenn nur die. PS: Die o.g. Methode läuft in einem Thread, der seine eigene Connection hat. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 20:16 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz