I have a class that looks like this:
type
TLogin = class(TThread)
private
//variables...
protected
procedure Execute; override;
public
constructor Create(testo: TLabel; splash: TRectangle; username: string; labels: LabelArray);
end;
I know that a constructor can take an endless number or parameters but I always prefer to have a little amount and I guess that 4 is the max. I have managed to reduce the numbers of parameters using a LabelArray.
type
LabelArray = array of TLabel;
It has a length of 15-22 (decided at runtime) and I was wondering if this code is correct or not (look at the var):
constructor Create(testo: TLabel; splash: TRectangle; username: string; var labels: LabelArray);
Does this makes sense? Of course the code compiles but I am not sure if the array is already passed by reference. I don't want to make a copy because that would be useless; I am going to change the text of those labels inside my array so I'd prefer a pass-by-reference.
This is a class that runs in a thread separated from the GUI so I'm calling a Queue at a certain point to update the labels' text of the main form. It would be
Queue(nil, procedure
begin
FLabelArray[0].Text := '...';
FLabelArray[1].Text := '...'; //and so on
end);
For this reason I think that a copy wouldn't be useful.
Delphi dynamic arrays are reference types. Passing the array with var doesn't accomplish anything because your constructor isn't actually assigning a new value to the labels parameter. There's no method of passing a dynamic array that would cause a copy to be created.
It's sufficient for your constructor to assign the labels parameter to the FLabelArray field with a simple assignment statement:
FLabelArray := labels;
That increases the array's reference count, so it will remain a valid array for the lifetime of the thread object you've just constructed.
Dynamic arrays are reference counted, so it doesn't really matter in this situation if you pass it by value or by reference. And even if you were passing around copies, the array holds object pointers, which are lightweight to make copies of. Your Queue() calls would still be operating on the original TLabel objects.
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.
I defined a Objectlist to store several Polygons as TFPolygon = array of TPoint inside this TObjectList; but with the add function of my objectlist I get an Access violation error
:
type
TFPolygon = array of TPoint;
TFPolygonList = class(TObjectList)
private
procedure SetPolygon(Index: Integer; Value: TFPolygon);
function GetPolygon(Index: Integer): TFPolygon;
public
procedure Add(p: TFPolygon);
property Items[index: Integer]: TFPolygon read GetPolygon write SetPolygon; default;
end;
implementation
procedure TFPolygonList.SetPolygon(Index: Integer; Value: TFPolygon);
begin
inherited Items[Index] := Pointer(Value);
end;
function TFPolygonList.GetPolygon(Index: Integer): TFPolygon;
begin
Result := TFPolygon(inherited Items[Index]);
end;
procedure TFPolygonList.Add(p: TFPolygon);
begin
inherited Add(Pointer(p));
end;
I can not understand the error inside this code sample ? Can I only store classes inside a TObjectList or is my approach to store arrays of TPoints also valid ?
Your approach is not valid. Dynamic arrays are managed types. Their lifetimes are managed by the compiler. For that to work you must not cast away the fact that they are managed types, which is exactly what you did.
You cast the dynamic array to Pointer. At that point you have taken a new reference to the dynamic array, but the compiler is not aware of it because a Pointer is not a managed type.
You've got a few options to solve your problem.
If you are on a modern Delphi then stop using TObjectList. Instead use the generic type safe containers in Generics.Collections. In your case TList<TFPolygon> is what you need. Because this is compile time type safe, all the lifetime of the managed types is taken care of.
If you are on an older Delphi, then you can wrap your dynamic array inside a class. Then add instances of those classes to your TObjectList. Make sure that your list is configured to own its objects. It's perfectly possible for you to do that wrapping purely in the implementation of TFPolygonList which will encapsulate things well.
I've got the following code in Form1.
public
{ Public declarations }
cas: integer;
end;
Then I work with the variable, and then I call another form with Form2.ShowModal; On Form2 I try to execute the following: Label9.Caption:=Format('%ds',[Form1.cas]);. But no matter what I do, in Form1 'cas' is assigned the proper value but in Form2 it always shows "0s". Why does that happen?
EDIT:
Now I have in the first unit called 'kolecka' this
var
Form1: TForm1;
barvy: array[1..6] of TColor;
kola: array[1..22] of TShape;
valid: integer;
bezi: boolean;
presnost: real;
skore: integer;
chyb: integer;
kliku: integer;
cas: integer;
and this in the other unit called 'dialog':
implementation
uses
kolecka;
{$R *.dfm}
procedure Statistiky();
begin
With Form2 do begin
Label8.Caption:=IntToStr(kolecka.skore);
Label9.Caption:=Format('%ds',[kolecka.cas]);
Label10.Caption:=IntToStr(kolecka.cas);
Label11.Caption:=IntToStr(skore);
Label12.Caption:=Format('%.2f%%',[presnost]);
end;
end;
But it still doesn't work.. still shows a zero.
EDIT2:
I feel like every answer says something different and I'm very confused..
EDIT3: This is how 'cas' is manipulated in Form1
procedure TForm1.Timer3Timer(Sender: TObject);
begin
cas:=cas+1;
Form1.Label5.Caption:=IntToStr(cas);
end;
FOUND IT!
Meh. I figured out where was the problem.
I was assigning the label captions on Form2 Create and not Show, so of course they were at 0 >.>
In your original question, you declared a field in an object, and you thought it was a global, perhaps?
unit unit1;
interface
uses Stuff;
type
TForm1 = class(TForm)
public
THisIsAFieldInAnObject:Integer;
end
var
ThisIsAGlobal:Integer;
implementation
uses OtherStuff;
...
Notice where you put globals above. Global variables are not fields inside a class. Where you put something, when you write code is called "the context you are in". Inside a class declaration, something like public makes sense as a visiblity specifier. It does not make things global, it makes them visible to users of the class.
To access the global, access it as unitName.VariableName, and don't forget to add 'Uses unitName' to the other unit.
Update You are now correctly accessing the global variable, and it doesn't contain the value you expected. That's where we start debugging. Set a breakpoint on the place where you set the variable, and on any other place where it is changed back to 0. Now set a breakpoint on the place where you read the variable. I find that variable writes work better when they actually happen, and when they aren't over-written by a subsequent write to the same place, that contains a different value. Variables are like a box which contains a number. Zero things writing to it (the code you thought got called did not get called) or two things writing to it (the thing you think should be there but is not there because the second write zapped the first value) are common sources of your sort of confusion.
You have a global variable: kolecka.cas and a field kolecka.Form1.cas. Those are different.
public
{ Public declarations }
cas: integer;//This is the field
end;
var
Form1: TForm1;
cas: integer;//This is the global variable
Label9.Caption:=Format('%ds',[kolecka.cas]); accesses the global variable.
cas in an instance method of TForm1 refers to the field.
On Label10.Caption:=IntToStr(kolecka.cas);, you're actually reading the cas global variable of unit kolecka, not the Form1's one.
In the first case, you could be trying to manipulateForm1.cas after call Form2.ShowModal. Please take note that, code following a ShowModal call won't be executed until you close the form shown with ShowModal.
UPDATE
It seems you're manipulating unit's cas variable, not form's one. I exactly do not know how Delphi treats this case. But it a good practice to explicity indicate which cas instance you are manipulating. Use this code:
self.cas := self.cas + 1;
The first thing I would check is if you have only 1 variable named Form1.
My best guess would be that you have 1 in unit Unit1(Where TForm1 is declared) and 1 in unit Kolecka, but that's just an assumption.
I have some global string variables.
I have to create the function that I could pass & store them in some structure.
Later I need to enumerate them and check their values.
how can this be easily achieved?
(I think I would need some kind of reflection, or store array of pointers).
Anyway, any help will be appreciated.
Thanks!
First of all you can't use Delphi's RTTI for that purpose, because Delphi 7's RTTI only covers published members of classes. Even if you were on Delphi XE, there's still no RTTI for global variables (because RTTI is tied to Types, not to "units").
The only workable solution is to create your own variable registry and register your globals using a name and a pointer to the var itself.
Example:
unit Test;
interface
var SomeGlobal: Integer;
SomeOtherGlobal: string;
implementation
begin
RegisterGlobal('SomeGlobal', SomeGlobal);
RegisterGlobal('SomeOtherGlobal', SomeOtherGlobal);
end.
were the RegisterXXX types would need to be defined somewhere, probably in there own unit, like this:
unit GlobalsRegistrar;
interface
procedure RegisterGlobal(const VarName: string; var V: Integer); overload;
procedure RegisterGlobal(const VarName: string; var V: String); overload;
// other RegisterXXX routines
procedure SetGlobal(const VarName: string; const Value: Integer); overload;
procedure SetGlobal(const VarName:string; const Value:string); overload;
// other SetGlobal variants
function GetGlobalInteger(const VarName: string): Integer;
function GetGlobalString(const VarName:string): string;
// other GetGlobal variants
implementation
// ....
end.
You could also have a global TStringList variable holding a list of name-value pairs.
On Delphi 7, I would follow Cosmin's idea for the interface, and for the implementation, I would use a dictionary type based on Julian Bucknall's excellent data structures code for Delphi, ezDSL.
Later versions of delphi like XE not only have more advanced RTTI they also include a pretty great dictionary type, using generics, so the dictionary can contain any type you like. The esDSL dictionary is pretty easy to use but since it's pointer based, it isnt as type safe as the delphi generics dictionary.
Since what you need to do is look up string "variable names" in very fast time (O(1) we like to call it), what you need is a string-to-variable dictionary. You could have Strings for the keys, and Variants as the values in the dictionary, and just get rid of the original global variables, or you could attempt some rather complex pointers-to-globals logic, but I really think you'd be better off with a simple dictionary of <string,variant> key,value tuples.