![]() |
Programm abrupt beenden ohne Memory Leaks
Hallo.
Mein Programm benutzt Zipping und Verschlüsselungsroutinen. Dafür nutze ich die Units KAZip, DEC (enthält viele große Units) und ZLib (von Borland). Es kann vorkommen, dass der Endbenutzer auf die Idee kommt, ein großes Verzeichnis oder ein ganzes Laufwerk zu zippen und diese Aktion wieder abbrechen möchte, da sie zu lange dauert. Leider beachten die oben genannten Units nicht Application.Terminated, weswegen ich ziemliche Probleme bei der Terminierung der Applikation habe. Da mein Programm keine Enddateien überschreibt (sondern vorher im Tempverzeichnis puffert) und bei meinem Programm keine Benutzerdaten bzw. getane Arbeit verloren gehen können, würde ich gerne dem Benutzer die Möglichkeit geben, das Programm ohne Strg+Alt+Entf zu beenden. Zur Demonstration sei folgende Methode gegeben:
Delphi-Quellcode:
Diese Funktion soll durch die Unendlichschleife eine lang andauernde ZIP/Verschlüsselungsfunktion darstellen. Es sei gegeben, dass ich auf diese Funktion nicht zugreifen oder sie verändern kann (ich habe keine Lust, DEC, KAZIP und ZLIB für application.terminated umzuschreiben).
procedure kann_lange_dauern;
begin repeat application.processmessages; until false; end; Ich habe folgende Ideen gehabt und versucht: 1. Idee: Ressourcen freigeben und Exceptions verschlucken Im Prinzip hat es funktioniert, aber es ist sehr unprofessionell, da ich Exceptions verschlucke.
Delphi-Quellcode:
Da hier Exceptions verschluckt werden und es interne Exceptions gibt, ist diese Variante extrem unprofessionell.
var
mystream: TStream; procedure TForm1.Button1Click(Sender: TObject); begin try // Wird bei Programmende durch eine Exception beendet, da wir MyStream freigeben. kann_lange_dauern(mystream); except end; end; procedure TForm1.OnClose(Sender: TObject; var Action: TCloseAction); begin try mystream.free; except end; end; 2. Idee: Threads verwerden Jetzt bin ich auf die Idee gekommen, Threads zu verwenden. Ich habe für jede ZIP/DEC-Aktion eine Thread-Klasse geschrieben, die sich den Thread bei Programmende automatisch per ExitThread bzw ExitProcess killt. Leider entstehen durch das abrupte Abbrechen des Threads MemoryLeaks.
Delphi-Quellcode:
3. Idee: ExitProcess verwenden
type
TKaZipExtract = class(TThread) private FExecuteFinished: boolean; FKaZIP: TKaZIP; FZipFile: string; FDestination: string; protected procedure Execute; override; public constructor Create; virtual; function RunAndFree(const KaZIP: TKaZIP; const ZipFile, Destination: string): boolean; end; { --- --- --- TKaZipExtract --- --- --- } procedure TKaZipExtract.Execute; begin FKaZIP.close; if fileexists(FZipFile) then begin FKaZIP.Open(FZipFile); try FKaZIP.ExtractAll(FDestination); finally FKaZIP.Close; end; end; FExecuteFinished := true; end; constructor TKaZipExtract.Create; begin inherited create(true); end; function TKaZipExtract.RunAndFree(const KaZIP: TKaZIP; const ZipFile, Destination: string): boolean; begin FKaZIP := KaZIP; FZipFile := ZipFile; FDestination := Destination; FExecuteFinished := false; try if Suspended then Resume; repeat Application.ProcessMessages; until FExecuteFinished or Application.Terminated or Terminated; result := not (Application.Terminated or Terminated); if not result then ExitProcess(handle); // Vorher: ExitThread finally free; end; end; function KaZIPExtract(const KaZIP: TKaZIP; const ZipFile, Destination: string): boolean; var tmp: TKaZipAdd; begin tmp := TKaZipAdd.Create; result := tmp.RunAndFree(KaZIP, ZipFile, Destination); end; Ich kam auf die Idee, das gesamte Programm durch ExitProcess bei dem Event TForm1.OnClose zu beenden. Ich habe außerdem geprüft, ob MemoryLeaks dabei entstehen. Seltsamerweiße hat mir weder der BDS 2006 Memory Manager noch FastMM ein MemoryLeak gemeldet. Test mit halt(), das ExitProcess gleicht.
Delphi-Quellcode:
Test mit ExitProcess:
procedure TForm1.OnCreate(Sender: TObject);
begin ReportMemoryLeaksOnShutDown := true; // ab BDS 2006 TObject.Create; // Provozierter Memory Leak end; procedure TForm1.Button1Click(Sender: TObject); begin kann_lange_dauern(); end; procedure TForm1.OnClose(Sender: TObject; var Action: TCloseAction); begin halt; // Warnung bzgl. Memory Leaks end;
Delphi-Quellcode:
Da ich bei ExitProcess keine Memory-Leak-Warnung erhielt, dachte ich, dass diese WinAPI-Methode den Arbeitsspeicher automatisch aufräumt. Auch bei MSDN (
procedure TForm1.OnCreate(Sender: TObject);
begin ReportMemoryLeaksOnShutDown := true; // ab BDS 2006 TObject.Create; // Provozierter Memory Leak end; procedure TForm1.Button1Click(Sender: TObject); begin kann_lange_dauern(); end; procedure TForm1.OnClose(Sender: TObject; var Action: TCloseAction); begin ExitProcess(ExitCode); // KEINE Warnung bzgl. Memory Leaks end; ![]() 4. Idee: Den Benutzer fragen, ob er MemoryLeaks will Da ich nun absolut keine Ideen habe, muss ich wohl den Benutzer beim OnCloseQuery-Ereignis fragen, ob er den laufenden Prozess wirklich beenden möchte und das Risiko eingeht, dass Rückstände im Arbeitsspeicher bleiben könnten.
Delphi-Quellcode:
Hat irgendjemand eine bessere Idee, wie ich mein Programm abrupt ohne Memory Leaks beenden kann? :|
procedure TForm1.OnCloseQuery(Sender: TObject; var CanClose: Boolean);
var res: integer; begin if programm_arbeitet then begin res := Application.MessageBox('Warnung', 'Das Programm arbeitet derzeit. Möchten Sie den Prozess beenden und die Gefahr auf sich nehmen, dass Rückstände im Arbeitsspeicher verbleiben?', MB_YESNO + MB_ICONEXCLAMATION); CanClose := res = ID_YES; end else begin CanClose := true; end; end; procedure TForm1.OnClose(Sender: TObject; var Action: TCloseAction); begin ExitProcess(ExitCode); end; Gruß blackdrake |
Re: Programm abrupt beenden ohne Memory Leaks
über memory-leaks solltest du dir unter xp und vista keine Gedanken machen. denn sobald dein Prozess beendet ist/wird werden auch alle Ressourcen welche dein Programm in Anspruch genommen hat mit frei gegeben.
|
Re: Programm abrupt beenden ohne Memory Leaks
Hallo.
Habe ich mir schon gedacht. Aber das Programm sollte ja auch unter 9x laufen, ohne den Arbeitsspeicher zuzumüllen. Und Memory Leaks sollte man stets vermeiden ( ![]() Gruß blackdrake |
Re: Programm abrupt beenden ohne Memory Leaks
100MB bei Win9X im Arbeitspspeicher war aber ein riesen Kramp, ich glaub da hängt das Programm so schon sehr. Aber Löglich das es noch Leute gibt die auf MemoryLeaks extrem achten.
|
Re: Programm abrupt beenden ohne Memory Leaks
Zitat:
hier noch einer :zwinker: :cheers: :mrgreen: |
Re: Programm abrupt beenden ohne Memory Leaks
Zitat:
Wo finde ich heraus, welche Windows Versionen denn genau dieses Feature mit dem RAM-Freigeben unterstützen? Hat Windows Server 2003 dieses Feature? Und hat NT dieses Feature echt nicht gekannt? Ist die Behauptung korrekt, dass alle Windows-Versionen >= 5.x ( ![]() Natürlich ist es ein wenig unprofessionell, wenn das Programm bei 9x nachfragt, ob der Benutzer beim Beenden ein MemoryLeak in Kauf nehmen will. Würde ich eine solche Frage gestellt bekommen, würde ich das bedenklich finden. Gruß blackdrake |
Re: Programm abrupt beenden ohne Memory Leaks
Zitat:
auch wenn das eine schöne Sache ist, finde ich, dass es ein schönerer Stol ist, wenn man sein Zeugs, was man erstellt hat, wieder freigibt. |
Re: Programm abrupt beenden ohne Memory Leaks
Zitat:
|
Re: Programm abrupt beenden ohne Memory Leaks
alle im den DEC Klassen enthaltenen Methoden die länger dauern können, zb.
THash_XYZ.CalcStream(), .CalcFile() TCipher_XYZ.Encode/DecodeStream/File unterstützen einen Progress -> IDECProgress Interface. Du deklarierst in deinem TForm zb. sowas
Delphi-Quellcode:
und kannst du dein Application.ProcessMessages reinbauen. Übrgeben bekommst du Min,Max und Pos das sind die Größen und Datezeiger/Streamtzeiger.
type
TForm1 = class(TForm, IDECProgress) procedure Process(const Min,Max,Pos: Int64); stdcall; end; Wenn du dem Anwender die Möglichkeit geben möchtest eine Aktion zu unterbrechen dann musst du 1.) ein Variable FAbort:Boolean in dein TForm einbauen 2.) deine Methode die alles macht per try except Block schützen und auf EAbort reagieren 3.) einen Button auf's TForm der FAbort auf TRUE setzt 4.) in deiner Methode .Process() bei FAbort = TRUE die Prozedure Abort aufrufen Es ist also so das alle anderen deiner Tools das eventuell nicht unterstützen, aber DEC tut es mit Sicherheit ermöglichen und das sehr einfach indem du nur eine Methode eines Interfaces implementierst. Beim Aufruf einer der obigen DEC Klassen Methoden übergibts du dann einfach als letzten Parameter Self. ![]() Gruß Hagen |
Re: Programm abrupt beenden ohne Memory Leaks
Zitat:
Normalerweise wird ein Programmierer eben nicht über Application.ProcessMessages eine parallele Abarbeitung des Messagesques anstoßen und deshalb ist der Aufruf von Application.Terminated meistens auch überflüssig. Du musst halt bedenken, Application.ProcessMessages ist es die in deinem TForm beim Button1.Click die OnClick() Methode aufruft. Wird darin nun selber in einer Schleife Application.ProcesssMessages aufgerufen dann hast du defakto sowas wie ein fast fertige "Endlosrekursion" programmiert. Nur ist diese nicht so offensichtlich da hier VCL+Events+Messages von Windows involviert sind. Hast du zb. eben nicht deinen Button disabled so kann der Benutzer diese erneut drücken und damit landet man wieder im OnClick() obwohl du noch im OnClick() in der .ProcessMessages Loop drinnen bist. Es ist nicht reentrant in diesem Moment und das hat arge Seiteneffekte. Zb. du greifts in deiner OnClick() + .Processmessages Loop auch auf Objecte des TForms zu. Nun, wenn .ProcessMessages aufgerufen wird un der Benutzer drückt den Close-Button des Fensters in der Titelleiste dann wird das TForm zerstört. Aber der Programcounter ist immer noch in deiner Schleife die auf Daten dieses Forms zugreift, bumms Exception. Das was du also vorhast ist im Grunde schlecht, aber relativ gesehen noch Bedienersicher zu bekommen. 1.) eine OnClick() muß reentrant werden, zb. durch Disablen des Buttons oder durch ein Locking 2.) OnCloseQuery des Form benutzen und dort erst dann TRUE zurückliefern wenn deine Berechnungsfunktion/Loop beendet wurde. die beste Methode wären aber Threads und solche Callbacks wie in meinem DEC. Man startet einen Thread und alle längerdauernen Operationen rufen eine Callback wie im DEC auf. In dieser wird überprüft ob der Thread terminiert werden soll, also Self.Terminated, und wenn ja dann wird eine stille Exception EAbort ausgelösst. Im Mainpart des Threads wurde alles per try except gekapselt. Die ZLib, KAZIP Methoden die per TStream Descants arbeiten kannst du ohne Änderungen am Originalsource mit einer solchen Callback versehen. Dazu gibts hier im Forum einen TStreamProgressAdapter oä. Dieser wird quasi zwischen den eigentlichen Stream zu deinen Dateien zwischen-geschaltet und als Parameter diesen Bibliotheken übergeben. Beim DEC geht das auch ist aber nicht notwendig. ![]() Gruß Hagen |
Re: Programm abrupt beenden ohne Memory Leaks
Hallo.
Zitat:
Zitat:
Delphi-Quellcode:
Ich verstehe aber nicht ganz, was du mit Punkt #2 meinst. Wie soll ich auf FAbort reagieren? Wie bekomme ich es hin, dass FAbort dazu führt, dass TCipher_xyz.EncodeFile etc. abbricht? Die einzigste Methode die mir einfällt, ist den Stream freizugeben und die Exceptions zu verschlucken (siehe Idee #1 ganz oben). Das scheint mir aber ziemlich unprofessionell, weil ich Speicher freigebe, der noch genutzt wird, um so die laufende Aktion zu terminieren. Außerdem verschlucke ich Exceptions (siehe
TProgress = class(TInterfacedObject, IDECProgress)
procedure Process(const Min, Max, Pos: Int64); stdcall; constructor Create; destructor Destroy; override; end; ![]() Das mit den TStream Descants verstehe ich auch nicht ganz. Das es eine Callback-Funktion gibt, verstehe ich. Aber wie gehe ich bei diesem Descants bei einer Termination vor? Gebe ich dann auch den Stream einfach frei und verschlucke die Exceptions, wenn ich eine Termination des Programmes wünsche? Sobald ich wieder zuhause bin, probiere ich mal mit Delphi das ganze aus. Nachtrag: Zitat:
Zitat:
Gruß blackdrake |
Re: Programm abrupt beenden ohne Memory Leaks
Die Sache ist wirklich sehr einfach.
Delphi-Quellcode:
Wenn FAction == 0 ist dann kann der Anwender beim KLick auf Button eine Datei verschlüsseln. In diesem Moment wird FAction == 1 sein. Über die .Process() Methode, quasi eine Callback, ruft nun der Cipher periodisch auf. Darin wird Application.ProcessMessages; aufgrufen, der User kann also den Button nochmals drücken, dessen Caption zeigt ja Abort an. Drück er nochmals drauf dann landet man im .OnClick() auf Grund der Anfrag if FAction = 0 then eben nicht in der Verschlüsselung sondern beim Inc(FAction). Aus FAction == 1 wird dann FAction > 1. Sobald nun FAction > 1 ist wird in der .Process() Callback eine Exception ausgelösst, eben Abort eine stille Exception. Da der Cipher intern dieses .Process() aufruft und wir dadrinnen eine Exception auslössen wird Cipher.EncodeFile() verlassen, abgebrochen. Man landet dann im finally end Block und von dort im except end Block in dem wir auf EAbort gezielt reagieren.
type
TForm1 = class(TForm, IDECProgress) procedure ButtonClick(Sender: TObject); procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean); private FAction: Integer; procedure Process(const Min,Max,Pos: Int64); stdcall; end; procedure TForm1.ButtonClick(Sender: TObject); begin if FAction = 0 then try Inc(FAction); Button.Caption := 'Abort'; try with TCipher_Blowfish.Create do try Init(...); EncodeFile(FileName, FileName, Self); finally Free; end; except on E: Exception do if E is EAbort then ShowMessage('aborted by User') else reraise; end; finally FAction := 0; Button.Caption := 'Start'; end else Inc(FAction); end; procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin CanClose := FAction = 0; if not CanClose then Inc(FAction); end; procedure TForm1.Process(const Min,Max,Pos: Int64); begin Application.ProcessMessages; if FAction > 1 then Abort; end; So sollte man mit Schutzblöcken und gezielter Exceptionbehandlung in Delphi arbeiten. In OnCloseQuery fragen wir noch ab ob gerade FAction = 0 ist, also keine Aktion gerade läuft. Wenn nicht dann lösen wir auch hier einen Abort indirekt aus. Der Benutzer muß dann meistens 2 mal den Closebutton der Titelzeile klicken. Ziemlich simpel die Sache, oder ? Wenn du jetzt statt dem TForm ein TThread benutzt dann müsste nur FAction entfernt werden und .Process() kein Application.ProcessMessages drinnen stehen. Dafür aber
Delphi-Quellcode:
.
if Terminated then Abort;
Gruß Hagen |
Re: Programm abrupt beenden ohne Memory Leaks
Hallo Hagen.
Danke für die schnelle und ausführliche Antwort. Ich probiere es zuhause gleich aus. Anschließend versuche ich es noch mit den restlichen ZIP/Compress-Funktionen mittels der TStream-Descants und melde mich, ob es funktioniert hat oder nicht. Gruß blackdrake |
Re: Programm abrupt beenden ohne Memory Leaks
Also DEC hält sich strikt intern daran und verwendet immer schön try finally end; Schutzblöcke wenn es Resource alloziert hat. Es ist jetzt für dich nur wichtig nachzuschauen ob das die beiden anderen Libs auch so handhaben. Wenn ja, können bei dieser Methode keine Memoryleaks entstehen, der User hat die Kontrolle die Aktion abzubrechen, und die Anwendung wird ganz sauber beendet so wie vorgesehen.
Keine Holzhammer Methode mehr notwendig indem du einen Thread oder Prozess abschießt. Auch die Anwendung bleibt weiter quasi bedienbar. Einzigstes Problem, sollte der Anwender zb. in die Titelzeile klicken und die Maustaste gedrückt lassen so wird der aktuelle Verschl. Vorgang temporär angehalten. Das kannst du nur mit Threads umgehen. Übrigens durch die if FAcxtion = 0 then Abfrage wird die OnClick Methode eben reentrant, sie kann quasi rekusiv merhfach afgerufen werden. Gruß Hagen |
Re: Programm abrupt beenden ohne Memory Leaks
Hallo.
Bei DEC hat es mit abort; und dem Progress-Interface sehr gut funktioniert. Aber bereits bei ZLib klappt es gar nicht:
Delphi-Quellcode:
Ich habe bereits versucht, den Adapter alternativ für CompressOutputStream oder CompressionStream zu verwenden. Bei CompressionStream gab es eine Stream-Operation-Exception. CompressInputStream müsste jedoch laut des Beispielprogrammes die korrekte Wahl sein. Wenn ich jetzt meine Anwendung schließe, scheint es einen Dead-Lock zu geben. Die Progressbar bewegt sich nicht mehr, aber die Methode CompressFile() wird nicht über die Exception verlassen, sondern bleibt aktiv. :(
procedure TForm1.zlib_progress(Sender: TObject; tag: Integer; Percent: Integer);
begin pbr_progress.position := Percent * pbr_progress.max div 100; application.processmessages; if application.Terminated then SysUtils.abort; end; procedure CompressFile(InputFileName, OutputFileName: string; Progress: TOnProgress); var CompressInputStream: TStreamProgressAdapter; // TFileStream CompressOutputStream: TFileStream; CompressionStream: ZLib.TCompressionStream; begin try CompressInputStream := TStreamProgressAdapter.Create(TFileStream.Create(InputFileName, fmOpenRead), 0); CompressInputStream.OnProgress := Progress; try CompressOutputStream := TFileStream.Create(OutputFileName, fmCreate); try CompressionStream := TCompressionStream.Create(clMax, CompressOutputStream); try CompressionStream.CopyFrom(CompressInputStream, CompressInputStream.Size); finally CompressionStream.Free; end; finally CompressOutputStream.Free; end; finally CompressInputStream.Free; end; except on E: Exception do if not (E is EAbort) then raise; end; end; begin CompressFile('...', '...', Form1.zlib_progress); end. Die Methode CompressFile() würde erst durch die EAbort-Exception verlassen werden, wenn ich direkt vor CompressionStream.CopyFrom ein "abort;" setzen würde. Irgendwie wird das Abort nicht weitergereicht. Gruß blackdrake |
Re: Programm abrupt beenden ohne Memory Leaks
Ach mann, nimm das blöde Application.Terminate raus und baue die ButtonOnClick() Methode wie in meinem obigen Beispiel auf. Statt .Process() vom IDECPrograess Interface dann das Gleiche mit dem OnProgress Event des Streams. Im ButtonClick() dann statt meinem TCipher Object eben deine CompressFile() Funktion aufrufen.
Gruß Hagen |
Re: Programm abrupt beenden ohne Memory Leaks
Hallo.
Habe jetzt endlich wieder Zeit gefunden, mich mit dem Problem zu beschäftigen und konnte es lösen. Außerdem verstehe ich nun die genaue Funktionsweise von Abort(). Der Aufruf von Abort() führt zu der Exception EAbort, die dann durchgeschleift wird und alle Aktionen der Methoden in der Aufrufhierarchie beendet bzw. zu deren except Teil der try..except's führt. In meinem letzten Post war die Progress-Methode nicht innerhalb der Hierarchie und deswegen schlug Abort() fehl (glaube ich). @negaH: Bei der Entwicklung vermeide ich es in der Regel, zu viele Baustellen auf einmal aufzumachen. Deswegen war in meinem vorherigen Code noch Application.Terminated anstatt WartenForm.FAbort. Bei meinem Programm hat der Verschlüsselungsbutton keine Re-Entranz, da das Form beim Klicken unsichtbar wird und ein Wartenformular mit einem Abbrechenbutton erscheint. Da Start/Stop in sofern auf 2 Buttons aufgeteilt ist, ist FAction unnötig. Für die, die eventuell das selbe Problem haben, füge ich die Lösung nun an. KaZIP selbst stellt keine Streams zur Verfügung. Doch nach kurzer Überlegung mit ich auf die Idee gekommen, Abort() in die On(De)Compress/OnAdd-Methoden von KaZIP zu packen:
Delphi-Quellcode:
Um die ZLib Aktionen abzubrechen, habe ich die Progress-Methode in eine eigene Klasse gepackt:
procedure TMainForm.kazip_add(Sender: TObject; ItemName: string);
begin application.processmessages; if WartenForm.FAbort then sysutils.Abort; end;
Delphi-Quellcode:
Die Anwendung:
type
TZLIBProgress = class(TObject) // Ist die Ableitung von TObject eigentlich hierfür sinnvoll? public procedure Progress(Sender: TObject; tag: Integer; Percent: Integer); constructor Create; destructor Destroy; override; end; { ************************ TZLIBProgress ************************ } procedure TZLIBProgress.Progress(Sender: TObject; tag: Integer; Percent: Integer); begin WartenForm.pbr_progress.Position := Percent * WartenForm.pbr_progress.max div 100; Application.ProcessMessages; if WartenForm.FAbort then SysUtils.abort; end; constructor TZLIBProgress.Create; begin inherited Create; end; destructor TZLIBProgress.Destroy; begin inherited Destroy; end;
Delphi-Quellcode:
Danke für euere Ratschläge! :-D
function DeCompressFile(InputFileName, OutputFileName: string): boolean;
var CompressInputStream: TFileStream; CompressOutputStream: TStreamProgressAdapter; DeCompressionStream: ZLib.TDeCompressionStream; CProgress: TCommonProgress; Buf: array[0..4095] of Byte; Count: Integer; begin result := true; CompressInputStream := TFileStream.Create(InputFileName, fmOpenRead); try CompressOutputStream := TStreamProgressAdapter.Create(TFileStream.Create(OutputFileName, fmCreate), 0); try DecompressionStream := TDecompressionStream.Create(CompressInputStream); try CProgress := TCommonProgress.Create; try try CompressOutputStream.OnProgress := CProgress.Progress; while true do begin Count := DeCompressionStream.Read(Buf[0], SizeOf(Buf)); if Count = 0 then break else CompressOutputStream.Write(Buf[0], Count); end; except on E: Exception do begin result := false; if not (E is EAbort) then raise; end; end; finally CProgress.Free; end; finally DecompressionStream.Free; end; finally CompressOutputStream.Free; end; finally CompressInputStream.Free; end; end; function CompressFile(InputFileName, OutputFileName: string): boolean; var CompressInputStream: TStreamProgressAdapter; CompressOutputStream: TFileStream; CompressionStream: ZLib.TCompressionStream; CProgress: TCommonProgress; begin result := true; CompressInputStream := TStreamProgressAdapter.Create(TFileStream.Create(InputFileName, fmOpenRead), 0); try CompressOutputStream := TFileStream.Create(OutputFileName, fmCreate); try CompressionStream := TCompressionStream.Create(clMax, CompressOutputStream); try CProgress := TCommonProgress.Create; try try CompressInputStream.OnProgress := CProgress.Progress; CompressionStream.CopyFrom(CompressInputStream, CompressInputStream.Size); except on E: Exception do begin result := false; if not (E is EAbort) then raise; end; end; finally CProgress.Free; end; finally CompressionStream.Free; end; finally CompressOutputStream.Free; end; finally CompressInputStream.Free; end; end; Gruß blackdrake [edit=SirThornberry]Code-Highlighting korrigiert - Mfg, SirThornberry[/edit] |
Alle Zeitangaben in WEZ +1. Es ist jetzt 21:19 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