Paul
Paul

Reputation: 26650

Pass TDateTime value to OleVariant property through RTTI

When I assign a TDateTime value to OleVariant property of an object using RTTI, object becomes Float value.

The object is designed so that this property can become either Null or value of any data type. If it becomes float, then result should be calculated as a difference of floats. If it becomes TDateTime, then result should be calculated as a difference of two TDateTime values.

Would I have passed the value directly to it, it will work fine, but there is RTTI in the middle.

I know that TDateTime is internally repersented as float, but is there a possibility to receive exactly the data type that I send?

Please look at the tkVariant case in following sample of code:

class procedure TRTTI.SetObjPropValue(obj: TObject; rprop: TRttiProperty; value: OleVariant);
var
  rtyp: TRttiType;
  vt: TVarType;
begin
  if obj = nil then Exit();
  if (rprop <> nil) and (rprop.IsWritable) then begin
    case rprop.PropertyType.TypeKind of
      tkInteger, tkInt64:
        begin
          value := TVarConv.NullableCurr(value);
          if VarIsNumeric(value) then rprop.SetValue(obj, TValue.FromVariant(Trunc(value)));
        end;
      tkFloat:
        begin
          if rprop.PropertyType.Name = 'TDateTime' then
            value := TVarConv.NullableDateTime(value)
          else
            value := TVarConv.NullableFloat(value);
          if not VarIsNull(value) then rprop.SetValue(obj, TValue.FromVariant(value));
        end;
      tkChar, tkString, tkWChar, tkLString, tkWString, tkUString:
        begin
          rprop.SetValue(obj, TValue.FromVariant(VarToStr(value)));
        end;
      tkEnumeration:
        begin
          if rprop.PropertyType.Name = 'Boolean' then
            value := TVarConv.NullableBool(value)
          else
            value := null;
          if not VarIsNull(value) then rprop.SetValue(obj, TValue.FromVariant(value));
        end;
      tkVariant:
        //Here I transmit the TDateTime value
        rprop.SetValue(obj, TValue.FromVariant(value));
        //An object receives Float
    end;
  end;
end;

Upvotes: 2

Views: 424

Answers (1)

Stefan Glienke
Stefan Glienke

Reputation: 21713

The issue here is that TValue.FromVariant is internally "unpacking" the passed Variant , storing the underlying value inside the TValue. In your case it properly notices that a TDateTime is stored in the Variant and stores it as TDateTime.

When you pass that TValue containing a TDateTime (which is of TypeKind tkFloat) down to the SetValue of the TRttiProperty it does a conversion to the type of the property which is Variant - see System.Rtti.Conv2Variant. This method ignores the fact that a tkFloat can be a TDateTime but simply puts a Variant that stores a float into the result.

The solution is simple: don't use TValue.FromVariant but simply TValue.From<Variant>(value). That way you are storing the Variant value inside the TValue and pass it as it is without any unnecessary implicit type conversions to the setter.

Reported as https://quality.embarcadero.com/browse/RSP-21176

Upvotes: 6

Related Questions