Einige kennen doch bestimmt das Problem, daß wenn sich Interfaces gegenseitig referenzieren,
daß diese, selbst bei Freigabe aller externen Referenzen, sich dennoch nicht freigeben, da sie sich ja gegenseitig halten.
Delphi-Quellcode:
type
TObjektMaster = Class(InterfacedObject);
FSlave: IInterface;
End;
TObjektSlave = Class(InterfacedObject);
FMaster: IInterface;
End;
Nja, da ich hiermit immer mal wieder Probleme hatte und deswegen oftmals sogar auf Interfaces verzichtete,
wollte ich endlich mal dafür eine befriedigende Lösung finden.
Erstmal 2 "einfache" Varianten, bei welchen man Referenzen registrieren kann, welche praktisch nicht gezählt werden sollen.
Delphi-Quellcode:
type
IXRefInterface = Interface
// register after set to the reference variable
// unregister before free the reference variable
Procedure RegisterXRef;
Function UnregisterXRef: Boolean; // FALSE if the object was destroyed
End;
TXRefInterfacedObject = Class(TObject, IInterface, IXRefInterface)
Protected
{IInterface}
Function QueryInterface(Const IID: TGUID; Out Obj): HResult; StdCall;
Function _AddRef: Integer; StdCall;
Function _Release: Integer; StdCall;
Protected
{IXRefInterface}
FRefCount, FXRefs: Integer;
Procedure RegisterXRef;
Function UnregisterXRef: Boolean; // FALSE if the object was destroyed
Procedure DoFreeXRefs; Virtual;
Public
Procedure AfterConstruction; Override;
Procedure BeforeDestruction; Override;
Class Function NewInstance: TObject; Override;
Property RefCount: Integer Read FRefCount;
End;
Function TXRefInterfacedObject.QueryInterface(Const IID: TGUID; Out Obj): HResult;
Begin
If GetInterface(IID, Obj) Then Result := S_OK
Else Result := E_NOINTERFACE;
End;
Function TXRefInterfacedObject._AddRef: Integer;
Begin
Result := InterlockedIncrement(FRefCount);
End;
Function TXRefInterfacedObject._Release: Integer;
Begin
Result := InterlockedDecrement(FRefCount);
If Result = FXRefs Then Destroy;
End;
Procedure TXRefInterfacedObject.RegisterXRef;
Begin
InterlockedIncrement(FXRefs);
End;
Function TXRefInterfacedObject.UnregisterXRef: Boolean;
Begin
InterlockedDecrement(FXRefs);
Result := FRefCount <> FXRefs;
If not Result Then Destroy;
End;
Procedure TXRefInterfacedObject.DoFreeXRefs;
Begin
// do nothing
End;
Procedure TXRefInterfacedObject.AfterConstruction;
Begin
// release the constructor's implicit refcount
InterlockedDecrement(FRefCount);
End;
Procedure TXRefInterfacedObject.BeforeDestruction;
Begin
If FRefCount <> FXRefs Then Error(reInvalidPtr);
DoFreeXRefs;
If FRefCount <> 0 Then Error(reInvalidPtr);
End;
Class Function TXRefInterfacedObject.NewInstance: TObject;
Begin
Result := Inherited NewInstance;
TInterfacedObject(Result).FRefCount := 1;
End;
Delphi-Quellcode:
type
IXRefInterface = Interface
// release before free the reference variable
Function GetNonCounteredReference: IInterface;
Procedure ReleaseNonCounteredReference;
End;
TXRefInterfacedObject = Class(TObject, IInterface, IXRefInterface)
Protected
{IInterface}
Function QueryInterface(Const IID: TGUID; Out Obj): HResult; StdCall;
Function _AddRef: Integer; StdCall;
Function _Release: Integer; StdCall;
Protected
{IXRefInterface}
FRefCount, FXRefs: Integer;
Function GetNonCounteredReference: IInterface;
Procedure ReleaseNonCounteredReference;
Procedure DoFreeXRefs; Virtual;
Public
Procedure AfterConstruction; Override;
Procedure BeforeDestruction; Override;
Class Function NewInstance: TObject; Override;
Property RefCount: Integer Read FRefCount;
End;
Function TXRefInterfacedObject.QueryInterface(Const IID: TGUID; Out Obj): HResult;
Begin
If GetInterface(IID, Obj) Then Result := S_OK
Else Result := E_NOINTERFACE;
End;
Function TXRefInterfacedObject._AddRef: Integer;
Begin
Result := InterlockedIncrement(FRefCount);
End;
Function TXRefInterfacedObject._Release: Integer;
Begin
Result := InterlockedDecrement(FRefCount);
If Result = FXRefs Then Destroy;
End;
Function GetNonCounteredReference: IInterface;
Begin
InterlockedIncrement(FXRefs);
Result := Self;
End;
Procedure ReleaseNonCounteredReference;
Begin
InterlockedDecrement(FXRefs);
//If FRefCount = FXRefs Then Destroy;
End;
Procedure TXRefInterfacedObject.DoFreeXRefs;
Begin
// do nothing
End;
Procedure TXRefInterfacedObject.AfterConstruction;
Begin
// release the constructor's implicit refcount
InterlockedDecrement(FRefCount);
End;
Procedure TXRefInterfacedObject.BeforeDestruction;
Begin
If FRefCount <> FXRefs Then Error(reInvalidPtr);
DoFreeXRefs;
If FRefCount <> 0 Then Error(reInvalidPtr);
End;
Class Function TXRefInterfacedObject.NewInstance: TObject;
Begin
Result := Inherited NewInstance;
TInterfacedObject(Result).FRefCount := 1;
End;
Und dann noch eine Variante, bei welcher jeweils erneut durchgezählt wird, was wirklich frei ist.
Delphi-Quellcode:
type
TXRefInterfacedObject = Class(TObject, IInterface)
Protected
{IInterface}
Function QueryInterface(Const IID: TGUID; Out Obj): HResult; StdCall;
Function _AddRef: Integer; StdCall;
Function _Release: Integer; StdCall;
Protected
FRefCount: Integer;
Procedure DoCountingXRefs(Var Count: Integer); Virtual;
Procedure DoFreeXRefs; Virtual;
Public
Procedure AfterConstruction; Override;
Procedure BeforeDestruction; Override;
Class Function NewInstance: TObject; Override;
Property RefCount: Integer Read FRefCount;
End;
Function TXRefInterfacedObject.QueryInterface(Const IID: TGUID; Out Obj): HResult;
Begin
If GetInterface(IID, Obj) Then Result := S_OK
Else Result := E_NOINTERFACE;
End;
Function TXRefInterfacedObject._AddRef: Integer;
Begin
Result := InterlockedIncrement(FRefCount);
End;
Function TXRefInterfacedObject._Release: Integer;
Var XRefs: Integer;
Begin
XRefs := 0;
DoCountingXRefs(XRefs);
Result := InterlockedDecrement(FRefCount);
If Result = XRefs Then Destroy;
End;
Procedure TXRefInterfacedObject.DoCountingXRefs(Var Count: Integer);
Begin
// do nothing
End;
Procedure TXRefInterfacedObject.DoFreeXRefs;
Begin
// do nothing
End;
Procedure TXRefInterfacedObject.AfterConstruction;
Begin
// release the constructor's implicit refcount
InterlockedDecrement(FRefCount);
End;
Procedure TXRefInterfacedObject.BeforeDestruction;
Var XRefs: Integer;
Begin
XRefs := 0;
DoCountingXRefs(XRefs);
If FRefCount <> XRefs Then Error(reInvalidPtr);
DoFreeXRefs;
If FRefCount <> 0 Then Error(reInvalidPtr);
End;
Class Function TXRefInterfacedObject.NewInstance: TObject;
Begin
Result := Inherited NewInstance;
TInterfacedObject(Result).FRefCount := 1;
End;
- Vorteil der letzeren Variante wäre, daß man die verlinkten Interfaces befragen könnte, ob ob diese einen wirklich noch benötigen.
- Allergings ist dieses (vorallem die Zählprozedur) nicht threadfähig, falls irgendwo anders in der Zwischenzeit deine neue Referenz erstellt oder freigegeben wird.
Hier müßte also noch eine Absicherung rein ('ne CriticalSection z.B.), welche solange alles andere blockiert und eventuell die Threads interagieren läßt.
- Die ersteren Beiden sind da leider nicht flexiebel (siehe Punkt 1)
und da sie einen externen Zugriff auf die Referenzzählung erlauben, kann es sein, daß über DoFreeXRefs nicht alle der externen Referenzen freigegeben werden, wenn dieses nötig wäre.
(hier wäre es wohl besser die Registrierungen auch nur innerhalb des Objektes zu handhaben)
- Theoretisch wäre auch noch eine Variante mit einem LöschCallback denkbar, welchen man beim Registrieren der nichtgezählten Referenz mit angeben muß.
(das war mir grad eben erst, beim Schreiben eingefallen ... werde hierzu demächst noch einen Beispielcode nachreichen)
So, nun seid ihr gefragt.
Wie würdet oder wie habt ihr solche Probleme gelöst,
bzw. welche Variante (kann auch eine ganz Andere sein) würdet ihr bevorzugen
und wo würdet ihr euer Pro und Contra setzen?