How to know what type is a var? - delphi

TypeInfo(Type) returns the info about the specified type, is there any way to know the typeinfo of a var?
var
S: string;
Instance: IObjectType;
Obj: TDBGrid;
Info: PTypeInfo;
begin
Info:= TypeInfo(S);
Info:= TypeInfo(Instance);
Info:= TypeInfo(Obj);
end
This code returns:
[DCC Error] Unit1.pas(354): E2133 TYPEINFO standard function expects a type identifier
I know a non instantiated var is only a pointer address.
At compile time, the compiler parses and do the type safety check.
At run time, is there any way to know a little more about a var, only passing its address?

No.
First, there's no such thing as a "non-instantiated variable." You instantiate it by the mere act of typing its name and type into your source file.
Second, you already know all there is to know about a variable by looking at it in your source code. The variable ceases to exist once your program is compiled. After that, it's all just bits.
A pointer only has a type at compile time. At run time, everything that can be done to that address has already been determined. The compiler checks for that, as you already noted. Checking the type of a variable at run time is only useful in languages where a variable's type could change, as in dynamic languages. The closest Delphi comes to that is with its Variant type. The type of the variable is always Variant, but you can store many types of values in it. To find out what it holds, you can use the VarType function.
Any time you could want to use TypeInfo to get the type information of the type associated with a variable, you can also directly name the type you're interested in; if the variable is in scope, then you can go find its declaration and use the declared type in your call to TypeInfo.
If you want to pass an arbitrary address to a function and have that function discover the type information for itself, you're out of luck. You will instead need to pass the PTypeInfo value as an additional parameter. That's what all the built-in Delphi functions do. For example, when you call New on a pointer variable, the compiler inserts an additional parameter that holds the PTypeInfo value for the type you're allocating. When you call SetLength on a dynamic array, the compiler inserts a PTypeInfo value for the array type.
The answer that you gave suggests that you're looking for something other than what you asked for. Given your question, I thought you were looking for a hypothetical function that could satisfy this code:
var
S: string;
Instance: IObjectType;
Obj: TDBGrid;
Info: PTypeInfo;
begin
Info:= GetVariableTypeInfo(#S);
Assert(Info = TypeInfo(string));
Info:= GetVariableTypeInfo(#Instance);
Assert(Info = TypeInfo(IObjectType));
Info:= GetVariableTypeInfo(#Obj);
Assert(Info = TypeInfo(TDBGrid));
end;
Let's use the IsClass and IsObject functions from the JCL to build that function:
function GetVariableTypeInfo(pvar: Pointer): PTypeInfo;
begin
if not Assigned(pvar) then
Result := nil
else if IsClass(PPointer(pvar)^) then
Result := PClass(pvar).ClassInfo
else if IsObject(PPointer(pvar)^) then
Result := PObject(pvar).ClassInfo
else
raise EUnknownResult.Create;
end;
It obviously won't work for S or Instance above, but let's see what happens with Obj:
Info := GetVariableTypeInfo(#Obj);
That should give an access violation. Obj has no value, so IsClass and IsObject both will be reading an unspecified memory address, probably not one that belongs to your process. You asked for a routine that would use a variable's address as its input, but the mere address isn't enough.
Now let's take a closer look at how IsClass and IsObject really behave. Those functions take an arbitrary value and check whether the value looks like it might be a value of the given kind, either object (instance) or class. Use it like this:
// This code will yield no assertion failures.
var
p: Pointer;
o: TObject;
a: array of Integer;
begin
p := TDBGrid;
Assert(IsClass(p));
p := TForm.Create(nil);
Assert(IsObject(p));
// So far, so good. Works just as expected.
// Now things get interesting:
Pointer(a) := p;
Assert(IsObject(a));
Pointer(a) := nil;
// A dynamic array is an object? Hmm.
o := nil;
try
IsObject(o);
Assert(False);
except
on e: TObject do
Assert(e is EAccessViolation);
end;
// The variable is clearly a TObject, but since it
// doesn't hold a reference to an object, IsObject
// can't check whether its class field looks like
// a valid class reference.
end;
Notice that the functions tell you nothing about the variables, only about the values they hold. I wouldn't really consider those functions, then, to answer the question of how to get type information about a variable.
Furthermore, you said that all you know about the variable is its address. The functions you found do not take the address of a variable. They take the value of a variable. Here's a demonstration:
var
c: TClass;
begin
c := TDBGrid;
Assert(IsClass(c));
Assert(not IsClass(#c)); // Address of variable
Assert(IsObject(#c)); // Address of variable is an object?
end;
You might object to how I'm abusing these functions by passing what's obviously garbage into them. But I think that's the only way it makes sense to talk about this topic. If you know you'll never have garbage values, then you don't need the function you're asking for anyway because you already know enough about your program to use real types for your variables.
Overall, you're asking the wrong question. Instead of asking how you determine the type of a variable or the type of a value in memory, you should be asking how you got yourself into the position where you don't already know the types of your variables and your data.

With generics, it is now possible to get the type info without specifying it.
Certain users indicated the following code doesn't compile without errors.
As of Delphi 10 Seattle, version 23.0.20618.2753, it compiles without errors, as seen below in the screenshot.
program TypeInfos;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, System.TypInfo;
type
TTypeInfo = class
class procedure ShowTypeInfo<T>(const X: T);
end;
{ TTypeInfo }
class procedure TTypeInfo.ShowTypeInfo<T>(const X: T);
var
LTypeInfo: PTypeInfo;
begin
LTypeInfo := TypeInfo(T);
WriteLn(LTypeInfo.Name);
end;
var
L: Exception;
B: Boolean;
begin
// Console output
TTypeInfo.ShowTypeInfo(L); // Exception
TTypeInfo.ShowTypeInfo(B); // Boolean
end.

Not that I know of. You can get RTTI (Run Time Type Information) on published properties of a class, but not for "normal" variables like strings and integers and so forth. The information is simply not there.
Besides, the only way you could pass a var without passing a type is to use either a generic TObject parameter, a generic type (D2008, as in ), or as an untyped parameter. I can't think of another way of passing it that would even compile.

Related

Value used as both output and (non-reference) input in a chain of methods

Consider the following minimal example of method chaining, where a floating-point variable is set (using an out parameter) by an early method and then passed (using a const parameter) to a later method in the chain:
program ChainedConundrum;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils;
type
ValueType = Double;
TRec = record
function GetValue(out AOutput: ValueType): TRec;
procedure ShowValue(const AInput: ValueType);
end;
function TRec.GetValue(out AOutput: ValueType): TRec;
begin
AOutput := 394;
Result := Self;
end;
procedure TRec.ShowValue(const AInput: ValueType);
begin
Writeln(AInput);
end;
var
R: TRec;
Value: ValueType = 713;
begin
R.GetValue(Value).ShowValue(Value);
Readln;
end.
I initially expected this to print the floating-point number 394 (in some format), but it doesn't (necessarily); when I build the program using the 32-bit compiler of Delphi 10.3.2, the program prints 713. Stepping through the program using the debugger confirms that the initial, pre-GetValue value of Value is passed to ShowValue.
However, if I build this using the 64-bit compiler, 394 is printed. Similarly, if I change ValueType from Double to Int32, I get 394 in both versions. Int64 yields 394 in 64-bit and 713 in 32-bit. Strings yield the updated value. Classes work just like records. Class methods, however, as opposed to instance methods, always give me the updated value. And, of course, abandoning method chaining (R.GetValue(Value); R.ShowValue(Value)) does the same.
Not surprisingly, if I change the AInput parameter of ShowValue from a const (or undecorated value) parameter to a var parameter, I always get the updated value.
My conclusion is that either
it is not allowed to both set and pass a variable in a chain of methods like this, or
there's a bug in the compiler.
My question is: which is it? And if it isn't allowed, where does the documentation state this? I have so far not been able to find the relevant passage. (The phrase "sequence point" seems very rarely to occur anywhere near the phrase "Delphi" on the WWW.)
Everyone who has commented on this issue here or elsewhere agrees that this either "feels like" or "clearly" is a compiler bug.
I've created issue RSP-29733 at the Embarcadero Jira.
Turning to possible workarounds, notice that the issue seems to be that an old value of a variable is used by the compiler. Hence, the problem arise when the value is changed close to the use of the variable.
However, the variable's address isn't changed, so if you pass the variable by reference instead of by value, the problem disappears. One way is to use a var parameter when the value is passed the second time, even though you don't need that, or even want that semantically.
Hence, a more natural approach seems to be to use a const [Ref] parameter:
procedure ShowValue(const [Ref] AInput: ValueType);
This has the same semantics as an undecorated const parameter but forces the compiler to pass the variable by reference, thus avoiding the bug.

Using ToString for Variant variables

The following code produces an EVariantInvalidOpError exception:
var
i : Variant;
begin
i := 10;
ShowMessage(i.ToString());
end;
All the following works good but I don't understand why the ToString function raises exception for Variant type variables:
var
i : Variant;
begin
i := 10;
ShowMessage(VarToStr(i));
end;
var
i : Integer;
begin
i := 10;
ShowMessage(i.ToString());
end;
Variants let you store values of various types in them, while the type may be unknown at compile-time. You can write an integer value into single variable of Variant type an later overwrite it with string value. Along with the value variant records stores also the type information in it. Among those values some of them are automatically allocated and/or reference counted. The compiler does a lot of stuff behind the scenes when writing or reading the value from Variant variable.
Variants of type varDispatch get even more special treat from the compiler. varDispatch indicates that the value is of type IDispatch (usually, but not necessarily related to Windows COM technology). Instance of IDispatch provides information about its methods and properties via GetTypeInfoCount and GetTypeInfo methods. You can use its GetIDsOfNames method to query the information by name.
Let's answer the question from your comment first:
Why does Delphi allow me to use the ToString function even if there is no helper implementing such function for the Variant type?
This is how Delphi implements concept called late binding. It allows you to call methods of an object which type is unknown at compile-time. The prerequisite for this to work is that the underlying variant type supports late binding. Delphi has built-in support for late binding of varDispatch and varUnknown variants as can be seen in procedure DispInvokeCore in unit System.Variants.
I don't understand why the ToString function raises exception for Variant type variables.
As discussed above, in run-time your program tries to invoke ToString method on variant value which in your case is of type varByte. Since it doesn't support late binding (as well as further ordinal variant types) you get the exception.
To convert variant value to string use VarToStr.
Here's a simple example of using late binding with Microsoft Speech API:
uses
Winapi.ActiveX,
System.Win.ComObj;
var
Voice: Variant;
begin
CoInitialize(nil);
try
Voice := CreateOleObject('SAPI.SpVoice');
Voice.Speak('Hello, World!');
finally
CoUninitialize;
end;
end.

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.

Why Delphi compiler can't see I'm trying to free an interface?

I've done a small mistake while coding this week-end.
In the following code, I'm creating an object and cast it to an interface. Later, I'm trying to free it with FreeAndNil();
type
IMyIntf = interface
[...]
end;
TMyClass = class(TInterfacedObject, IMyIntf)
[...]
end;
var
Myintf : IMyIntf;
begin
Myintf := TMyClass.Create;
[...] // Some process
FreeAndNil(Myintf); // CRASH !!!
end;
Of course, the program crash at this line.
I totally understand the issue, but what I don't understand is why the compiler doesn't warn me about it ? There is no dynamic things behind, it's just that I'm trying to free an interface !!! Why don't it write me an error / warning ?
Is there any real explanation behind or is it just a compiler limitation ?
As you know, the correct way to do this is to write Myintf := nil, or just to let it go out of scope. The question you ask is why the compiler accepts FreeAndNil(Myintf) and does not complain at compile time.
The declaration of FreeAndNil is
procedure FreeAndNil(var Obj);
This is an untyped parameter. Consequently it will accept anything. You passed an interface, but you could have passed an integer, a string and so on.
Why did the designers choose an untyped parameter? Well, they needed to use a var parameter since the whole purpose of FreeAndNil is to free the object and set the object reference to nil. That can't be done by a method of the target object and so a var parameter of a standalone function is needed.
You might imagine that you could write
procedure FreeAndNil(var Obj: TObject);
since all objects are descended from TObject. But this does not do the job. The reason being that the object you pass to a var parameters must be exactly the type of that parameter. If FreeAndNil was declared this way you would have to cast to TObject every time you called it.
So, the designers decided that the best solution to the design problem, the least bad choice, is to use the untyped var parameter.

Could omission of "^" when accessing a record pointer's members cause an access violation?

In VirtualTreeview, I am storing my data in the PVirtualNodes. I have experienced several Access Violations (typically with "Read of adress 00000000") in my App, and they mostly (I'd actually dare to say Always) occur when I am doing something with my Node Data.
However, the thing is, I declare my stuff & use it like this:
// DUMMY CODE - Not written or tested in IDE
var
MyNode : PVirtualNode;
MyData : PMyNodeData;
Begin
MyNode := VST.GetFirstSelected;
if Assigned(MyNode) then
Begin
MyData := VST.GetNodeData(MyNode);
if Assigned(MyData) then
Begin
MyData.DummyProperty := 'Test';
End;
End;
End;
As you probably noticed, I do not "dereference" (correct?) my "MyData" by doing MyData^! The reason I don't is that I have been told it was not necessary to add the caret to the pointer name, however I have a feeling that it has something to do with it. If I knew, I wouldn't be posting on here. ;)
So my question is: Is it in the end necessary to add the little ^ to MyData? And is it possible that by not doing that, I may provoke an Access Violation?
When you have a pointer to a record, then you can omit the ^. The following are equivalent:
MyData.DummyProperty
MyData^.DummyProperty
This is also the case for the deprecated Turbo Pascal object. I would expect it to be so for Delphi classes, although I have never tried with them since they are already reference types.
Sadly, this is not the explanation for your AV.
Using ^ to dereference records is optionnal as it is assumed implicitly by the compiler. When not using any hard typecast, any situation that would requires the "^" would not compile. But only 1 level of dereferencing is implicit.
type
TMyRecord = record
MyField : Integer;
end;
PMyRecord = ^TMyRecord;
PPMyRecord = ^PMyRecord;
procedure DoSomething;
var vMyField : PPMyRecord;
begin
vMyField.MyField; <---Won't compile
vMyField^.MyField; <---Will compile
end;
As for your access violation, here's my best guess based on what you wrote... Assuming your exemple is representative (i.e. that is, crash on assigning a string), and assuming PMyNodeData points to a record. I'd guess that PMyNodeData's memory was reserved with "GetMem" instead of "New", making the string field of the record uninitialized.
There is an exception where Data.xx and Data^.xx are not the same: when the field pointed at is of the same pointer type or the generic pointer type:
var
x: PPointer;
y: Pointer;
begin
x := GetPPointer();
y := x;
y := x^;
end;
I consider it best practice to always add the operator ^ when the pointed value is used to avoid ambiguous situations like above.
Given your example: The problem is possibly memory corruption. Did you set NodeDataSize correctly?

Resources