Access violation after going out of a RTTI-related function: Result disappears - delphi

Previously I had one function to get object property through RTTI and set it's value. Now I decided to split it up in TRTTI.GetObjProp and TRTTI.SetObjPropValue in order to cache value returned by TRTTI.GetObjProp and speed up data processing.
Strange things happen after that. First of all, I suddenly noticed that ctx.GetType works and has always worked although I never initialized the ctx variable. Ok, if I initialize it with TRttiContext.Create(), nothing changes in the second weirdness:
I call the TRTTI.GetObjProp function and as long as I am in this function, Result is defined, and I can request it's properties with debugger. When I go out of the function, I try to request the same properties of rprop variable and get Access Violation error. According to debugger, rprop is defined.
procedure TConfManager._setValue(indicator: TComponent; name: string; value: OleVariant);
var
rprop: TRttiProperty;
begin
rprop := TRTTI.GetObjProp(indicator, name);
//Access violation here when I try to use rprop
TRTTI.SetObjPropValue(indicator, rprop, value);
end;
class function TRTTI.GetObjProp(obj: TObject; propName: string): TRttiProperty;
var
ctx: TRttiContext;
rtyp: TRttiType;
begin
if obj = nil then Exit(nil);
rtyp := ctx.GetType(obj.ClassType);
Result := rtyp.GetProperty(propName);
end;
Why the function result is properly defined when I am in the function, but isn't properly defined when I go out of it?

The TRttiProperty instance is owned by the TRttiContext object. When your local variable ctx leaves scope, that TRttiContext object is destroyed, taking with it all the objects it owns, including your TRttiProperty instance.
You need to make sure that the TRttiContext object that owns any RTTI objects that you use lives at least as long as any references to those owned objects. The simplest way to achieve that is probably to make the TRttiContext object be a global variable.

Related

TStringList is not passing value

