TValue string<-->Boolean back and forth - delphi

I'm playing arround with TValue
I've written this code in a blank project:
uses
RTTI;
procedure TForm1.FormCreate(Sender: TObject);
var
s: string;
b: Boolean;
begin
s := TValue.From<Boolean > (True).ToString;
b := TValue.From<string > (s).AsType<Boolean>;
end;
But I can not convert back from string to boolean; I get an Invalid Typecast exception in the second line.
I'm using Delphi XE but it is the same result in Delphi Xe6 which leads me to the conclusion: I'm using TValue wrong.
So please what am I doing wrong.

Although you give Boolean as the example in your question, I'm going to assume that you are really interested in the full generality of enumerated types. Otherwise you would just call StrToBool.
TValue is not designed to perform the conversion that you are attempting. Ultimately, at the low-level, the functions GetEnumValue and GetEnumName in the System.TypInfo unit are the functions that perform these conversions.
In modern versions of Delphi you can use TRttiEnumerationType to convert from text to an enumerated type value:
b := TRttiEnumerationType.GetValue<Boolean>(s);
You can move in the other direction like this:
s := TRttiEnumerationType.GetName<Boolean>(b);
These methods are implemented with calls to GetEnumValue and GetEnumName respectively.
Older versions of Delphi hide TRttiEnumerationType.GetValue and TRttiEnumerationType.GetName as private methods. If you are using such a version of Delphi then you should use GetEnumName.

TValue is not meant to convert types that are not assignment compatible. It was designed to hold values while transporting them in the RTTI and to respect the assignment rules of Delphi.
Only ToString can output the value in some string representation but a type that you cannot simply assign a string to will also fail when doing that with TValue.
TValue is not a Variant.
If you want to convert a string to boolean and back then use StrToBool and BoolToStr.

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.

Implementing Custom Binary Search for TObjectList<myClass> (Delphi XE)

I need to implement a binary search on TObjectList that uses a custom comparer, I believe using TCustomComparer.
Goal: binary search returns instances in the list that conform to a particular property parameter.
For example:
TMyClass=class
public
Index:integer
end;
TMyObjectList=TObjectList<TMyClass>;
begin
...
aMyClass.Index:=1;
aMyObjectList.binarysearch(aMyClass, aMyClassRef)
...
end;
Or simply:
begin
...
aMyObjectList.binarysearch(1, aMyClassRef)
...
end;
I want to loop and get back instances of TMyClass in the list that also have Index==1.
In C++, overloading the '==' operator achieves this goal.
The new Delphi 'help' is rather sparse and scattered around making things hard to find, and I'm not that familiar with all the nuances of the new Delphi generics.
So - how do I do it in Delphi XE with the Generics.TObjectList?
(Using Delphi XE).
TIA
The help file is indeed a little limited here. I generally just read the source code to Generics.Defaults and Generics.Collections. Anyway, you need to provide an IComparer<TMyClass>. There's lots of ways to do that. For example, using an anonymous function:
var
List: TObjectList<TMyClass>;
Target: TMyClass;
Index: Integer;
Comparer: IComparer<TMyClass>;
Comparison: TComparison<TMyClass>;
....
Comparison :=
function(const Left, Right: TMyClass): Integer
begin
//Result := ??;//your comparison rule goes here
end;
Comparer := TComparer<TMyClass>.Construct(Comparison);
List.BinarySearch(Target, Index, Comparer);
If you don't want to use an anonymous function you can implement Comparison some other way. For example a method of some object, or a class function, or even just a plain old fashioned non-OOP function. It just has to have the same signature as above.
As always with comparison functions, return <0 if Left<Right, >0 if Left>Right and 0 if Left=Right.

Delphi - records with variant parts

