GetItem on TDictionary eleminated by linker - delphi

I am using a TDictionary of <string, string>. But for some reason, the linker decides that I do not want to get items out of it.
I have the following code:
function TSheet.GetFieldName(Field: string; Default: string): string;
begin
Result := Default;
if FFieldNames[Field] = '' then
Result := Field
else
Result := FFieldNames[Field];
end;
FFieldNames is a TDictionary<string, string>. On line 2 (if FFieldNames[Field] = '' then), it throws a 'File not found' exception. Adding FFieldNames[Field] to my watch tells me that Function to be called, {System.Generics.Collections}TDictionary.GetItem, was eliminated by linker.
Someone asked here on a similar issue on how to avoid the linker eliminating functions during debugging. From this I gathered, that the compiler/linker assumes that I am not using it. Someone suggested - during conversation - that I should try using it more.
So I created the following code:
FFieldNames.Add(Name, S);
V := FFieldNames.Items[Name];
Where S, Name and V are strings. This is from the code where FFieldNames is filled with data. V's only purpose is to obtain the just inserted S; it does nothing else.
Strangely, while the debugger tells me the same thing (i.e. GetItem being eliminated), V does get set to the expected value. But it does not in my TSheet.GetFieldName function. :|
What am I missing?

The same problem applies to TList<>. Even if the code is using a method in the class it is not accessible from the debugger ("xxx on TList eliminated by linker"). I guess this is a problem with generics in general.
If you make a descendent class it will not have this problem
type
TMyList = class(TList<TMyObject>)
end;
var
List : TMyList;
begin
...
end;

Related

Delphi closure and "old style" object type

Working with anonymous functions I found out that sometimes the compiler throws the following error:
E2555 Cannot capture symbol 'Self' when I try to use some field of the object.
I also noticed that this error seems to be related to the fact that a type, the method belongs to, is declared with "object" key word:
MyType = object()
field: integer;
...
end;
MyType.Method1()
begin
p := procedure
begin
// do something with field
end;
end;
However when a type is declared with "class" keyword it seems it works fine.
I know that to prevent the compiler error I can make a local copy of needed fields and use them inside the anonymous functions, but just to be sure - is "object" type cause of the compiler error and what's the reason of that?
Thanks in advance
As David properly analyzed it is because Self in your case is a value and not a reference. It cannot be moved to the internally created class - same is the case with any method arguments that are records. They also cannot be captured for the very same reason.
For arguments I usually copy them to a local variable which is being captured.
The same can be done for capturing Self in a record or object.
However if you capture it as value you get a copy and calling the closure later might have the "wrong" state because it captured a copy. To make it work similar you would have to capture a reference to Self but then for a value type you cannot guarantee that this reference is still valid when you call the closure.
You can see this in the following code:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TProc = reference to procedure;
PRecord = ^TRecord;
TRecord = object
y: Integer;
procedure Foo;
function GetProc: TProc;
end;
procedure TRecord.Foo;
begin
Writeln(y);
end;
function TRecord.GetProc: TProc;
var
this: PRecord;
begin
this := #Self;
Result :=
procedure
begin
this.Foo;
end;
end;
procedure Nested(var p: TProc);
var
r: TRecord;
begin
p := r.GetProc();
r.y := 0;
p();
r.y := 32;
p();
end;
procedure Main;
var
p: TProc;
begin
Nested(p);
p(); // <- wrong value because PRecord not valid anymore
end;
begin
Main;
end.
If you would capture TRecord it would do a local copy that it captures - you can see that it then will print 0 all the time.
Since Turbo Pascal object is long deprecated, it is reasonable for new language features not to have support for object.
There's not really any need to look much further. Since you are maintaining legacy code, I would not expect you to be introducing new language features like anonymous methods. Once you start introducing such language features, this no longer feels like legacy code maintenance and it would be reasonable to re-factor the code away from the legacy language features like object.
Having said that, I do note that the same restriction to capture applies in methods of advanced records.
type
TProc = reference to procedure;
TRecord = record
procedure Foo;
end;
procedure TRecord.Foo;
var
P: TProc;
begin
P :=
procedure
begin
Foo;
end;
end;
This fails to compile with error:
E2555 Cannot capture symbol 'Self'
Why does this code fail, even though advanced records are a fully supported modern feature?
I don't have an explanation for that and the documentation does not make it clear. A plausible explanation is that records are value types. When a local variable is captured, it is hoisted from being a stack allocated variable to a variable owned by an internally created class. That's possible for Self when Self is a reference to an instance of a class. But when Self is a value like a record, it is too late to hoist the record.
Or perhaps it is much more prosaic. Maybe the designers just implemented the most important use case (capturing Self for a class) and omitted the less widely used cases for expediency. It is frustrating that the documentation does not appear to give any rules for what can and cannot be captured.

