Consider a simple class TMyObject that contains an array[0..9] of string (Description) and some other properties (omitted for this example). At a certain point, I want to remove all "empty" objects (i.e. where all 10 string are empty) from FMyList (TList<TMyObject>). For this purpose I am using the Pack method with the IsEmpty function:
FMyList.Pack(
function(const MyObject1, MyObject2: TMyObject): boolean
var
ii: Integer;
begin
result := true;
for ii := 0 to 9 do
if (MyObject1.Description[ii] <> '') then
begin
result := false;
break;
end;
end);
Initially I (wrongfully) assumed Pack would also free (release) the objects. This is not the case, so the above code causes memory leaks, leaving the removed TMyObjects "dangling".
Unfortunately, the documentation is a bit sparse on Pack. How to correctly use this method and ensure that after a Pack, not only are the objects removed from the TList but also correctly freed?
Pack is specifically to remove empty items from the list with empty being related to the data type. That is why it does not trigger any OnChange event as it technically does not remove items but just empty slots in the backing array.
The overload with IsEmpty func is not meant for "remove all items from the list where this and that applies".
That being said for your task using Pack is not the solution. You will have to use a backwards loop and Delete. When using a TObjectList<T> with OwnsObjects = True it will automatically destroy the object. Otherwise you need to do that yourself.
Edit: Just to mention this for completeness sake - but I advise against doing this: you can still .Free the item inside the IsEmpty func you provide when you return True. But imho that code is kinda smelly.
Related
I'm struggling with interfaces in Delphi. This question might be trivial, but I am new to Delphi, so please excuse.
I have a TreeView with customized nodes, which hold an interface to an object (essentially, just like it is proposed here: Storing interface pointer inside tree view nodes).
The problem is, once I delete a node (in order to redraw the treeview) and set the interface variable to nil (freeing won't do with interfaces for some reason I haven't fully understood), the weirdest thing happens:
In my object, which contains a list, an integer and a string variable, the string and list will be set empty, while the integer remains the same.
I can't explain this. Does anybody know a workaround, or the possible reason for this behavior? BTW, I am using Delphi 10.2 Tokyo.
Here's my quite unspectacular destroy method:
myNode.destroy;
begin
intf:= nil;// intf holds the interface to the object
end;
Edit: this is a simplified version of my code:
The object I'm referring to: (I have several similar classes which look like obj but are slightly different and I don't know which one will be stored in the interface, but all share these variables)
Obj = class(InterfacedObject, IMyinterface)
count: integer; //this remains the same
children: array of ChildObj; //this will be emptied
name: string; //this will be set to ''
procedure addChild;
procedure IMyInterface.add = addChild;
end;
My customized treeNode:
MyNode = class(TTreeNode)
Intf: IMyinterface;
destructor destroy; override;
end;
Inside my class manages the TreeView:
MyForm.ReloadTree;
begin
if myTreeView.Items.Count > 0 then
begin
myTreeView.Items.Clear;
end
for I:= 0 to RootObj.Count-1 do
begin
myTreeView.Items.AddChild(MyTreeview.Items[0], RootObj.Children[i].name);
(myTreeView.Items[0][i] as MyNode).Intf := Intf(RootObj.Children[i]);
//I will proceed iterating over all children and their children, doing
//the same process, a level higher in the treeView
//...
end;
end;
in my object, which contains a list, an integer and a string variable, the string and list will be set empty, while the integer remains the same.
This is perfectly normal behavior. Strings and interfaces are compiler-managed types. Integers are not. When an object is destructed, compiler-managed data members are automatically deallocated as needed, which in the case of strings and interfaces involves nil'ing the pointers to their referenced data. The containing object itself is not zeroed out completely, so non-managed types, like integers, are not overwritten in memory.
Now, that being said, I see some bugs in your ReloadTree() procedure.
Your for loop is exceeding the upper bound of the RootObj.Children[] list.
When calling AddChild(), the second parameter is a string. You are passing RootObj.Children[i] in that parameter. But, in the next statement, you are type-casting the same RootObj.Children[i] value to an interface when assigning the MyNode.Intf field. A string is not an interface. So, what exactly does RootObj.Children[] contain - strings or interfaces?
When assigning the MyNode.Intf field, you are always accessing the first node in the TreeView, instead of the newly added node.
I am using delphi XE5.
This a resume of my code, Next code works, but there have to be something in my code that destroy normal behaviour:
unit Class1;
type
TClass1 = class
private
FDic:TDictionary<String,String>.Create;
public
constructor create;
procedure insertValue(key,value:String);
end;
implementation
constructor TClass1.create;
begin
FDic:=TDictionary<String,String>.Create;
end;
procedure insertValue(key,value:String);
begin
if(FDic.ContainsKey(key))then
FDic[key] := value
else
begin
FDic.Add(key,value);
end;
end.
And now another unit:
unit Class2;
type
uses Class2;
TClass1 = class
public
class2 :TClass2;
TS: TStringList;
procedure DoSomething;
end;
implementation
procedure TClass1.DoSomething;
var
i: Integer;
c,test: TClass1;
begin
c := TClass1.create;
c.insertValue('height','.34cm');
c.insertValue('width','22cm');
c.insertValue('radio','2cm');
TS.AddObject('square',c);
c := TClass1.create;
c.insertValue('height','.88cm');
c.insertValue('width','11cm');
c.insertValue('top','12cm');
TS.AddObject('circle',c);
test := TS.Objects[0] as TClass1;//test.FDic height should be .34cm but gets the value of the last object create, the same for width an common keys.
//here when I look for the values of FDic test.FDic.Items['height'] the value is .88cm instead of .34cm, each time the values of any element of the Dictionary is replace with the previous of the last object created. And the memory address is the same. Why don't create a new memory address for any new element if it is a different object.
That is a resume of my code, I can put all my code because is too big, but I would like to know where I can search to resolve this problem. I am not is not easy, maybe I am not the only one with that problema, maybe some class in the uses, class variables, there is something that causes a memory problema in that dictionary, but no way to find it.
It's a little hard to be sure of the problem because you posted code that does not compile. For future reference, please don't do that. It's good to cut down the code to a small size, but you should then make it into a small console application that compiles and runs and demonstrates the fault. In spite of this, I think that I can see where the problem is.
You are creating objects and then adding them to a string list with
TS.AddObject(...);
But then you never free those objects. That, I guess, is the source of the leak. You can deal with this by setting the OwnsObjects property of the string list to True.
Specifies whether the string list owns the objects it contains.
The OwnsObjects property specifies whether the string list owns the
stored objects or not. If the OwnsObjects property is set to True,
then the Destroy destructor will free up the memory allocated for
those objects.
That I think will explain the leaks. The other part of your question is why index 0 contains the item that you added second. The only explanation for that, given the code in the question, is that your string list has the Sorted property set to True.
Again, I'm inferring this with a little intuition, but if you have only posted a complete program that compiled and run then your question could have been answered with complete certainty.
I do wonder whether or not a string list is the correct class to be using here. Perhaps you would be better with TObjectDictionary<string, TClass1>. You would create it like this:
dict := TObjectDictionary<string, TClass1>.Create([doOwnsValues]);
The use of doOwnsValues tells the dictionary that it is take assume ownership of the objects that you add as values and destroy them when they are removed from the dictionary, or when the dictionary is destroyed.
Note also that your insertValue method can be implemented in a far simpler way using the AddOrSetValue method.
In Delphi 7, I'm using a TCheckListBox. I want it to use a TStringList rather than a TStrings, so I can set Duplicates to dupIgnore, and Sorted to TRUE.
Can I just do this:
Form1 = class(TObject
CheckListBox1: TCheckListBox; // created by the IDE
end;
procedure TForm1.FormCreate
begin
CheckListBox1.Items.Free;
CheckListBox1.Items := TStringList.Create;
CheckListBox1.Items.Sorted := TRUE;
CheckListBox1.Items.Duplicates := dupIgnore;
end;
Is this safe? Any caveats or suggestions?
EDIT: Removed declaration for MyStringList and added .Items to the last two assignment lines.
EDIT 2: Trying to compile the above, it looks like I'd have to cast the two final lines like this:
TStringList(CheckListBox1.Items).Sorted := TRUE;
TStringList(CheckListBox1.Items).Duplicates := dupIgnore;
Although I might be able to get this to run, I'm asking the question because just getting it to run doesn't mean it will always run or is safe.
You don't control what class TCheckListBox uses to store its items. Assigning the Items property a value only assigns its items to the internal storage.
Also, you shouldn't call Items.Free;. TCheckListBox depends on its internal instance of TListBoxStrings.
To answer your edits in your question: Don't hard-cast the Items property to TStringList, either. The typecast is wrong (the instance exposed by Items is not a TStringList) and will only cause problems.
Edit, to suggest a workaround for what you seem to try to achieve: To keep the checklistbox sorted, you can set its Sorted property to True. To avoid duplicates, you can check the list before adding an item in code.
I am helping out my company with some old delphi 7 code.
There is a record declared at the start that is used throughout to store all the data we want outputted.
type
TOutput_Type = record
result: String;
resultoffset: String;
selected: boolean;
resultcategory: integer;
end;
and then an array of this is declared
Output: array of TOutput_Type;
The length is set at the start to a large value, as actual length is unknown.
This array is used all over the place, but unfortunately the value selected is not always set when used.
My problem is I am adding in a summary of the data, but because selected is not set, delphi seems to give it a random true or false status.
Is there a way of setting all instances of selected as true at the start? Seems like a simple enough thing to do, but I'm not a delphi programmer so am unsure if its possible? I know I can go through and add in selected := true every time a new record is made, but I'd like to do it cleanly at the start if possible....
Thanks in advance
After calling SetLengt for Output variable you must first initiate the new record parts (because new allocated memory isn't defined) in for loop.
Something like:
OldLength := Length(Output);
SetLength(Output, NewLength);
for n := OldLength to NewLength -1 do
Output[n].selected := True;
Records, unlike objects, aren't initialized upon creation, so you need to initialize them yourself. Since you're on Delphi 7, you can't use records with methods, so what I'd do is make an initialization function, something like this:
type
TOutputArray: array of TOutput_Type;
function CreateOutputArray(length: integer): TOutputArray;
var
i: integer;
begin
SetLength(result, MyArbitraryItemCount);
FillChar(result[0], Length(Output)*SizeOf(TOutput_Type), 0);
for i := 0 to high(result) do
result[i].selected := true;
end;
I'd go for the factory method like in the question dcp linked to. Parameterless constructors aren't allowed for records, so you would always have to specify some parameters, which might be annoying if you don't really need them.
If this is all about initializing the content of the large array once at the start you could also use this:
SetLength(Output, MyArbitraryItemCount);
FillChar(Output[0], Length(Output)*SizeOf(TOutput_Type), 1);
Then everything is 1. Including selected :) Of course you could also use a for-loop...
I have a main app and a type library contains 2 COM objects, one is IFile, one is IFiles. IFiles creates IFile, and stores them in a TLIST, and has standard methods like Add, Remove etc. Both IFile and IFiles are TAutoObject.
"Add" method in IFiles is working fine, it simply creates IFile object [Code 1], and adds it to TList. Problem is IFile object instance gets lost in a very strange way. see [Code 2]
[Code 1]
function IFiles.Add(AFilename: String): IFile;
begin
Result := CoIFile.Create;
Result.Filename := AFilename;
// ShowMessage(IntToStr(Result._AddRef));
fFiles.Add(#Result);
end;
In the main app I have test code like this.
[Code 2]
var
i: Integer;
f: IFile;
Files: IFiles;
begin
Files := CoTIFile.Create;
for i:= 1 to 4 do
begin
// Create a dummy file object
f := Files.Add('Filename ' + IntToStr(i));
f._AddRef; // Not sure if AddRef works like this
// Prints out the last file
Memo1.Lines.Add(Files.Files[i-1].Filename);
end;
for i:= 0 to Files.Count-1 do
begin
f := Files.Files[i];
// F is nil at all time.
if (f<>nil) then Memo1.Lines.Add(f.Filename); // ! No print out.
end;
end;
From the 2nd loop, even though fFiles.Count = 4, but all contents have lost. Do I need some extra treatment in IFile to handle AddRef and Release? or the way IFiles.Add method I wrote is wrong?
Try using TInterfaceList rather than TList to store the instances of IFile. This may solve your problem.
The problem in your original code was that you were adding an IFile pointer to the list, but when you read a value out of the list later, you assigned the pointer directly to another IFile variable. So you had was was essentially a PIFile value stored in an IFile variable. Delphi generally allows you to assign the untyped Pointer type to any pointer-like type, including interfaces.
To fix your original code, you would need to write the second look something like this:
var
p: Pointer;
for i := 0 to Pred(Files.Count) do begin
p := Files.Files[i];
if not Assigned(p) then
continue;
f := IFile(p^);
if not Assigned(f) then
continue;
Memo1.Lines.Add(f.Filename);
end;
You were right to call f._AddRef in your first loop. When IFiles.Add returns, the reference count on the result is 1 because the value stored in the loop is a pointer, not the actual reference. You need to increment the reference count because f is going to be re-used for other values. Since the reference you're manually counting is stored in the FFiles list, it would be better to call _AddRef inside IFiles.Add instead of waiting until it returns.
When you clear the list, or as you remove items from the list, you would need to call _Release on all the interface references.
But Toby's answer gives the better idea: Use TInterfaceList to store a list of interfaces. TList simply isn't suited to the task by itself.
A final piece of advice: The "I" prefix on names is used to denote interface types. Interfaces don't have method implementations of their own. You've shown the implementation of IFiles.Add, so IFiles clearly isn't an interface type. It should be named TFiles instead, or maybe TFileList.
The COM objects i automaticly released if there is no references to it.
In Code 1 the COM object gets released at the "end" statement.
I think you need to creat a wrapper object, and that wrapper object is what you add to files.
In sory i don't have the time to creat an example right now.