So i have a procedure, that is getting the list of dom nodes.
procedure TmainForm.getNodeListByClass(className:string; outputList:TStringList);
var
foundNode:TDomTreeNode;
foundNodesList:TStringlist;
begin
foundNodesList:=Tstringlist.Create;
foundNode:=nodeFindNodeByClassName(DomTree.RootNode,className);
if Assigned(foundNode) then
getNodeList(foundNode,foundNodesList);
outputList:=foundNodesList;
freeandnil(foundNodesList);
end;
And a procedure that is using it
procedure TmainForm.getByXpathBtnClick(Sender: TObject);
var
temp:TStringlist;
begin
temp:=TStringlist.Create;
temp.Add('testval');
getNodeListByClass('table_input',temp);
memo1.Lines:=temp;
getNodeListByClass('left iteminfo',temp);
dbgForm.memo1.Lines:=temp;
getNodeListByClass('left',temp);
dbgForm.memo2.Lines:=temp;
freeandnil(temp);
end;
And i really don't understand, why it wouldn't work, result of first procedure is always empty.
I found out, that when the first procedure is executing, "foundNodesList" have the correct list, and setting it to "outputList" is working too, but as soon as its returning to the second procedure (in "temp" list) its just empty.
So its clearing old data from "test" ('testval' what i am writing in the beginning), but not adding the result from the first one.
Could someone point me in the right direction?
The problem is here
outputList := foundNodesList;
FreeAndNil(foundNodesList);
The assignment is a reference assignment. I think that you are expecting the content of foundNodesList to be transferred into outputList. But what happens is that you end up with two variables referring to the same instance.
Your code can be fixed very easily. You do not need a temporary string list, you can simply populate the string list passed into the method.
procedure TmainForm.getNodeListByClass(className: string; outputList: TStringList);
var
foundNode: TDomTreeNode;
begin
outputList.Clear;
foundNode := nodeFindNodeByClassName(DomTree.RootNode, className);
if Assigned(foundNode) then
getNodeList(foundNode, outputList);
end;
Note that in the other function when you write
memo1.Lines := temp;
this works a little differently. The Lines property of a TMemo has a property setter that copies the right hand side, rather than taking a reference. Your code that performs assignment to Lines is therefore correct.
You must understand that objects are reference types in Delphi, and that these references are passed by value. So your procedure
procedure TmainForm.getNodeListByClass(className:string; outputList:TStringList);
var
foundNode:TDomTreeNode;
foundNodesList:TStringlist;
begin
foundNodesList:=Tstringlist.Create;
foundNode:=nodeFindNodeByClassName(DomTree.RootNode,className);
if Assigned(foundNode) then
getNodeList(foundNode,foundNodesList);
outputList:=foundNodesList;
freeandnil(foundNodesList);
end;
will never change the outputList of the caller. Indeed, the line
outputList:=foundNodesList;
merely sets the getNodeListByClass procedure's own local variable outputList, which was only a copy of the pointer to the caller's string list. Hence, this copy of the pointer is changed, but the actual object, and the caller's pointer to it, are left unchanged.
Also, even if this had not been the case, your code would have had a bug, because
freeandnil(foundNodesList);
destroys the string list object foundNodesList, and this is the same object that outputList points to at that point. Hence, if the caller would have been able to see the "new" outputList (if it had been a var parameter), it would only see a dangling pointer (memory corruption bug).
What you need is
procedure TmainForm.getNodeListByClass(const className: string; outputList: TStringList);
var
foundNode: TDomTreeNode;
foundNodesList: TStringlist;
begin
foundNodesList := TStringList.Create;
try
foundNode := nodeFindNodeByClassName(DomTree.RootNode, className);
if Assigned(foundNode) then
getNodeList(foundNode, foundNodesList);
outputList.Assign(foundNodeList);
finally
foundNodeList.Free;
end;
end;
assuming your functions do what I think they do. But this can be simplified to
procedure TmainForm.getNodeListByClass(const className: string; outputList: TStringList);
var
foundNode: TDomTreeNode;
begin
outputList.Clear;
foundNode := nodeFindNodeByClassName(DomTree.RootNode, className);
if Assigned(foundNode) then
getNodeList(foundNode, outputList);
end;
(I don't know if you want to append the list or replace it. You have to adjust the code accordingly.)
Also, notice that you always must protect your objects, using try..finally blocks, for instance. Your code must never leak resources (memory, for instance), not even if an exception is raised!

E2555 Cannot capture symbol 'Self'

When I run the following code I get E2555 Cannot capture symbol 'Self'.
type
TLookupTable = record
FData: TArray<TOtherRec>;
procedure ReverseLeftToRight;
end;
procedure TLookupTable.ReverseLeftToRight;
begin
Parallel.For(0, Length(FData)-1).NoWait.Execute(procedure(i: integer) begin
FData[i]:= FData[i].ReverseLeftRight;
end); {for i}
end;
How do I fix this?
The problem is that var parameters (including hidden var parameters to Self) do not get captured. However, we do not want to copy the record, that would be useless, because then our method would not work.
The trick is to make the hidden self parameter explicit.
If it's a class, then it's easy (var S:= Self), if not you'll have to declare a pointer to your record.
procedure TLookupTable.ReverseLeftToRight;
type
PLookupTable = ^TLookupTable;
var
S: PLookupTable;
begin
S:= #Self;
Parallel.For(0, Length(FData)-1).NoWait.Execute(procedure(i: integer) begin
S.FData[i]:= S.FData[i].ReverseLeftRight;
end); {for i}
end;
Now the compiler no longer complains.
(Note that I'm using the implicit syntax for S^.xyz).
Delphi Rio
Using a inline var declaration as shown below, does not work.
//S: PLookupTable;
begin
var S:= #Self; //inline declaration
Parallel.For(0, Length(FData)-1).NoWait.Execute(procedure(i: integer) begin
S.FData[i]:= S.FData[i].ReverseLeftRight;
end); {for i}
This generates: E2018 Record, object or class type required.
I guess the inline #Self gets resolved to a generic pointer, which is a shame, because there is enough info to infer the correct type for the inline variable.
Asynchronous issues
If you're executing the code using a Async (.NoWait) thread/task, then it might be better to put FData in the local variable. FData, being a dynamic array, is already a pointer (so no copying will take place, just a ref count). And dynamic arrays do not have Copy-on-Write semantics, so the original will get updated.
As is, the Self record might go out of scope whilst the code is running, because the pointer operation S:= #Self does not cause the reference count on FData to increase). This might cause an access violation (or worse).
Taking a reference to FData causes its refcount to go up, meaning it cannot go out of scope prematurely.

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.

DWScript: Getting from IScriptObj to IInfo or TProgramInfo

Given a IScriptObj reference how does one get to a corresponding IInfo or TProgramInfo?
I have a script object that wraps a Delphi object.
In order to manage the life time of the script object the Delphi object stores a reference to the script object. The Script object is declared with a TdwsUnit component. It's pretty standard and goes something like this:
Delphi
type
TDelphiObject = class
private
FScriptObject: IScriptObj;
public
procedure DoSomething;
property ScriptObject: IScriptObj read FScriptObject write FScriptObject;
end;
Script
type
TScriptObject = class
protected
procedure DoSomething; virtual;
public
constructor Create;
end;
The instantiation of the Delphi object and setup of the Delphi/script links happens in the Delphi implementation of the script object constructor. Also pretty standard:
Delphi
// Implements TScriptObject.Create
procedure TMyForm.dwsUnitClassesTScriptObjectConstructorsCreateEval(Info: TProgramInfo; var ExtObject: TObject);
var
DelphiObject: TDelphiObject;
DelphiObjectInfo: IInfo;
begin
// Create the Delphi-side object
DelphiObject := TDelphiObject.Create;
// Get the script object "self" value
DelphiObjectInfo := Info.Vars['self'];
// Store the ScriptObject reference
DelphiObject.ScriptObject := DelphiObjectInfo.ScriptObj;
// Return the instance reference to the script
ExtObject := DelphiObject;
end;
Ideally I would have saved the IInfo reference rather that the IScriptObj since IInfo does everything I need later on, but from experience it seems the IInfo object is only valid for the duration of the method call.
Anyway, the problem occurs later on when TDelphiObject.DoSomething is called on the Delphi side.
TDelphiObject.DoSomething is meant to call the corresponding virtual method on the script object:
Delphi
procedure TDelphiObject.DoSomething;
var
Info: IInfo;
DoSomethingInfo: IInfo;
begin
// I have a IScriptObj but I need a IInfo...
Info := { what happens here? };
// Call the virtual DoSomething method
DoSomethingInfo := Info.Method['DoSomething'];
DoSomethingInfo.Call([]);
end;
I have tried a lot of different techniques to get a usable IInfo or TProgramInfo from the stored IScriptObj but every thing has failed. So what is the correct way of doing this?
The problem turned out to be that I assumed I needed an IInfo interface to encapsulate the object instance but apparently DWScript doesn't work that way. What I need is to create a temporary reference/pointer to the instance and then create an IInfo on that instead.
Here's how that is done:
procedure TDelphiObject.DoSomething;
var
ProgramExecution: TdwsProgramExecution;
ProgramInfo: TProgramInfo;
Data: TData;
DataContext: IDataContext;
Info: IInfo;
DoSomethingInfo: IInfo;
begin
(*
** Create an IInfo that lets me access the object represented by the IScriptObj pointer.
*)
// FProgramExecution is the IdwsProgramExecution reference that is returned by
// TdwsMainProgram.CreateNewExecution and BeginNewExecution. I have stored this
// elsewhere.
ProgramExecution := TdwsProgramExecution(FProgramExecution);
ProgramInfo := ProgramExecution.AcquireProgramInfo(nil);
try
// Create a temporary reference object
SetLength(Data, 1);
Data[0] := FScriptObject;
ProgramInfo.Execution.DataContext_Create(Data, 0, DataContext);
// Wrap the reference
Info := TInfoClassObj.Create(ProgramInfo, FScriptObject.ClassSym, DataContext);
// Call the virtual DoSomething method
DoSomethingInfo := Info.Method['DoSomething'];
DoSomethingInfo.Call([]);
finally
ProgramExecution.ReleaseProgramInfo(ProgramInfo);
end;
end;
What this does is enable object oriented call backs from Delphi to the script. Without this it is only possible to call global script functions from Delphi.
FWIW, the following two lines from the above:
ProgramInfo.Execution.DataContext_Create(Data, 0, DataContext);
Info := TInfoClassObj.Create(ProgramInfo, FScriptObject.ClassSym, DataContext);
can be replaced with a call to CreateInfoOnSymbol (declared in dwsInfo):
CreateInfoOnSymbol(Info, ProgramInfo, FScriptObject.ClassSym, Data, 0);

Disposing pointers to complex records

I have list of pointers to some complex records. Sometimes when I try disposing them I get invalid pointer operation error. I'm not really sure if I'm creating and disposing them properly.
The record looks like this:
type
PFILEDATA = ^TFILEDATA;
TFILEDATA = record
Description80: TFileType80; // that's array[0..80] of WideChar
pFullPath: PVeryLongPath; // this is pointer to array of WideChar
pNext: PFILEDATA; // this is pointer to the next TFILEDATA record
end;
As I understand when I want a pointer to such record I need to initialize the pointer and the dynamic arrays like this:
function GimmeNewData(): PFILEDATA;
begin
New(Result);
New(Result^.pFullPath);
end;
Now to dispose of series of these records I wrote this:
procedure DisposeData(var pData: PFILEDATA);
var pNextData: PFILEDATA;
begin
while pData^.pNext <> nil do begin
pNextData := pData^.pNext; // Save pointer to the next record
Finalize(pData^.pFullPath); // Free dynamic array
Dispose(pData); // Free the record
pData := pNextData;
end;
Finalize(pData^.pFullPath);
Dispose(pData);
pData := nil;
end;
When I run my program in the debug mode (F9) in the Delphi 2010 IDE something weird happens. When I step trough DisposeData code with F8 it appears that program skips Finalize(pData^.pFullPath) line and jumps to Dispose(pData). Is this normal? Also when Dispose(pData) is executed the Local variables window that displays contents of the pointers does not change. Does this mean that dispose fails?
Edit:
PVeryLongPath is:
type
TVeryLongPath = array of WideChar;
PVeryLongPath = ^TVeryLongPath;
Edit2
So I create 2 TFILEDATA records then I dispose them. Then I create the same 2 records again. For some reason this time pNext in the second record is not nil. It points to the 1st record. Disposing this weird thing gets invalid pointer operation error.
Randomly I have inserted pData^.pNext := nil in the DisposeData procedure.
Now the code looks like this:
procedure DisposeData(var pData: PFILEDATA);
var pNextData: PFILEDATA;
begin
while pData <> nil do begin
pNextData := pData^.pNext;
pData^.pNext := nil; // <----
Dispose(pData^.pFullPath);
Dispose(pData);
pData := pNextData;
end;
end;
The error is gone.
I'll try to change PVeryLongPath into TVeryLongPath.
First, if you free something, the contents of pointers to it do not change. That is why you don't see a change in the local variables display.
EDIT: declare pFullPath as TVeryLongPath. This is a reference type already, and you should not use a pointer to such a type. New() doesn't do what you think it does, in such a case.
It would probably be better if you declared it as UnicodeString, or if your Delphi doesn't have that, WideString.
If pFullPath is declared as a dynamic "array of WideChar", then you should not use New() on it. For dynamic arrays, use SetLength() and nothing else. Dispose() will properly dispose of all items in your record, so just do:
New(Result);
SetLength(Result^.pFullPath, size_you_need);
and later:
Dispose(pData);
In normal code, you should never have to call Finalize(). This is all taken care of by Dispose, as long as you pass a pointer of the correct type to Dispose().
FWIW, I would recommend this and this article of mine.
The fact that you accepted Serg's answer indicates that there is something wrong with your node creation code. Your comment to that answer confirms that.
I'm adding this as a new answer because the edits to the question significantly change it.
Linked list code should look like this:
var
Head: PNode=nil;
//this may be a global variable, or better, a field in a class,
//in which case it would be initialized to nil on creation
function AddNode(var Head: PNode): PNode;
begin
New(Result);
Result.Next := Head;
Head := Result;
end;
Notice that we are adding the node to the head of the list. We don't need to initialize Next to nil anywhere because we always assign another node pointer to Next. That rule is important.
I've written this as a function which returns the new node. Since the new node is always added at the head this is somewhat redundant. Because you can ignore function return values it doesn't really do any harm.
Sometimes you may want to initialize the contents of the node when you add new nodes. For example:
function AddNode(var Head: PNode; const Caption: string): PNode;
begin
New(Result);
Result.Caption := Caption;
Result.Next := Head;
Head := Result;
end;
I much prefer this approach. Always make sure that your fields are initialized. If zero initialization is fine for you then you can use AllocMem to create your node.
Here's a more concrete example of using such a method:
type
PNode = ^TNode;
TNode = record
Caption: string;
Next: PNode;
end;
procedure PopulateList(Items: TStrings);
var
Item: string;
begin
for Item in Items do
AddNode(Head, Item);
end;
To destroy the list the code runs like this:
procedure DestroyList(var Head: PNode);
var
Next: PNode;
begin
while Assigned(Head) do begin
Next := Head.Next;
Dispose(Head);
Head := Next;
end;
end;
You can clearly see that this method can only return when Head is nil.
If you encapsulate your linked list in a class then you can make the head pointer a member of the class and avoid the need to pass it around.
The main point I would like to make is that manual memory allocation code is delicate. It is easy to make little mistakes in the details. In situations like that it pays to put the delicate code in helper functions or methods so you only need to write it once. Linked lists are a great example of a problem that loves to be solved with generics. You can write the memory management code once and re-use it for all sorts of different node types.
I recommend that you avoid using a dynamic array of WideChar which is not at all convenient to work with. Instead use string if you have Delphi 2009 or later, or WideString for earlier Delphi versions. Both of these are dynamic string types with WideChar elements. You can assign to them and Delphi deals with all the allocation.
So, assuming that you now have the following record:
TFILEDATA = record
Description80: TFileType80;
pFullPath: WideString;
pNext: PFILEDATA;
end;
you can simplify things considerably.
function GimmeNewData(): PFILEDATA;
begin
New(Result);
end;
procedure DisposeData(var pData: PFILEDATA);
var pNextData: PFILEDATA;
begin
while pData <> nil do begin
pNextData := pData^.pNext;
Dispose(pData);
pData := pNextData;
end;
end;
You should also initialize pNext field to nil - without it you will finally get access violation. Taking into account what was already said in the previous answers, you can change your code as
type
TFileType80 = array[0..80] of WideChar;
PFILEDATA = ^TFILEDATA;
TFILEDATA = record
Description80: TFileType80;
FullPath: WideString;
pNext: PFILEDATA;
end;
function GimmeNewData: PFILEDATA;
begin
New(Result);
Result^.pNext:= nil;
end;
I think most of your problems are caused by the assumption that New() gives you memory that is zeroed out. I'm pretty sure (and I'm also sure someone will correct me if I'm wrong), but Delphi does not guarantee that that is the case. This can be rectified by changing your code to this:
function GimmeNewData(): PFILEDATA;
begin
New(Result);
ZeroMemory(Result, SizeOf(TFILEDATA));
end;
You should always either zero the memory you get allocated for a record, or at least fill all the fields with something else relevant. This behavior is different to objects, which are guaranteed to be zeroed on allocation.
Hope this helps.

Resources