Jump to content
Andre1

Check base type of dedicated type

Recommended Posts

Hi,


I wrote a framework to call delphi methods from other programming languages. This works quite well, except for the method

procedure TBitmap.Clear(const AColor: TAlphaColor);


TAlphaColor is defined as dedicated type with base type Cardinal

TAlphaColor = type Cardinal;

 

The generic logic to execute delphi methods fails, because from the foreign programming language an Unsigned Integer is passed, while the logic expects
exactly TAlphaColor.

The condition "if parameters[Index].ParamType.Handle <> Args[Index].TypeInfo then" evaluates to false.

 

function executeInstanceMethod(Reference: Pointer; const AName: string;
  const Args: array of TValue): TValue;
var
  context: TRttiContext;
  instType: TRttiInstanceType;
  obj: TObject;
  meth: TRttiMethod;
  parameters: TArray<TRttiParameter>;
  Found: Boolean;
  index: Integer;
begin
  context := TRttiContext.Create;
  try
    meth := nil;
    Found := false;
    obj := TObject(Reference);
    instType := (context.GetType(obj.ClassType) as TRttiInstanceType);

    for meth in instType.GetMethods do
      if SameText(meth.Name, AName) then
      begin
        parameters := meth.GetParameters;

        if Length(Args) = Length(parameters) then
        begin
          Found := True;
          for Index := 0 to Length(parameters) - 1 do

            if parameters[Index].ParamType.Handle <> Args[Index].TypeInfo then
            begin

              IF Args[Index].IsObject AND Args[Index].AsObject.InheritsFrom
                (parameters[Index].ParamType.AsInstance.MetaclassType) then
              begin

              end
              else
              begin
                Found := false;
                Break;
              end;

            end;
        end;

        if Found then
          Break;
      end;

    if (meth <> nil) and Found then
    begin
      result := meth.Invoke(obj, Args);
    end
    else
      raise Exception.CreateFmt('method %s not found', [AName]);

  finally
    context.Free;
  end;
end;


I wonder whether there are some means to check whether the dedicated type (parameters[Index].ParamType.Handle) has the base type (Args[Index].TypeInfo) ?

Kind regards
André

Share this post


Link to post

Are you sure? Did you debug it properly?


Because the line 

if parameters[Index].ParamType.Handle <> Args[Index].TypeInfo then

works for me but because of the following line

if Args[Index].IsObject AND Args[Index].AsObject.InheritsFrom (parameters[Index].ParamType.AsInstance.MetaclassType) then

the Found variable is set to false.

 

So, if you added a new if-condition (or just comment out the IsObject-condition), you will see, that the TRttiMethod is there and can be called:
 

Full Example with a FMX form and a button:

 

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Controls.Presentation, FMX.StdCtrls,
  System.Rtti;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
  public
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

function executeInstanceMethod(Reference: Pointer; const AName: string;
  const Args: array of TValue): TValue;
var
  context: TRttiContext;
  instType: TRttiInstanceType;
  obj: TObject;
  meth: TRttiMethod;
  parameters: TArray<TRttiParameter>;
  Found: Boolean;
  index: Integer;
begin
  context := TRttiContext.Create;
  try
    meth := nil;
    Found := false;
    obj := TObject(Reference);
    instType := (context.GetType(obj.ClassType) as TRttiInstanceType);

    for meth in instType.GetMethods do
    begin
      if SameText(meth.Name, AName) then
      begin
        parameters := meth.GetParameters;

        if Length(Args) = Length(parameters) then
        begin
          Found := True;
          for Index := 0 to Length(parameters) - 1 do
          begin
            if parameters[Index].ParamType.Handle <> Args[Index].TypeInfo then
            begin

              if Args[Index].IsObject AND Args[Index].AsObject.InheritsFrom
                (parameters[Index].ParamType.AsInstance.MetaclassType) then
              begin

              end  // I added this line, because TAlphaColor ist obviously a Ordninal and not a class
              else if Args[Index].IsOrdinal then
              begin
                // everything is okay, Found is still True
              end else
              begin
                Found := false;
                Break;
              end;
            end;
          end;
        end;

        if Found then
          Break;
      end;
    end;

    if (meth <> nil) and Found then
    begin
      result := meth.Invoke(obj, Args);
    end
    else
      raise Exception.CreateFmt('method %s not found', [AName]);

  finally
    context.Free;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
    MyBitmap: TBitmap;
