DELPHI class with dynamic array has problem with SetLength() - arrays

I need to create a class containing an array of record objects but trying to use SetLength raise an Access Violation errror.
Consider the following example of a tree object with fruits.
type
TFruit = record
color: string;
weight: double;
end;
type
TObjectTree = class
Public
Fruits: array of TFruit;
constructor Create;
procedure AddFruit;
end;
In the implementation, when trying to resize the array of Fruit objects or initializing to nil generates problems.
constructor TObjectTree.Create;
begin
inherited Create;
Fruits:=nil; //Raises an error
end;
procedure TObjectTree.AddFruit(FruitColor: string; FruitWeight: integer);
begin
SetLength(Fruits, Length(Fruits)+1); //Raises an error (when I comment Fruits:=nil; in the constructor)
Fruits[Length(Fruits)].color:=FruitColor;
Fruits[Length(Fruits)].weight:=FruitWeight;
end;
How can I use dynamic arrays in a class?

Replace
Fruits[Length(Fruits)].color:=FruitColor;
Fruits[Length(Fruits)].weight:=FruitWeight;
with
Fruits[High(Fruits)].color:=FruitColor;
Fruits[High(Fruits)].weight:=FruitWeight;
then it works.

Something tells me you've neglected to create an instance of TObjectTree. You've declared a TObjectTree variable, but you've either not called TObjectTree.Create, or you've called it directly on the variable you declared instead of assigning a new value to that variable:
var
Tree: TObjectTree;
begin
// This is wrong.
Tree.Create;
// This is right.
Tree := TObjectTree.Create;
Without properly instantiating TObjectTree, there is no valid memory to back the Fruits field you attempt to use, so assigning a value to it gives an error.

As an addition to the answers of iamjoosy and Rob Kennedy, I would code this like so:
procedure TObjectTree.AddFruit(FruitColor: string; FruitWeight: integer);
var
NewCount: Integer;
begin
NewCount := Length(Fruits)+1;
SetLength(Fruits, NewCount);
Fruits[NewCount-1].color := FruitColor;
Fruits[NewCount-1].weight := FruitWeight;
end;
It is clearer, in my view, to call Length() just once.
You do not need to assign Fruits := nil in the constructor since that happens automatically. All fields are zero-initialised when an object is instantiated. That said, Fruits := nil should not raise an error. If it does it is probably a result of a memory corruption due to the out-of-bounds array accessing.
A further point to make is that enabling range checking would have resulted in an informative error that would have explained the problem. This is much more helpful than relying on access violations. I can't recommend range checking highly enough.
Finally, the SetLength(..., Length(...)+1) pattern typically leads to very inefficient memory usage and can lead to performance problems for large lists. If you have Delphi 2009+, I would recommend using TList<TFruit> instead.

Related

Dynamic Variables in loop

I'm trying to figure out what the best approach would be to handle variables when looping through a slice.
I have the following bit of code:
type Server struct {
Name string
Features []string
}
func main() {
var server1 Server
server1.Name = "server-abc-1"
server1.Features = append(server1.Features, "feature1", "feature2", "feature3" )
subMenuServer1 := systray.AddMenuItem(server1.Name, "Server Menu")
//Manually adding a menu item
subMenuFeatureItem1 := subMenuServer1.AddSubMenuItem("feature1", "feature1 description")
//Creating menu items from looping through slice - Not working
for i, s := range server1.Features {
dynamicVariable := subMenuServer1.AddSubMenuItem(s, "test")
}
}
How would I loop through the features slice to dynamically create the menu items? The main problem is I need to generate a dynamic variable of some sort which is not supported in Go. Is there a better why to do things?
I've found people mentioning using hash table instead of dynamic variables. If so what type does the hash table would I need if the value is going to be "subMenuServer1.AddSubMenuItem(s, "test")"
Creating new variables dynamically is almost always a bad idea, even in languages that support it. Go doesn't support it, since it's a statically compiled language.
You're much better off using a map of type map[string]T, T being the type returned by AddSubMenuItem.

Error code: VUTA(3) Error: separate target of the Object_call is not controlled

