Integers Stored in a components “Objects” field and the use of Dynamic Records, ClassReferences and Pointers enables developers to extend components beyond the “Data Aware” mindset of basic Delphi. These original building blocks of the Pascal Language became to be considered “Unsafe” tricks and porting such code to the latest “NextGen” compiler environment becomes problematic.
Until the introduction of Auto Reference Counting in the Delphi NextGen compiler object references and memory pointers were interchangeable so that a TList, a TStringList and a TObjectList would store references to an object, a record, a class Type or a simple memory reference.
Of course in the case of both records and objects the memory management fell to the programmer and the OwnsObjects flag should not be set if objects are not in the store. As long as one was careful with 64 bit compiles a simple integer was also happily accommodated.
I never used OwnsObjects or TObjectList as I prefer to manage memory allocations explicitly and I like the text/Object relationship of a TStringList. By encapsulating these lists as private entities within other objects I could be confident of the type of the entity stored. Using pointers as the return from functions removes the need to type cast.
function TISMultiUserDBObjectFile.ReadFOByIndexASClass(AIdx: Integer; AOfThisClass: TPFClass):Pointer; Begin // end;
The result of this single function can be allocated to any decedent of TPFClass without typecasting as nil is returned if the class is not as expected; Nil is also returned if there is no object at AIdx so handling nil is always required.
While these principles worked well before Auto Reference Counting porting any code using them to a mobile environment was challenging. Any allocation to an "object" variable now calls TObject.__ObjAddRef and will raise an exception if the pointer does not reference an actual object in memory. Similarly if an Object is no longer assigned to an "object" reference it will be destroyed even if a reference is held in a pointer somewhere.
TList is an Ancestor of TObjectList and TStrings is the Abstract base of TStringList and therefore if you use OOP polymorphic features they form natural bases. The new “Safe” Generic forms are not polymorphic.
TList and TObjectList
Because the TList has pointers, objects added to the TList instance do not up the reference count and will be deleted under ARC unless another (Object) reference is maintained. In some cases I was able to use a generic TList<T> and keep ARC happy in other cases I planned to use a TObjectList but had troubles finding System.Contnrs on Android, also ObjectList[idx]:TObject had to be cast to allocate to descendant types of TObject and nil could not be allocated a generic list as they did not match the <type>.
I chose therefore to derive my own TISObjList from TList for use on NextGen platforms and whose list property is a pointer but which updates the reference counters.
TISObjList = class(TList) private FOwnsObjects: Boolean; protected procedure Notify(Ptr: Pointer; Action: TListNotification); override; // Use notify to manage RefCount; public constructor Create; overload; constructor Create(AOwnsObjects: Boolean); overload; procedure FreeInstance; override; property OwnsObjects: Boolean read FOwnsObjects write FOwnsObjects; end;
Note:TObjectList may not be fully tested under mobiles as System.Contnrs is not provided as a standard DCU in Lib.Android.Debug.
TStringList and TStrings
The instances of TStringList in the existing code however created more complex problems.
The database uses TStringlists to store index values and their object references but as objects are on the network and not in memory the references are just integers. This therefore requires a container with the functionality of TstringList but with the “Objects[i]” property a pointer and not defined as object.
This solution (TISStringPtrList) was achieved replicating the TStringList code replacing the FList array with an array of TStringItemWPtr.
TStringItemWPtr = record FString: string; FObject: Pointer; //was TObject end; PStringItemWPtrList = ^TStringItemWPtrList; TStringItemWPtrList = array of TStringItemWPtr; // TISStringPtrList class for the Compare Function TStringPtrListSortCompare = function(List: TISStringPtrList; Index1, Index2: Integer): Integer;
The TISStringPtrList works fine for database internal use but interactions with FMX Components were expecting the ancestors Tstrings or TStringList and when calling addstrings, sort or other functions the Objects:TObject property references caused calls to TObject.__ObjAddRef and subsequent exceptions. Here I introduced a TIsIntPtrStrings which does not attempt to stores pointers, integers or even objects directly but creates a TIsIntPtrStringsObject in the TStrings.Objects property and stores the pointers or integers in that object. The resulting Container is then fully polymorphic and compliant with Tsrings and TStringList for addstrings, etc. but you need to know you if you are storing an object, memory pointer or integer.
TIsIntPtrStringsObject = class(TObject) Public StoredObj: TObject; StoreInt: Integer; StorePointer: Pointer; Constructor Create; procedure FreeInstance; override; end; TIsIntPtrStrings = class(TStringList) private // implementation // public function AddObject(const S: string; AObject: TObject): Integer; override; function AddPointer(const S: string; APtrForObj: Pointer): Integer; function AddInteger(const S: string; AIntegerForObj: Integer): Integer; Constructor Create; Destructor Destroy; override; procedure FreeInstance; override; Property ApplicationObjects[Index: Integer]: TObject read GetActualObject write PutActualObject; Property Pointers[Index: Integer]: Pointer read GetPointer write PutPointer; Property Integers[Index: Integer]: Integer read GetInteger write PutInteger; end;
The full code of this unit is available from Github
I use three containers
TISObjList = class(TList)
Accepts and delivers pointers in default Items property but manages the reference counting so objects inserted are not “Recovered” by ARC while they are contained.
TISStringPtrList = class(TStrings)
A copy of TStringList Code but with the default Objects property defined as a pointer this means AddObject can accept Objects, Integers or Pointers without crashing ARC.
The reference count of “objects” is not managed so Object Life Times must be maintaioned else where.
TIsIntPtrStrings = class(TStringList)
Has its own internal object structure enabling properties of Objects Pointers and Integers and (String,Item) Methods of AddObject, AddPointer and AddInteger
Because the actual stored items are objects containing the Integers or pointers TStrings.AddStrings works fine and it can be passed to components such as list boxes etc to select or sort items based on the “string” representation but the associated integer or pointer can be referenced from the index once the string is selected.