Objekt sich selbst freigeben lassen |
|
| System | Win9x, WinNT, Win2000, WinXP, Vista, Win7 |
|---|---|
| Ab Delphi-Version | Delphi 1 |
| Letzte Änderung | 21.01.2012 |
Manchmal möchte man, dass sich ein Objekt gewissermaßen selbst freigibt. Beispielsweise könnte sich ein Button bei einem Klick selbst entfernen.Das Problem ist hierbei, dass es zu einer Zugriffsverletzung kommen kann, wenn sich ein Objekt in seiner eigenen Methode freigibt: Nämlich dann, wenn nach der Methode auf Felder des freigegebenen Objekts zugegriffen werden. Da man selbst ja nie wissen kann, was intern nach z.B. einem Event abläuft, kann es sehr riskant sein, wenn man in so einem Event das Objekt, welches es ausgelöst hat, freigeben will. Es sei denn, man ist sich absolut sicher, dass nach dem Free keine Zugriffe mehr auf Felder des Objekts stattfinden. Dies ist aber nicht zu empfehlen und gilt als sehr unsauber.
Mit dieser Unit kann man dem entgegenwirken: Ein Objekt wird mit PostFree freigegeben. Aber nicht sofort - erst dann, wenn alle nach dem PostFree folgenden Routinen fertig sind. Außerdem kann man verhindern, dass eine Zugriffsverletzung auftritt, wenn ein Objekt zweimal über PostFree desselben FreeManagers freigegeben wird.
Warnung: Ein Aufruf der Prozedur Application.ProcessMessages bringt den FreeManager dazu, vorzeitig alle zu zerstörenden Objekte freizugeben. Ein Aufruf dieser Prozedur sollte also nicht nach einem PostFree innerhalb der selbigen Methode stattfinden. Darum ist es besser, PostFree erst am Ende der Methode des zu zerstörenden Objekts aufzurufen.
Außerdem muss beachtet werden, dass diese Unit nicht Thread-safe ist!
Damit sich ein Objekt selbst freigeben kann, muss sich die Unit "hdFreeUtilities" zunächst im Delphi-Suchpfad befinden. Um die Unit dann benutzen zu können, muss sie in der Uses-Deklaration der aufrufenden Unit referenziert sein.Dann kann ein Objekt einfach mit der Funktion PostFree freigegeben werden.Beispiel:
uses hdFreeUtilities;
[...]
procedure TForm1.Button1Click(Sender: TObject);
begin
PostFree(Sender); //Entfernt den Butten, der diese Methode aufruft
end;
Folgende Unit muss einfach kopiert und als "hdFreeUtilities.pas" gespeichert werden:
interface
uses
Messages, Windows, Classes;
const
// Nachricht, die den FreeManager dazu veranlasst, ein Objekt freizugeben
WM_FREE_OBJECT = WM_USER + 0;
type
// Der FreeManager - Eine Instanz wird bereits bei Unit-Initialisierung erzeugt.
TFreeManager = class(TObject)
private
FFreeList: TList;
FWndHandle: HWND;
protected
procedure WndProcFreeObj(var Msg: TMessage); message WM_FREE_OBJECT;
public
function PostFree(AObject: TObject): Boolean;
procedure FreeObjects;
constructor Create();
destructor Destroy; override;
end;
// Alle hiervon abgeleiteten Objekte können sich über die eigene Methode PostFree selbst freigeben
TOwnFreeObject = class(TObject)
public
procedure PostFree;
end;
// Postfree kann auch unabhängig von bereits verwendeten FreeManagern benutzt werden:
// es wird dann der Unit-lokale FreeManager verwendet
function PostFree(AObj: TObject): Boolean;
implementation
var
FreeManager: TFreeManager; // Lokaler FreeManager
///
/// Diese Funktion gibt das übergebene Objekt nach einer gewissen Zeit frei.
///
/// TFreeManager.PostFree
function PostFree(AObj: TObject): Boolean;
begin
Result := FreeManager.PostFree(AObj);
end;
{ TFreeManager }
constructor TFreeManager.Create;
begin
inherited;
FFreeList := TList.Create();
FWndHandle := Classes.AllocateHWnd(WndProcFreeObj);
end;
destructor TFreeManager.Destroy;
begin
FreeObjects;
FFreeList.Free;
Classes.DeallocateHWnd(FWndHandle);
inherited;
end;
///
/// Mit dieser Funktion wird ein angegebenes Objekt nach einer gewissen Zeit freigegeben.
/// Ein Objekt kann sich so also in seiner eigenen Routine freigeben,
/// allerdings wird das Objekt dann erst freigegeben, wenn alle Routinen abgearbeitet wurden.
///
/// Das freizugebende Objekt
///
/// False, wenn das Objekt in diesem FreeManager bereits freigegeben wurde
/// True, wenn alles erfolgreich war
///
function TFreeManager.PostFree(AObject: TObject): Boolean;
begin
Result := Assigned(AObject) and (FFreeList.IndexOf(AObject) < 0);
if Result then
begin
FFreeList.Add(AObject);
PostMessage(FWndHandle, WM_FREE_OBJECT, integer(AObject), 0);
end;
end;
///
/// Die Warteliste wird abgearbeitet. Diese Prozedur muss nur dann aufgerufen werden,
/// wenn das Programm entweder eine Konsolen-Anwendung ist, oder das über PostFree angegebene Object
/// vorzeitig zerstört werden muss.
///
procedure TFreeManager.FreeObjects;
var
i: Integer;
begin
for i := 0 to FFreeList.Count - 1 do
TObject(FFreeList[i]).Free;
FFreeList.Clear;
end;
///
/// Hier wird die asynchrone Nachricht empfangen und das angegebene Objekt freigegeben.
///
procedure TFreeManager.WndProcFreeObj(var Msg: TMessage);
var
Index: Integer;
AObject: TObject;
begin
AObject := TObject(msg.WParam);
Index := FFreeList.IndexOf(AObject);
if (Index > -1) then
begin
FFreeList.Delete(Index);
AObject.Free;
end;
end;
{ TOwnFreeObject }
///
/// Über diese Prozedur kann sich das von TOwnFreeObject abgeleitete Objekt selbst freigeben.
///
procedure TOwnFreeObject.PostFree;
begin
FreeManager.PostFree(Self)
end;
///
/// Initialierung und finalisierung des lokalen FreeManagers
///
initialization
FreeManager := TFreeManager.Create();
finalization
FreeManager.Free;
end.