I want to have a record (structure) with a 'polymorphic' comportment. It will have several fields used in all the cases, and I want to use other fields only when I need them. I know that I can accomplish this by variant parts declared in records. I don't know if it is possible that at design time I can access only the elements I need. To be more specific, look at the example bellow
program consapp;
{$APPTYPE CONSOLE}
uses
ExceptionLog,
SysUtils;
type
a = record
b : integer;
case isEnabled : boolean of
true : (c:Integer);
false : (d:String[50]);
end;
var test:a;
begin
test.b:=1;
test.isEnabled := False;
test.c := 3; //because isenabled is false, I want that the c element to be unavailable to the coder, and to access only the d element.
Writeln(test.c);
readln;
end.
Is this possible?
All variant fields in a variant record are accessible at all times, irrespective of the value of the tag.
In order to achieve the accessibility control you are looking for you would need to use properties and have runtime checks to control accessibility.
type
TMyRecord = record
strict private
FIsEnabled: Boolean;
FInt: Integer;
FStr: string;
// ... declare the property getters and settings here
public
property IsEnabled: Boolean read FIsEnabled write FIsEnabled;
property Int: Integer read GetInt write SetInt;
property Str: string read GetString write SetString;
end;
...
function TMyRecord.GetInt: Integer;
begin
if IsEnabled then
Result := FInt
else
raise EValueNotAvailable.Create('blah blah');
end;
Even if I heard that by original Niklaus Wirth's Pascal definition all should work as you expected, I saw no such behaviour in Delphi, starting from its ancestor, Turbo Pascal 2.0. Quick look at FreePascal showed that its behaviour is the same. As said in Delphi documentation:
You can read or write to any field of any variant at any time; but if you write to a field in one variant and then to a field in another variant, you may be overwriting your own data. The tag, if there is one, functions as an extra field (of type ordinalType) in the non-variant part of the record."
Regarding your intent, as far as I understood it, I would use two different classes, kind of
a = class
b : Integer
end;
aEnabled = class(a)
c: Integer
end;
aDisabled = class(a)
d: String //plus this way you can use long strings
end;
This way you can get some support from IDE's Code Editor even at designtime. More useful, though, is that it will be much more easier to modify and support later.
However, if you need quick switching of record variable values at runtime, #David Heffernan's variant , to use properties and have runtime checks, is more reasonable.
The example given is NOT a variant record, it includes all the fields all the time.
A true variant record has the variants sharing the same memory. You just use the "case discriminator: DiscType of ..... " syntax, no need for a separate field telling you what variant is active.

How do I stop this Variant memory leak?

I'm using an old script engine that's no longer supported by its creators, and having some trouble with memory leaks. It uses a function written in ASM to call from scripts into Delphi functions, and returns the result as an integer then passes that integer as an untyped parameter to another procedure that translates it into the correct type.
This works fine for most things, but when the return type of the Delphi function was Variant, it leaks memory because the variant is never getting disposed of. Does anyone know how I can take an untyped parameter containing a variant and ensure that it will be disposed of properly? This will probably involve some inline assembly.
procedure ConvertVariant(var input; var output: variant);
begin
output := variant(input);
asm
//what do I put here? Input is still held in EAX at this point.
end;
end;
EDIT: Responding to Rob Kennedy's question in comments:
AnsiString conversion works like this:
procedure VarFromString2(var s : AnsiString; var v : Variant);
begin
v := s;
s := '';
end;
procedure StringToVar(var p; var v : Variant);
begin
asm
call VarFromString2
end;
end;
That works fine and doesn't produce memory leaks. When I try to do the same thing with a variant as the input parameter, and assign the original Null on the second procedure, the memory leaks still happen.
The variants mostly contain strings--the script in question is used to generate XML--and they got there by assigning a Delphi string to a variant in the Delphi function that this script is calling. (Changing the return type of the function wouldn't work in this case.)
Have you tried the same trick as with the string, except that with a Variant, you should put UnAssigned instead of Null to free it, like you did s := ''; for the string.
And by the way, one of the only reasons I can think of that requires to explicitly free the strings, Variants, etc... is when using some ThreadVar.

Resources