I'm a complete beginner to Eiffel and I'm implementing a linked list as an exercise. I get the following error in the feature has (which tells you if the list contains v).
Error code: VUTA(3)
Error: separate target of the Object_call is not controlled.
What to do: ensure the target of the call is controlled or is not separate.
Class: MY_LINKED_LIST [G]
Feature: has
Type: Generic #1
Line: 159
then
-> if l_cursor_a.item.is_equal (v) then
Result := True
The weird thing is that when I change the '.is_equal' for a '=' the error is gone. I don't know what 'controlled' in the error description means and what difference to it does make to use '=' in this context. The code is the following:
MY_LINKED_LIST[G]
class
MY_LINKED_LIST[G]
feature -- Access
item: G
require
not off
do
check
off: attached cursor as l_cursor
then
Result := l_cursor.item
end
end
first,
last: detachable like item
feature -- Measurement
count: INTEGER
feature -- Element change
feature -- Status report
index: INTEGER
before: BOOLEAN
after: BOOLEAN
has (v: like item): BOOLEAN
require
local
l_cursor: like cursor
do
from
l_cursor := first_element
until
not attached l_cursor or Result
loop
check
attached l_cursor as l_cursor_a
then
if l_cursor_a.item.is_equal (v) then
Result := True
end
l_cursor := l_cursor_a.next
end
end
ensure
function_not_change_state: item = old item
end
feature {NONE} -- Implementation
cursor,
first_element,
last_element: detachable MY_CELL[G]
end -- class
MY_CELL[G]
class
MY_CELL[G]
feature -- Access
item: G
The error message is related to SCOOP — the model of concurrent programming built into Eiffel. According to it, a feature can be called on a separate object only when the object is controlled. This is achieved when the target of the call is an argument of a feature, or when a special separate instruction is used. In your case, the latter would look like
separate l_cursor_a.item as x do
if x.is_equal (v) then
Result := True
end
end
Why l_cursor_a.item is considered separate in the first place? It has a type G, and the formal generic is unconstrained that is identical to have a constraint detachable separate ANY (so, most probably, the code above would not compile, you would need to make sure x is attached before calling is_equal on it).
The equality operator = does not perform any calls (unless the involved types are expanded, but expanded types are never separate). For reference types (including separate ones), it simply tests whether two references are the same. This explains why the error disappears when is_equal is replaced with =.
An alternative solution to avoid the error message is to change the constraint of the formal generic to be non-separate: MY_LINKED_LIST [G -> detachable ANY].
Side note. The check instruction check attached l_cursor as l_cursor_a then ... seems to be redundant, the compiler should be able to figure out automatically that l_cursor is attached.

How to call a function with void return using Firedac FDConnection Component in Delphi XE5?