Delphi: Types of Actual and Formal Parameters Must Be Identical

There are a lot of questions already with this title; however, none address my issue. I am actually trying to pass by reference, yet am receiving the E2033: Types of actual and formal var parameters must be indentical error when trying to compile my code. I am trying to pass three (3) variables, each is an Integer, two by reference (Var) and the other not.
I do not understand the issue with my code, below. I have included the declaration, the definition, and the call.
Declaration of Routine:
private
updateDeviceStatus(Var aReturnCount, aNotFoundCount: Integer; aNumOfDevices: Integer);
I have tried to not condense the declaration of the arguments and declared Var for the first two, explicitly' however, this did not work.
Question 1: is the error because I am mixing by reference and by value (if I remember, correctly, some languages do not permit this)?
Definition of Routine:
procedure TfrmReturnMeterToMfg.UpdateDeviceStatus(Var aReturnCount, aNotFoundCount: Integer; aNumOfDevices : Integer);
begin
// DO SOMETHING
end;
Really, the code in the body of the routine is trivial with regard to the problem and does not affect the problem (at least it shouldn't be the cause in this case).
The Call to Routine:
The following is contained within another routine's body:
// local variables:
var ReturnCount, NotFoundCount, NumOfDevices: Integer;
begin
// SOMETHING HAPPENS TO EACH OF THESE VALUES (THEY ARE INCREMENTED)
UpdateDeviceStatus([ReturnCount], [NotFoundCount], NumOfDevices);
end;
Then I receive the error.
Question 2: is this a result of my syntax when calling the routine (attempting to pass the arguments)?
EDIT
So, you may be wondering (you being a more experienced Delphi programmer), "Where did this lark pick up the [ and ] bit? Here's the resource I was consulting (and see why I looked at the wrong thing in the comments, below): consulted resource.
I guess the brackets are the problem.
This code works for me:
program Project12;
{$APPTYPE CONSOLE}
type
TfrmReturnMeterToMfg = class
private
procedure UpdateDeviceStatus(Var aReturnCount, aNotFoundCount: Integer; aNumOfDevices : Integer);
end;
procedure TfrmReturnMeterToMfg.UpdateDeviceStatus(Var aReturnCount, aNotFoundCount: Integer; aNumOfDevices : Integer);
begin
// DO SOMETHING
end;
var thing : TfrmReturnMeterToMfg;
ReturnCount, NotFoundCount, NumOfDevices: Integer;
begin
ReturnCount := 4;
NotFoundCount := 2;
NumOfDevices := 42;
thing := TfrmReturnMeterToMfg.Create;
thing.UpdateDeviceStatus( ReturnCount, NotFoundCount, NumOfDevices);
thing.Free;
end.
Adding brackets around the argument changes the meaning of the code completely, they mean something. In this particular case, by adding brackets you instruct the compiler to pass
UpdateDeviceStatus(
[ ReturnCount ], // array or set of integer (with one element)
[ NotFoundCount ], // same again here
NumOfDevices // integer
);
This is something completely different.

Delphi XE4 gives E2036 when accessing generic list items of 'object's

This is probably similar / continuation on the previous question below:
Why Delphi XE3 gives "E2382 Cannot call constructors using instance variables"?
Now I'm trying Delphi XE4 with the same code (with 'constructor' changed to 'procedure' as per the solution of the above question).
Now I have also these things in a generics list, i.e. I have
TCoordRect = object
public
function Something: Boolean;
end;
and then a list of these in a function parameter, which I loop through and try to access the items directly:
function DoSomething(AList: TList<TCoordRect>): Boolean;
var
i: Integer;
begin
Result := False;
for i := 0 to AList.Count - 1 do
begin
Result := Result or AList[i].Something; // <-- Here comes the compiler error!
end;
end;
This gives the compiler error "E2036 Variable required". However, if I don't access it directly, i.e put instead a local variable and use that first, then it works:
function DoSomething(AList: TList<TCoordRect>): Boolean;
var
i: Integer;
ListItem: TCoordRect;
begin
Result := False;
for i := 0 to AList.Count - 1 do
begin
ListItem := AList[i];
Result := Result or ListItem.Something; // <-- Now this compiles!
end;
end;
And another "workaround" is to remove all these 'object' types and change them to 'class', but I'm curious as to why this does not work like it used to? Is it again just something with "the compiler moving towards mobile development" or is there some more specific reason, or is this even a bug? BTW I also reported this as a QC issue, so will see if something comes from there.
It's a compiler bug, and it's present in all earlier versions of the compiler. The fault is not limited to XE4. Submitting a QC report is the correct response.
I would not be surprised if Embarcadero never attempt to fix it. That's because you are using deprecated object. Switch to using record and the code compiles.
The issue you have uncovered in this question is unrelated to the SO question you refer to at the top of your question.
Incidentally, this really is a case of old meets new. Legacy Turbo Pascal objects, and modern day generic containers. You are mixing oil and water!

Delphi: How to set field value of a generic using RTTI?

I'd like to fill the field of a generic object at runtime using D2010.
program generic_rtti_1;
{$APPTYPE CONSOLE}
uses
SysUtils, rtti;
type
TMyObject = class
FField1: string;
end;
TGeneric<TElement: class> = class
procedure FillFields(Element: TElement);
end;
procedure TGeneric<TElement>.FillFields(Element: TElement);
var
ctx: TRttiContext;
begin
ctx := TRttiContext.Create();
ctx.GetType(TypeInfo(TElement)).GetField('FField1').
SetValue(#Element, TValue.FromVariant('Some string'));
ctx.Free();
end;
When the line ctx.Free(); is executed, I get an AV at line 21986 in System.pas (function _IntfClear()). This is called from FContextToken := nil in rtti.pas. (In fact, the SetValue-induced AV pops up if I step into SetValue, however if step over it, only the ctx.Free-induced is reported. See below.)
If I remove ctx.Free();, the AV appears when calling SetValue(#Element, TValue.FromVariant('Some string'));. This too at line 21986 in System.pas.
Trying to figure this mess out, I replaced
ctx.GetType(TypeInfo(TElement)).GetField('FField1').
SetValue(#Element, TValue.FromVariant('Field 1 is set'));
with this:
rType := ctx.GetType(TypeInfo(TElement));
rField := rType.GetField('FField1');
Val := TValue.FromVariant('Field 1 is set');
rField.SetValue(#Element, Val);
This time, I got no error, however WriteLn(MyObject.FField1) printed an empty string. (The AV re-appears if I combine SetValue and TValue.FromVariant, i.e. write rField.SetValue(#Element, TValue.FromVariant('Field 1 is set'));.
In order to pinpoint the guilty line, I commented out line by line, replacing the commented code with a compound statement. By accident I forgot to comment out the Val := TValue.FromVariant('Field 1 is set');-line above, which causes the AV to disappear once more (still calling rField.SetValue(#Element, TValue.FromVariant('Field 1 is set'));). (Note that I don't actually use Val in the troublesome call, still the AV disappears.)
I'm kind'a lost at this point.
For sake of completeness, here's how I'd like to use the above code:
var
Generic: TGeneric<TMyObject>;
MyObject: TMyObject;
begin
MyObject := TMyObject.Create();
Generic := TGeneric<TMyObject>.Create();
Generic.FillFields();
WriteLn(MyObject.FField1);
Generic.Free();
MyObject.Free();
ReadLn;
end;
end.
Do anyone know what I'm doing wrong? (Is this even possible? Are there better ways to do this using generics? )
Well, I don't know if this makes sense to you guys, but here's how I solved it. Hard cast to TObject in procedure TGeneric<TElement>.FillFields works like a charm. Like so:
ctx.GetType(TypeInfo(TElement)).GetField('FField1').
SetValue(TObject(Element), TValue.FromVariant('Field 1 is set'));
Hope this is useful to someone else out there.

Initialise string function result?

I've just been debugging a problem with a function that returns a string that has got me worried. I've always assumed that the implicit Result variable for functions that return a string would be empty at the start of the function call, but the following (simplified) code produced an unexpected result:
function TMyObject.GenerateInfo: string;
procedure AppendInfo(const AppendStr: string);
begin
if(Result > '') then
Result := Result + #13;
Result := Result + AppendStr;
end;
begin
if(ACondition) then
AppendInfo('Some Text');
end;
Calling this function multiple times resulted in:
"Some Text"
the first time,
"Some Text"
"Some Text"
the second time,
"Some Text"
"Some Text"
"Some Text"
the third time, etc.
To fix it I had to initialise the Result:
begin
Result := '';
if(ACondition) then
AppendInfo('Some Text');
end;
Is it necessary to initialise a string function result? Why (technically)? Why does the compiler not emit a warning "W1035 Return value of function 'xxx' might be undefined" for string functions? Do I need to go through all my code to make sure a value is set as it is not reliable to expect an empty string from a function if the result is not explicitly set?
I've tested this in a new test application and the result is the same.
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
S: string;
begin
for i := 1 to 5 do
S := GenerateInfo;
ShowMessage(S); // 5 lines!
end;
This is not a bug, but "feature":
For a string, dynamic array, method
pointer, or variant result, the
effects are the same as if the
function result were declared as an
additional var parameter following the
declared parameters. In other words,
the caller passes an additional 32-bit
pointer that points to a variable in
which to return the function result.
I.e. your
function TMyObject.GenerateInfo: string;
Is really this:
procedure TMyObject.GenerateInfo(var Result: string);
Note "var" prefix (not "out" as you may expect!).
This is SUCH un-intuitive, so it leads to all kind of problems in the code. Code in question - just one example of results of this feature.
See and vote for this request.
We've run into this before, I think maybe as far back as Delphi 6 or 7. Yes, even though the compiler doesn't bother to give you a warning, you do need to initialize your string Result variables, for precisely the reason you ran into. The string variable is getting initialized -- it doesn't start as a garbage reference -- but it doesn't seem to get reinitialized when you expect it to.
As for why it happens... not sure. It's a bug, so it doesn't necessarily need a reason. We only saw it happen when we called the function repeatedly in a loop; if we called it outside a loop, it worked as expected. It looked like the caller was allocating space for the Result variable (and reusing it when it called the same function repeatedly, thus causing the bug), rather than the function allocating its own string (and allocating a new one on each call).
If you were using short strings, then the caller does allocate the buffer -- that's long-standing behavior for large value types. But that doesn't make sense for AnsiString. Maybe the compiler team just forgot to change the semantics when they first implemented long strings in Delphi 2.
This is not a Bug. By definition no variable inside function is initialized, including Result.
So your Result is undefind on first call, and can hold anything. How it is implemented in compiler is irrelevant, and you can have different results in different compilers.
It seems like your function should be simplified like this:
function TMyObject.GenerateInfo: string;
begin
if(ACondition) then
Result := 'Some Text'
else
Result := '';
end;
You typically don't want to use Result on the right side of an assignment in a function.
Anyway, strictly for illustrative purposes, you could also do this, though not recommended:
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
S: string;
begin
for i := 1 to 5 do
begin
S := ''; // Clear before you call
S := GenerateInfo;
end;
ShowMessage(S); // 5 lines!
end;
This looks like a bug in D2007. I just tested it in Delphi 2010 and got the expected behavior. (1 line instead of 5.)
If you think that some automatic management of strings are made to make your life easier, you're only partly right. All such things are also done to make string logic consistent and side-effects free.
In plenty of places there are string passed by reference, passed by value, but all these lines are expecting VALID strings in whose memory-management counter is some valid, not a garbage value. So in order to keep strings valid the only thing for sure is that they should be initialized when they firstly introduced. For example, for any local variable string this is a necessity since this is the place a string is introduced. All other string usage including function(): string (that actually procedure(var Result: string) as Alexander correctly pointed out) just expects valid strings on the stack, not initialized. And validness here comes from the fact that (var Result: string) construction says that "I'm waiting for a valid variable that definetly was introduced before". UPDATE: Because of that the actual contents of Result is unexpected, but due to the same logic, if it's the only call to this function that has a local variable at the left, the emptiness of the string in this case is guaranteed.
Alex's answer is nearly always right and it answers why I was seeing the strange behaviour that I was, but it isn't the whole story.
The following, compiled without optimisation, produces the expected result of sTemp being an empty string. If you swap the function out for the procedure call you get a different result.
There seems to be a different rule for the actual program unit.
Admittedly this is a corner case.
program Project1;
{$APPTYPE CONSOLE}
uses System.SysUtils;
function PointlessFunction: string;
begin
end;
procedure PointlessProcedure(var AString: string);
begin
end;
var
sTemp: string;
begin
sTemp := '1234';
sTemp := PointlessFunction;
//PointlessProcedure(sTemp);
WriteLn('Result:' + sTemp);
ReadLn;
end.

Resources