dmitrybv 5 Posted November 1, 2024 Hello. My TValue.ToString for TAlphaColor type returns a strange result. I can't figure out if it's a bug or if I'm using the TValue.ToString function incorrectly Here's an example of code. type TColorObj = class(TPersistent) private FColorProp: TAlphaColor; published property ColorProp: TAlphaColor read FColorProp write FColorProp; end; procedure TFormSimpleDraw2.Button1Click(Sender: TObject); var RttiContext: TRttiContext; Val: TValue; LType: TRttiType; ColorObj: TColorObj; AColorProp: TRttiProperty; begin ColorObj := TColorObj.Create; ColorObj.ColorProp := 4294303411; //TAlphaColorRec.Wheat RttiContext := TRttiContext.Create; LType := RttiContext.GetType(TColorObj); AColorProp := LType.GetProperty('ColorProp'); Val := AColorProp.GetValue(ColorObj); //Memo1.Lines.Add('ColorObj.ColorProp.ToString = ' + ColorObj.ColorProp.ToString); Memo1.Lines.Add('ColorObj.ColorProp.ToString = ' + Cardinal(ColorObj.ColorProp).ToString); Memo1.Lines.Add('TValue.ToString = ' + Val.ToString); ColorObj.Free; end; Here's the result. ColorObj.ColorProp.ToString = 4294303411 TValue.ToString = 18446744073708887731 Embarcadero® RAD Studio 12 Version 29.0.53571.9782 Delphi 12 Update 2. Share this post Link to post
Remy Lebeau 1619 Posted November 1, 2024 (edited) I can reproduce the issue. Interestingly, if you don't use the TRttiContext and just assign the ColorProp directly to the TValue then TValue.ToString() works as expected: //Val := AColorProp.GetValue(ColorObj); Val := ColorObj.ColorProp; // Val.ToString now returns '4294303411' In both cases, the numeric value (inside the TValue.FAsULong field) is correct, but the RTTI for the value different - in the first case, the TValue.FTypeInfo belongs to TAlphaColor, but in the second case the TValue.FTypeInfo belongs to Cardinal instead! Both RTTIs have Kind=tkInteger and OrdType=otULong, so the numeric value is stored in TValue.FAsULong and TValue.ToString() formats as a UInt64. But, the TValue.AsUInt64 property getter behaves differently on the two RTTI types: function TValue.AsUInt64: UInt64; begin if not IsEmpty then begin if FTypeInfo = System.TypeInfo(Int64) then Exit(FAsSInt64) else if FTypeInfo = System.TypeInfo(UInt64) then Exit(FAsUInt64) else if FTypeInfo = System.TypeInfo(Cardinal) then Exit(FAsULong) // <-- SECOND CASE else if FTypeInfo^.Kind = tkInteger then Exit(AsInteger); // <-- FIRST CASE end; AsTypeInternal(Result, System.TypeInfo(UInt64)); end; function TValue.AsInteger: Integer; begin if not IsEmpty then begin if FTypeInfo = System.TypeInfo(Integer) then Exit(FAsSLong) else if FTypeInfo^.Kind = tkInteger then case GetTypeData(FTypeInfo)^.OrdType of otSByte: Exit(FAsSByte); otSWord: Exit(FAsSWord); otSLong: Exit(FAsSLong); else Exit(FAsULong); // <-- FIRST CASE end; end; AsTypeInternal(Result, System.TypeInfo(Integer)); end; In the first case, TValue.FTypeInfo does not belong to either UInt64 or Cardinal (it belongs to TAlphaColor), so the TValue.FAsULong field (whose unsigned value is 4294303411, hex $FFF5DEB3) first gets converted to a signed Integer, resulting in a negative value of -663885 (hex $FFF5DEB3), which is then converted up to a sign-extended UInt64, resulting in a large unsigned value of 18446744073708887731 (hex $FFFFFFFFFFF5DEB3). In the second case, the TValue.FTypeInfo belongs to Cardinal (an unsigned type), so the TValue.FAsUlong field (hex $FFF5DEB3) is converted as-is to a zero-extended UInt64, preserving the original value (hex $00000000FFF5DEB3). Edited November 1, 2024 by Remy Lebeau 7 Share this post Link to post
darnocian 99 Posted November 6, 2024 (edited) AColorProp := LType.GetProperty('ColorProp'); Val := AColorProp.GetValue(ColorObj); //Memo1.Lines.Add('ColorObj.ColorProp.ToString = ' + ColorObj.ColorProp.ToString); Memo1.Lines.Add('ColorObj.ColorProp.ToString = ' + Cardinal(ColorObj.ColorProp).ToString); Memo1.Lines.Add('TValue.ToString = ' + Val.ToString); Should Val.ToString not be Val.AsType<TColorObj>.ToString ? I don’t think TValue.ToString is what you were expecting. It is more diagnostic in nature if you look at its implementation, as it supports all kinds of types. I suspect that what you’re seeing is simply an interpretation based on the contained value. However, in RTTI, these values are usually cast to their correct types—especially when passed to a TRttiMethod or similar, where FTypeInfo is considered appropriately. Edited November 6, 2024 by darnocian Share this post Link to post