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

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.

Related

How to get unit path in runtime with Delphi?

I assume that it is possible to get the complete path of the unit (not just the name) at runtime, since when I generate an error, Delphi already has this stored information:
try
Assert (False, '#');
except
on E: EAssertionFailed from
begin
   ShowMessage (E.Message); // this show me the path
end;
end;
Would anyone know if there is any function that returns me the path of a specific unit, or something similar?
The complete path of the unit as it was on the machine that compiled the project is only possible using Assert. However personally I don't find that information incredibly useful unless you have many units with the same name in different folders or lost control over your source repository and and library paths in effect when compiling.
To get the unit name you can turn on map file or debug information (aka TD32) and do the following:
Use the FileByLevel function from JclDebug - in your case with the default value (0).
The level parameter tells the function how many calls it look up the callstack. If you put that method into a FormCreate of a VCL form for example and pass 1 it will give you Vcl.Forms.pas as the event handler was called from there.
I hesitate to write this answer as it shows a really dirty hack to get the unit name making use of the Assert compiler magic shown above.
Use the following unit:
unit UnitNameHack;
interface
const
cUnitNameSentinel = '$$$sentinel$$$';
var
HackUnitname: string = '';
implementation
var
OrgAssertErrorProc: TAssertErrorProc = nil;
procedure MyAssertErrorProc(const Message, Filename: string; LineNumber: Integer; ErrorAddr: Pointer);
begin
if Message = cUnitNameSentinel then begin
HackUnitname:= Filename;
end
else begin
OrgAssertErrorProc(Message, Filename, LineNumber, ErrorAddr);
end;
end;
initialization
OrgAssertErrorProc := AssertErrorProc;
AssertErrorProc := MyAssertErrorProc;
finalization
AssertErrorProc := OrgAssertErrorProc;
end.
Now whenever you need the unit name call
Assert(False, cUnitNameSentinel);
and retrieve the unit name from HackUnitname.
Note that you cannot wrap the Assert call and reading HackUnitName into a function, not even if inlined.
You've said, something similar. I see that it might have been in a different context, but anyway, for objects you can inspect UnitName or UnitScope to get the name of the module where the object instance was declared, without the module path (which is not valuable information anyway).
Based on Uwe Raabe's answer:
// directly after the implementation uses:
var
ThisUnit: string = '<unknown>';
procedure MyAssertErrorProc(const Message, Filename: string; LineNumber: Integer; ErrorAddr: Pointer);
begin
ThisUnit := Filename;
end;
procedure InitThisUnit;
var
OrgAssertErrorProc: TAssertErrorProc;
begin
OrgAssertErrorProc := AssertErrorProc;
try
AssertErrorProc := MyAssertErrorProc;
Assert(False);
finally
AssertErrorProc := OrgAssertErrorProc;
end;
end;
// [...]
// call InitThisUnit in the initialization section
initialization
InitThisUnit;
end.
It's still a hack, but a bit more elegant. ;-)

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.

Converting TStack code from Delphi to Lazarus

In Delphi I have the following code, and all works well:
var
StackOptions:TStack<String>;
s:string;
bfisio:boolean;
begin
StackOptions:=TStack<String>.Create;
//some pushs here
for s in StackOptions do begin
dosomething;
end;
end;
In Lazarus I can do this:
uses
..., gstack;
type
TStringStack = specialize TStack<String>;
var
StackOptions: TStringStack;
s:string;
begin
//But this code doesn;t compile
StackOptions := TStringStack.Create;
//some pushs here
for s in StackOptions do begin // <-- Error
dosomething;
end;
end;
I get the next error in Lazarus:
Compile Project, Target: TicketLaz.exe: Exit code 1, Errors: 1
umain.pas(263,12) Error: Cannot find an enumerator for the type "TStack$1$crcAC3AF268"
How could I loop the Stack and search for a value with Lazarus without removing items from Stack?
FPC's stack is backed by a TVector.
The TVector has an enumerator.
You can easily add a class helper like so:
Quick and dirty code.
type
TStringStack = specialize TStack<String>;
type
{ TStackHelper }
TVectorEnumerator = specialize TVector<string>.TVectorEnumerator;
TStackHelper = class helper for TStringStack
function GetEnumerator: TVectorEnumerator;
end;
{ TStackHelper }
function TStackHelper.GetEnumerator: TVectorEnumerator;
begin
Result:= FData.GetEnumerator;
end;
I really don't see why a stack is not supposed to have an iterator.
Even in assembly you can simply do mov reg,[esp-04].
This puritanical approach to data-structures helps no-one
All this is complicated by the fact that TStack is generic.
I know FPC allows generic class helpers, but I'm not sure how to make the solution work for all TStack<T>
Another approach would be to simply edit gstack.pas to expose the iterator.

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!

GetItem on TDictionary eleminated by linker

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;

Resources