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:
procedure kann_lange_dauern;
begin
repeat
application.processmessages;
until false;
end;
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).
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:
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;
Da hier Exceptions verschluckt werden und es interne Exceptions gibt, ist diese Variante extrem unprofessionell.
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:
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;
3. Idee: ExitProcess verwenden
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:
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;
Test mit ExitProcess:
Delphi-Quellcode:
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;
Da ich bei ExitProcess keine Memory-Leak-Warnung erhielt, dachte ich, dass diese
WinAPI-Methode den Arbeitsspeicher automatisch aufräumt. Auch bei
MSDN (
http://msdn2.microsoft.com/en-us/library/ms682658.aspx ) wird bei ExitProcess nicht vor eventuell auftretenden MemoryLeaks gewarnt. Laut MemProof soll mein Programm durch ExitProcess(0) über 1000 MemoryLeaks besitzen. Ob Windows nun automatisch bei ExitProcess den Arbeitsspeicher aufräumt, weiß ich leider nicht.
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:
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;
Hat irgendjemand eine bessere Idee, wie ich mein Programm abrupt ohne Memory Leaks beenden kann?
Gruß
blackdrake