Einzelnen Beitrag anzeigen

Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
43.142 Beiträge
 
Delphi 12 Athens
 
#1

Kreuzreferenzen - selbsthaltende Interfaces

  Alt 25. Apr 2010, 07:44
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?
Garbage Collector ... Delphianer erzeugen keinen Müll, also brauchen sie auch keinen Müllsucher.
my Delphi wish list : BugReports/FeatureRequests
  Mit Zitat antworten Zitat