begin
    MyBitmap := TBitmap.Create(256, 256);
    try
        // if you set a breakpoint in FMX.Graphics.TBitmap.Clear, you will see that the Clear() method is called twice
        MyBitmap.Clear(TAlphaColorRec.Black);
        executeInstanceMethod(MyBitmap, 'Clear', [TAlphaColorRec.Black]);
    finally
        MyBitmap.Free;
    end;
end;

end.

 

Share this post


Link to post

Hi,
thanks for having a look. In the button1Click method you have used the dedicated type TAlphaColorRec.Black. This is correct in Delphi but I call the function from another programming language,
therefor I have only the primitive known to C. To simulate the behavior, the button1Click could look like this:

 

procedure TForm1.Button1Click(Sender: TObject);
var
    MyBitmap: TBitmap;
    black_card: Cardinal;
begin
    MyBitmap := TBitmap.Create(256, 256);
    try
        // if you set a breakpoint in FMX.Graphics.TBitmap.Clear, you will see that the Clear() method is called twice
        MyBitmap.Clear(TAlphaColorRec.Black);

        black_card := TAlphaColorRec.Black;
        executeInstanceMethod(MyBitmap, 'Clear', [black_card]);
    finally
        MyBitmap.Free;
    end;
end;


I noticed you added in the method executeInstanceMethod a  very specific solution for this sample. While this works fine for this specific case, it is not generic.
I will fail if the dedicated type is not based on Ordinal type.
 

else if Args[Index].IsOrdinal then


I wonder whether there is some generic function in Delphi to check whether the given arg from the method signature (in this case Cardinal) is the base type of the
called method parameter (in this case TAlphaColor)?

Kind regards
André


 

Share this post


Link to post
1 hour ago, Andre1 said:

procedure TForm1.Button1Click(Sender: TObject); var MyBitmap: TBitmap; black_card: Cardinal; begin MyBitmap := TBitmap.Create(256, 256); try // if you set a breakpoint in FMX.Graphics.TBitmap.Clear, you will see that the Clear() method is called twice MyBitmap.Clear(TAlphaColorRec.Black); black_card := TAlphaColorRec.Black; executeInstanceMethod(MyBitmap, 'Clear', [black_card]); finally MyBitmap.Free; end; end;

Hm, for me it works in Win64 Debug with Delphi 11.3 with the direct use of a Cardinal/UInt32 variable.
Do you get other results when you call from the C context?

 

Call Stack

FMX.Graphics.TBitmap.Clear(4278190080)
System.Rtti.RawInvoke(???,???)
System.Rtti.Invoke($E35330,(($E1DF70, Pointer($13D40A0) as IValueData, 0, 6656, 4038203904, $26BF0B21A00, TClass($26BF0B21A00), 0, 6656, -256763392, -4,40958110556075e+29, 1,31551053242737e-311, 1,31551053242737e-311, 2662622960128, 266262296,0128, 2662622960128, 2662622960128, ($26BF0B21A00, nil), $26BF0B21A00), ($C74CA0, Pointer($13D40A0) as IValueData, 0, 0, 4278190080, $FF000000, TClass($FF000000), 0, 0, -16777216, -1,70141183460469e+38, 2,11370674490681e-314, 2,11370674490681e-314, 4278190080, 427819,008, 4278190080, 4278190080, ($FF000000, nil), $FF000000)),ccReg,nil,False,False)
System.Rtti.TRttiInstanceMethodEx.DispatchInvoke(($E1DF70, Pointer($13D40A0) as IValueData, 0, 6656, 4038203904, $26BF0B21A00, TClass($26BF0B21A00), 0, 6656, -256763392, -4,40958110556075e+29, 1,31551053242737e-311, 1,31551053242737e-311, 2662622960128, 266262296,0128, 2662622960128, 2662622960128, ($26BF0B21A00, nil), $26BF0B21A00),(...))
System.Rtti.TRttiMethod.Invoke($26BF0B21A00,(...))
Unit1.executeInstanceMethod($26BF0B21A00,'Clear',(...))
Unit1.TForm1.Button1Click(???)



 

