{***************************************************************************} { } { DUnitX } { } { Copyright (C) 2015 Vincent Parrett & Contributors } { } { vincent@finalbuilder.com } { http://www.finalbuilder.com } { } { } {***************************************************************************} { } { Licensed under the Apache License, Version 2.0 (the "License"); } { you may not use this file except in compliance with the License. } { You may obtain a copy of the License at } { } { http://www.apache.org/licenses/LICENSE-2.0 } { } { Unless required by applicable law or agreed to in writing, software } { distributed under the License is distributed on an "AS IS" BASIS, } { WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. } { See the License for the specific language governing permissions and } { limitations under the License. } { } {***************************************************************************} unit DUnitX.WeakReference; (* The idea behind this unit is provide a similar lifecycle to reference counted objects in delphi as WeakReference does in .NET. Reference counted objects in delphi have some limitations when it comes to circular references, where for example TParent references it's children (via IChild), and TChild references it's parent (via IParent). If we remove any external references to our IParent and IChild instances without first getting the child to remove it's reference to IParent, we would end up with orphaned objects. This is because our IChild and IParent instances are holding references to each other, and thus they never get released. This unit was borrowed from FinalBuilder 7(with permission), it has been extensively used with Delphi 2010 and has so far proven to be very reliable. *) interface {$I DUnitX.inc} type /// Implemented by our weak referenced object base class IWeakReferenceableObject = interface ['{3D7F9CB5-27F2-41BF-8C5F-F6195C578755}'] procedure AddWeakRef(value : Pointer); procedure RemoveWeakRef(value : Pointer); function GetRefCount : integer; end; /// This is our base class for any object that can have a weak reference to /// it. It implements IInterface so the object can also be used just like /// any normal reference counted objects in Delphi. TWeakReferencedObject = class(TObject, IInterface, IWeakReferenceableObject) protected FWeakReferences : Array of Pointer; FRefCount: Integer; function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; function _AddRef: Integer; stdcall; function _Release: Integer; stdcall; procedure AddWeakRef(value : Pointer); procedure RemoveWeakRef(value : Pointer); function GetRefCount : integer; public procedure AfterConstruction; override; procedure BeforeDestruction; override; {$IFDEF NEXTGEN}[Result: Unsafe]{$ENDIF} class function NewInstance: TObject; override; property RefCount: Integer read FRefCount; end; // This is our generic WeakReference interface IWeakReference = interface ['{A6B88944-15A2-4FFD-B755-1B17960401BE}'] function IsAlive : boolean; function Data : T; end; //The actual WeakReference implementation. TWeakReference = class(TInterfacedObject, IWeakReference) private FData : TObject; protected function IsAlive : boolean; function Data : T; public constructor Create(const data : T); destructor Destroy;override; end; implementation uses DUnitX.ResStrs, {$IFDEF USE_NS} System.TypInfo, System.Classes, System.Sysutils, System.SyncObjs; {$ELSE} TypInfo, classes, SysUtils, SyncObjs; {$ENDIF} {$IFNDEF DELPHI_XE2_UP} type TInterlocked = class public class function Increment(var Target: Integer): Integer; static; inline; class function Decrement(var Target: Integer): Integer; static; inline; class function Add(var Target: Integer; Increment: Integer): Integer;static; end; class function TInterlocked.Decrement(var Target: Integer): Integer; begin result := Add(Target,-1); end; class function TInterlocked.Increment(var Target: Integer): Integer; begin result := Add(Target,1); end; class function TInterlocked.Add(var Target: Integer; Increment: Integer): Integer; {$IFNDEF CPUX86} asm .NOFRAME MOV EAX,EDX LOCK XADD [RCX].Integer,EAX ADD EAX,EDX end; {$ELSE CPUX86} asm MOV ECX,EDX XCHG EAX,EDX LOCK XADD [EDX],EAX ADD EAX,ECX end; {$ENDIF} {$ENDIF DELPHI_XE2_UPE2} constructor TWeakReference.Create(const data: T); var weakRef : IWeakReferenceableObject; d : IInterface; begin d := IInterface(data); if Supports(d,IWeakReferenceableObject,weakRef) then begin FData := d as TObject; weakRef.AddWeakRef(@FData); end else raise Exception.Create(SWeakReferenceError); end; function TWeakReference.Data: T; begin result := Default(T); /// can't assign nil to T if FData <> nil then begin //Make sure that the object supports the interface which is our generic type if we //simply pass in the interface base type, the method table doesn't work correctly if Supports(FData, GetTypeData(TypeInfo(T))^.Guid, result) then //if Supports(FData, IInterface, result) then result := T(result); end; end; destructor TWeakReference.Destroy; var weakRef : IWeakReferenceableObject; begin if IsAlive then begin if Supports(FData,IWeakReferenceableObject,weakRef) then begin weakRef.RemoveWeakRef(@FData); weakRef := nil; end; end; FData := nil; inherited; end; function TWeakReference.IsAlive: boolean; begin result := FData <> nil; end; { TWeakReferencedObject } procedure TWeakReferencedObject.AddWeakRef(value: Pointer); var l : integer; begin MonitorEnter(Self); try l := Length(FWeakReferences); Inc(l); SetLength(FWeakReferences,l); FWeakReferences[l-1] := Value; finally MonitorExit(Self); end; end; procedure TWeakReferencedObject.RemoveWeakRef(value: Pointer); var l : integer; i : integer; idx : integer; begin MonitorEnter(Self); try l := Length(FWeakReferences); if l > 0 then begin idx := -1; for i := 0 to l - 1 do begin if idx <> -1 then begin FWeakReferences[i -1] := FWeakReferences[i]; end; if FWeakReferences[i] = Value then begin FWeakReferences[i] := nil; idx := i; end; end; Dec(l); SetLength(FWeakReferences,l); end; finally MonitorExit(Self); end; end; procedure TWeakReferencedObject.AfterConstruction; begin // Release the constructor's implicit refcount TInterlocked.Decrement(FRefCount); end; procedure TWeakReferencedObject.BeforeDestruction; var i: Integer; begin if RefCount <> 0 then System.Error(reInvalidPtr); MonitorEnter(Self); try for i := 0 to Length(FWeakReferences) - 1 do TObject(FWeakReferences[i]^) := nil; SetLength(FWeakReferences,0); finally MonitorExit(Self); end; end; function TWeakReferencedObject.GetRefCount: integer; begin result := FRefCount; end; class function TWeakReferencedObject.NewInstance: TObject; begin // Set an implicit refcount so that refcounting // during construction won't destroy the object. Result := inherited NewInstance; TWeakReferencedObject(Result).FRefCount := 1; end; function TWeakReferencedObject.QueryInterface(const IID: TGUID; out Obj): HResult; begin if GetInterface(IID, Obj) then Result := 0 else Result := E_NOINTERFACE; end; function TWeakReferencedObject._AddRef: Integer; begin Result := TInterlocked.Increment(FRefCount); end; function TWeakReferencedObject._Release: Integer; begin Result := TInterlocked.Decrement(FRefCount); if Result = 0 then Destroy; end; end.