I recently started using the [ExecSQLScalar]1 and [ExecSQL]2 methods of the FDConnection component in Delphi XE5. It's very handy not to need to build a Dataset object, like FDQuery just for simple queries or executions.
However I had a curious problem when executing a function with void return that has internal validations where it can generate exceptions. I'm using a Postgres database.
CREATE FUNCTION can_be_exception()
RETURNS void AS
$$
BEGIN
RAISE EXCEPTION E'fail';
END;
$$
LANGUAGE plpgsql STABLE;
In delphi, I call the ExecSQLScalar function ...
FDConnection1.ExecSQLScalar('select 1');
FDConnection1.ExecSQLScalar('select can_be_exception()');
On first run, I get the following error:
Project TFDConnectionDEMO.exe raised exception class
EPgNativeException with message '[FireDAC][Phys][PG][libpq] ERROR:
fail'.
On the second run, I get a Violation Access error:
Project TFDConnectionDEMO.exe raised exception class $C0000005 with
message 'access violation at 0x00000000: read of address 0x00000000'.
Apparently the error occurs in the line below in unit FireDAC.Comp.Client
function TFDCustomConnection.ExecSQLScalar(const ASQL: String;
const AParams: array of Variant; const ATypes: array of TFieldType): Variant;
var
oCmd: IFDPhysCommand;
begin
oCmd := BaseCreateSQL;
try
if BasePrepareSQL(oCmd, ASQL, AParams, ATypes) or (FExecSQLTab = nil) then begin
FDFree(FExecSQLTab);
...
ignoring the previous error and trying again, another error is displayed...
Project TZConnectionDEMO.exe raised exception class EFDException with
message '[FireDAC][DatS]-24. Row is not nested'.
Searching, I found no response to this error. I figured my mistake would be to call the bank raise_exception function using the ExecSQLScalar function of the FDConnection component. So I tried using FDConnection.ExecSQL and as I imagined, you can not use this if there is a SELECT clause in the parameter.
Is there a better way to call function with void return using FDConnection.ExecSQL? would a BUG be in the component? or would not it be correct to make that kind of call?
Using ExecSQLScalar is fine in this case. This is certainly a bug (which was already fixed, at least in Delphi 10.2.3). As you've correctly pointed out, the problem is in releasing a table storage object instance held by the FExecSQLTab field by using FDFree procedure.
I don't have Delphi XE5 source code but maybe you can see something like this inside (comments about what happened are added by me):
if BasePrepareSQL(oCmd, ASQL, AParams, ATypes) or (FExecSQLTab = nil) then
begin
FDFree(FExecSQLTab); { ← directly calls destructor if object is not nil }
FExecSQLTab := oCmd.Define; { ← no assignment if command execution raises exception }
end;
Problem was that when a SQL command execution raised exception during storage table definition stage (oCmd.Define), reference to previously destroyed storage table object instance (by FDFree) remained stored in the FExecSQLTab field (as a dangling pointer).
Then when a different command was executed that way, FDFree procedure was called just for that dangling pointer. Hence the access violation.
Way to correct this is replacing line e.g. by:
FDFree(FExecSQLTab);
by:
FDFreeAndNil(FExecSQLTab);
which was done in some later Delphi release.

how can use dynamic array in delphi

i use dynamic array in delphi.
var
var
frame3:array[0..10] of TFrame3
procedure TForm1.Button1Click(sender:TObject);
begin
frame3[count] := TFrame3.create(self);
gridpanel2.insertcontrol(frame3[count]);
but this code is 'A component named Frame3 alredy exists.'
this error what can ido?
If you need multible instances of TFrame3 you need to give it a new name after you created it.
so change
frame3[count] := TFrame3.create(self);
gridpanel2.insertcontrol(frame3[count]);
To
frame3[count] := TFrame3.create(self);
frame3[count].Name := 'Frame3_' + InttoStr(Count);
gridpanel2.insertcontrol(frame3[count]);
The other issue is that it is not seen in your code how you are changing count loop variable. Is it defined in advance?
You need to do something like this:
procedure TForm1.Button1Click(sender:TObject);
var count:byte;
begin
for count:=1 to 10 do
begin
frame3[count] := TFrame3.create(self);
...
end;
or use any other way to set count before each array member (class instance) creation. This code will even probably not require to set Name property at all.

Delphi -> Delphi prism, how to use array of records?

I'm learning Delphi Prism, and i don't find how to write the following code with it :
type
TRapportItem = record
Label : String;
Value : Int16;
AnomalieComment : String;
end;
type
TRapportCategorie = record
Label : String;
CategoriesItems : Array of TRapportItem;
end;
type
TRapportContent = record
Categories : array of TRapportCategorie;
end;
Then, somewhere, i try to put items in the array :
rapport.Categories[i].Label:=l.Item(i).InnerText;
But it doesn't work.. Can someone enlight me?
Thanks!
You didn't specify exactly what "didn't work". You should include the error in questions like this.
Arrays are reference types, and they start out with the value nil. They need to be initialized before elements can be accessed.
You can do this with the new operator:
rapport.Categories = new TRapportCategorie[10]; // 0..9
Arrays are quite a low-level type. Usually it's better to work with List<T> instead.
So you'd declare:
Categories: List<TRapportCategorie>;
But lists also need initializing, using the new operator. Also, modifying the return value of the indexer on a list containing a value type will be modifying a copy, not the original, which leads to the next point.
Records are usually not the best data type for representing data, as they are not reference types; it's very easy to end up modifying a copy of the data, rather than the original data. It's usually best to use classes instead, where you can put all the initialization code (such as allocating the array or list) in the constructor.

Resources