Michael Nicht - WikiCommons

This blog post is about an annoying bug I have found in Delphi, and the consequences of it.

The Invokable variants bug

During preparation for an Android 64 bit update of one of the apps I have made and that currently is released as Android 32 bit, which is no longer supported by the latest Samsung phones (amongst others), I found that the application, which otherwise works fine in Win32, Win64, Android 32 and IOS 64 bit mode, experienced a weird problem when compiled for Android 64 bit mode.

I have latest Delphi 10.4.2 installed with up to date patches.

The problem cause the kbmMW SmartClient syntax not to be usable on Android 64 bit, which is a bummer. However it is fortunately easy to circumvent, since the SmartClient syntax is just a syntactical sugar alternative to the old style SmartClient Request method call, so it is not a showstopper for me, but it does cause me to rewrite a few lines making calls to the kbmMW application server.

I have already reported this problem to Embarcadero as RSP-34063

To illustrate the problem I made a simple FMX application with a basic form and some code.

The form looks like this:

And the code like this:
unit Unit6;

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,
  FMX.Memo.Types, FMX.ScrollBox, FMX.Memo;

type
  TTestVariantType = class(TInvokeableVariantType)
  public
    procedure Clear(var V: TVarData); override;
    procedure Copy(var Dest: TVarData; const Source:TVarData; const Indirect:boolean); override;
    function DoProcedure(const V:TVarData; const Name:string; const Arguments:TVarDataArray):boolean; override;
    function DoFunction(var Dest:TVarData; const V:TVarData; const Name:string; const Arguments:TVarDataArray):boolean; override;
  end;

  TForm6 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    FTestVariantType:TTestVariantType;
  public
    { Public declarations }
    constructor Create(Owner:TComponent); override;
    function CreateTestVariant:variant;
    procedure Test;
  end;

var
  Form6:TForm6;

implementation

{$R *.fmx}

procedure TTestVariantType.Clear(var V: TVarData);
begin
     V.VType:=varEmpty;
end;

procedure TTestVariantType.Copy(var Dest:TVarData; const Source:TVarData; const Indirect:boolean);
begin
     if Indirect and VarDataIsByRef(Source) then
        VarDataCopyNoInd(Dest,Source)
     else
     begin
          TVarData(Dest).VType:=TVarData(Source).VType;
          TVarData(Dest).VPointer:=nil;
     end;
end;

function TTestVariantType.DoProcedure(const V:TVarData; const Name:string; const Arguments:TVarDataArray):boolean;
begin
     Result:=True;
end;

function TTestVariantType.DoFunction(var Dest:TVarData; const V:TVarData; const Name:string; const Arguments:TVarDataArray):boolean;
var
   s:string;
   i,n:integer;
   v1:variant;
   vd:TVarData;
begin
     n:=length(Arguments);
     Form6.Memo1.Lines.Add('Will attempt to show '+inttostr(n)+' arguments:');
     for i:=0 to n-1 do
     begin
          vd:=Arguments[i];
          v1:=Variant(vd);

          s:=v1;
          Form6.Memo1.Lines.Add(inttostr(i)+') VType='+inttostr(vd.VType)+'='+s);
     end;
     Form6.Memo1.Lines.Add('-----');
     Result:=true;
end;

// ---------------------------------------
// TForm6
// ---------------------------------------

constructor TForm6.Create(Owner:TComponent);
begin
     inherited Create(Owner);
     FTestVariantType:=nil;
end;

procedure TForm6.Button1Click(Sender: TObject);
begin
     FTestVariantType:=TTestVariantType.Create;
     try
        Test;
     finally
        FreeAndNil(FTestVariantType);
     end;
end;

function TForm6.CreateTestVariant:variant;
begin
     TVarData(Result).VType:=FTestVariantType.VarType;
end;

procedure TForm6.Test;
var
   V1,V2:variant;
begin
     V1:=CreateTestVariant;

     // Make two function calls.
     // Win32, Win64, IOS 64, Android 32 all works well.
     // Android 64 provides incorrect argument management invalidating the 3 string arguments.
     V2:=V1.func('DEF','','');

     // Extra call to see if argument order makes any distinction to the outcome of the error. It does not.
     V2:=V1.func('','','ABC');
end;

end.

What the code does is to create a sample invokable variant. An invokable variant is a variant that can be used for calling late bound functions and procedures. In other words you can say v.MyProc(..) without ever having a true function in your code called MyProc. What happens is that the Delphi compiler generates code that will instead call a special default abstract function that you will need to override, called DoProcedure, providing the name of the late bound function name (MyProc) as a string, and the arguments as an array of TVarData.

The programmer can then choose to do what he want to do in that situation. From the outside, your code looks like you are calling a true function that is linked in, but in reality it is always ending up in your invokable variants DoProcedure or DoFunction method.

Hence the above code continues to call the late bound (non existing in reality) function called func with 3 string arguments, and then again, just with a different set of string arguments.

When it runs fine, which it does on Win32, Win64, IOS 64 and Android 32 (I have not checked Linux), the form will look like this (true screenshot from a Samsung S9+ with Android 10):

But when compiling for Android 64 bit and running it on the same Samsung S9+, this is the result:

It is clear that the string arguments has been messed thoroughly up.

I have tried to debug what happens, to see if I could patch the bad behaviour from the outside (without changing system code in Delphi), but I did not find the solution.

So for the time being I can unfortunately not recommend using one of SmartClient’s smartest features, the late bound function call.

Loading

2 thoughts on “Delphi revelations #8 – Delphi bugs – Invokable variants bug using Android 64”
    1. Hi,
      I think that the issue is somewhat different. I do provide Unicode strings as the arguments, and it does work on IOS 64, which afaik mimics MacOS closely API and handling wise. But I have not tested it as an OSX application so I do not know if “my” problem will be there on OSX or not.
      /Kim

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.