2
votes

I need to call a function expecting an array of Integer, but I have my values in a variable of type Variant, containing the array.

Do I really have to copy the values in a loop? I couldn't find a better way that works.

The same variant can also hold a single Integer instead of the array, so I created a helper function allowing both (checking with VarIsArray). It works, but it is just lengthy and not nice :)

type
  TIntegerArray = array of Integer;

function VarToArrayInt(const V: Variant): TIntegerArray;
var
  I: Integer;
begin
  if VarIsArray(V) then begin
    SetLength(Result, VarArrayHighBound(V, 1) + 1);
    for I:= 0 to High(Result) do Result[I]:= V[I];
  end else begin
    SetLength(Result, 1);
    Result[0]:= V;
  end;
end;

I'm using Delphi 10.2.2 and the function to be called cannot be changed and looks like this:

function Work(Otherparameters; const AParams: array of Integer): Boolean;
2
I would really appreciate it if downvoters could leave a short explanation and what I should have done better. This is a real question, I found a working solution myself, but I hoped for and got a much better one and also learned a lot. What's wrong with it?maf-soft

2 Answers

5
votes

If the function takes an array of Integer as a separate type, eg:

type
  TIntegerArray = array of Integer;

function DoIt(const Values: TIntegerArray): ReturnType;

Then the function takes a Dynamic Array as input. You can assign/pass a Variant holding an array to a Dynamic Array variable/parameter. The compiler is smart enough to call the RTL's VarToDynArray() function to allocate a new Dynamic Array that has a copy of the Variant's array elements. There is no way to pass a Variant holding an array to a Dynamic Array without making a copy of the array data.

However, if the function takes an array of Integer directly in its parameter list instead, eg:

function DoIt(const Values: array of Integer): ReturnType;

Then it takes an Open Array as input:

an Delphi function that has an open array parameter can be called by explicitly passing two parameters:

  • A pointer to the first element of the array
  • A count, which is the value of the last index (that is, the size/number of array elements, minus one)"

You can't pass a Variant (whether it holds an array or not) directly to an Open Array parameter. The compiler is not smart enough to extract the array pointer and element count and pass them to the Open Array parameter. However, you can do it manually with a little typecast trickery, eg:

function DoIt(const Values: array of Integer): ReturnType;

...

type
  TOpenArrayFunc = function(const Values: PInteger; ValuesHigh: Integer): ReturnType;

var
  V: Variant;
  Count: Integer;
  P: PInteger;
begin
  ...
  V := ...;
  Count := VarArrayHighBound(V, 1) - VarArrayLowBound(V, 1) + 1;
  P := VarArrayLock(V);
  try
    TOpenArrayFunc(@DoIt)(P, Count-1);
  finally
    VarArrayUnlock(V);
  end;
  ...
end;

This passes the Variant's array directly to the function without making any copies of the array elements at all.

2
votes

Fortunately there is no need for a loop, at least when the array is 0-based.

If the called function would expect a dynamic array, you could just pass the Variant as it is. You can also directly assign it to a dynamic array variable.

In your case it is an open array parameter, and that needs casting in this case.

Here is some demonstration of what is possible and how, including a nice and short helper function allowing both arrays and single values.

program Test;

uses Variants;

procedure PrintOpenArray(const Arr: array of Integer); {open array parameter}
var
  I: Integer;
begin
  for I in Arr do Writeln(I);
end;

procedure PrintDynamicArray(const Arr: TArray<Integer>); {dynamic array param}
begin
  PrintOpenArray(Arr);
end;

function VarToArrayInt(const V: Variant): TArray<Integer>;
begin
  if VarIsArray(V) then Result:= V else Result:= [V];
  {[V] works only in XE7 and up. You can use TArray<Integer>.Create(V) instead}
end;

type  {dynamic integer array, but only compatible to this type}
  TIntegerArray = array of Integer;

var
  V: Variant;
  A: TArray<Integer>; {dynamic array, compatible to any other TArray<Integer>}
begin {all the following only works with 0-based arrays!}
  V:= VarArrayCreate([0, 2], varInteger);
  V[0]:= 1;
  V[1]:= 2;
  V[2]:= 3;

  A:= V; {Variant can just be assigned to dynamic array if it contains an array}
  PrintOpenArray(A);

  PrintDynamicArray(V); {works directly without casting}
  PrintOpenArray(TArray<Integer>(V)); {not possible without casting}
  PrintOpenArray(TIntegerArray(V));
  PrintOpenArray(VarToArrayInt(V));

  V:= 4; {demonstration of helper function to allow arrays and single values}
  PrintOpenArray(VarToArrayInt(V));
  PrintDynamicArray(VarToArrayInt(V));

  Readln;
end.