Share this post


Link to post

Hi,

it works due to the isOrdinal addition, but this has 2 issues:

 

if Args[Index].IsObject AND Args[Index].AsObject.InheritsFrom
(parameters[Index].ParamType.AsInstance.MetaclassType) then
begin

end  // I added this line, because TAlphaColor ist obviously a Ordninal and not a class
else if Args[Index].IsOrdinal then
begin
// everything is okay, Found is still True
end else

1) The framework I build should work with any dedicated type, not only with Ordinal types. Therefore this solution is limited
to this specific sample (TBitmap.Clear) while I search a solution which will work with any dedicated types, indepedent of the base type.

2) It introduces a bug. Without the isOrdinal addition I will get the exception "Method not found" in case a wrong argument type is provided.
But this does not work now anymore. For example if I call now this method with a cardinal, a conversion exception is thrown instead:

executeInstanceMethod(MyBitmap, 'LoadFromFile', [black_card]);



(As explanation the inheritsFrom function is used to enable calling methods with sub classes. As example, a method expect a class of type
Animal. The Inheritsfrom Method is needed to allow calling the method with a class Dog or Duck. Exactly the same mechanism I search for
the issue with dedicated types).

I need to identify the case that I have a dedicted type and need to verify that the passed argument is the base type of the dedicated type in a generic way.
Therefor it must also work if the dedicated type is not a ordinal....

Kind regards
André
 

Share this post


Link to post

And when you break the boolean check for the correct method and argument types into two different variables?

 

function executeInstanceMethod(Reference: Pointer; const AName: string;
  const Args: array of TValue): TValue;
var
  context: TRttiContext;
  instType: TRttiInstanceType;
  obj: TObject;
  meth: TRttiMethod;
  parameters: TArray<TRttiParameter>;
  MethodIsFound, ArgumentTypesAreEqual: Boolean;
  index: Integer;
begin
  context := TRttiContext.Create;
  try
    meth := nil;
    MethodIsFound := false;
    obj := TObject(Reference);
    instType := (context.GetType(obj.ClassType) as TRttiInstanceType);

    for meth in instType.GetMethods do
    begin
      MethodIsFound := SameText(meth.Name, AName);
      if MethodIsFound then
      begin
        parameters := meth.GetParameters;
        ArgumentTypesAreEqual := False;
        if Length(Args) = Length(parameters) then
        begin
          for Index := 0 to Length(parameters) - 1 do
          begin
            ArgumentTypesAreEqual := parameters[Index].ParamType.Handle.Kind = Args[Index].TypeInfo.Kind;

            if not ArgumentTypesAreEqual then
            begin
                raise Exception.CreateFmt('Argument type of %s is not %s, is %s', [parameters[Index].Name, parameters[Index].ParamType.Name, Args[Index].TypeInfo.NameFld.ToString]);
                Break;
            end;
          end;
        end;

        if MethodIsFound then
          Break;
      end;
    end;

    if (meth <> nil) and MethodIsFound and ArgumentTypesAreEqual then
    begin
      result := meth.Invoke(obj, Args);
    end
    else
      raise Exception.CreateFmt('method %s not found', [AName]);

  finally
    context.Free;
  end;
end;

 

Edited by TiGü

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×