Search code examples
delphidelphi-xe3rtti

How do I use a string in TRttiMethod.Invoke as parameter properly?


I'm trying to generalize the content validation of visual components with the Text-property using RTTI but when I try to pass a string value into TRttiMethod.Invoke, I get the Message "Invalid Typecast". (Actually "Ungültige Typumwandlung" but I guess, that was a fitting translation.)

The code below is stripped of all security measures, assertions and so on, assuming all passed objects are just perfect.

procedure ValidateTextFieldAndSetFocus(const Field: TObject; const Validator: TObject; const errorStates: array of TStringValidationResult; const sErrorMessage: string);
var
  context  : TRttiContext;
  objField : TRttiType;
  objValid : TRttiType;
  prop     : TRttiProperty;
  execute  : TRttiMethod;
  I        : Integer;
  validResult : TStringValidationResult;
  value    : TValue;
begin
  context  := TRttiContext.Create;
  objField := context.GetType(Field.ClassInfo);
  objValid := context.GetType(Validator.ClassInfo);
  prop     := objField.GetProperty('Text');
  value    := prop.GetValue(Field);
  execute  := objValid.GetMethod('Execute');
  for I := 0 to High(errorStates) do
    if execute.Invoke(Validator,[value]).TryAsType<TStringValidationResult>(validResult) then
      if validResult = errorStates[I] then
      begin
        SetFocusIfCan(Field);
        raise Exception.Create(sErrorMessage);
      end;
end;

The Validator's Execute only has one string-Parameter. I've seen examples where strings were passed directly into the array of TValue, but then I get the same typecast error.

edit:

The actual error appears in execute.Invoke(Validator,[value]).

Example

TNoSemicolonNullValidator = class
  class function Execute(const aStr: string): TStringValidationResult;
end;

procedure TestValidation;
var
  Validator : TNoSemicolonNullValidator;
begin
  Validator := TNoSemicolonNullValidator.Create;
  try
    ValidateTextFieldAndSetFocus(Edit1,Validator,[svInvalid],'Edit1 is invalid!');
  finally
    Validator.Free;
  end;
end;

Solution

  • You are calling a class function here but you are passing a TObject as first parameter (which is the hidden Self argument of non static methods). On a class method the Self parameter must not be an instance but the class of it. So the correct call would be:

    execute.Invoke(validator.ClassType, [value]);
    

    Here is a minimal example to prove that:

    program Project1;
    
    {$APPTYPE CONSOLE}
    
    uses
      Rtti,
      SysUtils;
    
    type
      TValidator = class
        class function Execute(const s: string): Boolean;
      end;
    
    class function TValidator.Execute(const s: string): Boolean;
    begin
      Writeln(s);
    end;
    
    var
      ctx: TRttiContext;
      v: TValidator;
    begin
      v := TValidator.Create;
      try
        ctx.GetType(TValidator).GetMethod('Execute').Invoke(v, ['test']);
      except
        on E: Exception do
          Writeln(E.ClassName, ': ', E.Message);
      end;
      try
        ctx.GetType(TValidator).GetMethod('Execute').Invoke(v.ClassType, ['test']);
      except
        on E: Exception do
          Writeln(E.ClassName, ': ', E.Message);
      end;
      Readln;
    end.