3

I am trying to pass an array of bytes from VBscript to my windows Delphi Application and can't seem to find the correct syntax to interpret the passed data.

The requirement is fairly simple as the VBscript snippet below demonstrates

Dim inst,arr(5)

Sub Main
  set inst=instruments.Find("EP1")
  arr(0) = 0
  arr(1) = 1
  arr(2) = 2
  arr(3) = 3
  arr(4) = 4
  inst.writebytes arr,5
end Sub

I can get the server to accept the olevariant passed by the script but the data seems garbled, my example server code is shown below and is based on the Stackoverflow question here How to use variant arrays in Delphi

procedure TInstrument.WriteBytes(Data: OleVariant; Length: Integer);
var i,n:integer; Pdat:Pbyte; Adata:PvarArray;
begin
  if VarIsArray(data) then
  begin
    n:=TVarData(Data).VArray^.Bounds[0].ElementCount;
    Adata:= VarArrayLock(Data);
    Getmem(Pdat,length);
    try
      for i:=0 to length-1 do
        Pdat[i]:=integerArray(Adata.data^)[i];
      Finstrument.WriteBytes(Pdat,Length);
    finally
      freemem(Pdat)
    end;
  end;
end;

So the idea is to accept the integers passed by the script, convert it to the local data representation (array of byte) then pass it on to my function to use the data.

I have tried several different data types and methods to try and get some ungarbled data out of the variant all to no avail.

What is the correct method of extracting the array data from the passed variant?

Also, TVarData(Data).VArray^.Bounds[0].ElementCount has a value of zero, why would that be?

Andy k
  • 1,056
  • 1
  • 11
  • 22
  • 3
    1. You're assuming VBScript is passing you a variant array of integers but it might be a variant array of variants instead. (Check `VarType(Data)`). 2. You should use the variant access APIs: check that `VarArrayDimCount(Data) = 1` and then use `for I := VarArrayLowBound(Data, 1) to VarArrayHighBound(Data, 1) do ... VarArrayGet(Data, [I])` – Ondrej Kelle Jan 14 '20 at 12:21
  • Thanks, Yes the help file lists the Variants functions but I'm struggling to find any information on how to use them. Its not intuitive. I'll check out to see if they get me any further. – Andy k Jan 14 '20 at 13:32

1 Answers1

5

Arrays created in VBScript are

  1. zero based
  2. untyped
  3. declared with upper bound (not size as you assumed; size of array declared as Dim arr(5) is 6)
  4. include dimension info in them (so you don't need to pass it along with the array)

When used in COM, they are passed as variant arrays of type varVariant (as the Ondrej Kelle points out in his comment). To process such an array in your method you have to assert that:

  1. the value is a single dimensional array
  2. each element can be converted to byte

You can write helper routine for that:

function ToBytes(const Data: Variant): TBytes;
var
  Index, LowBound, HighBound: Integer;
  ArrayData: Pointer;
begin
  if not VarIsArray(Data) then
    raise EArgumentException.Create('Variant array expected.');
  if VarArrayDimCount(Data) <> 1 then
    raise EArgumentException.Create('Single dimensional variant array expected.');
  LowBound := VarArrayLowBound(Data, 1);
  HighBound := VarArrayHighBound(Data, 1);
  SetLength(Result, HighBound - LowBound + 1);
  if TVarData(Data).VType = varArray or varByte then
  begin
    ArrayData := VarArrayLock(Data);
    try
      Move(ArrayData^, Result[0], Length(Result));
    finally
      VarArrayUnlock(Data);
    end;
  end
  else
  begin
    for Index := LowBound to HighBound do
      Result[Index - LowBound] := Data[Index];
  end;
end;

for loop in the routine will be horribly slow when processing large arrays, so there's optimization for special case (variant array of bytes) that uses Move to copy bytes to result. But this will never happen with VBScript array. You might consider using VB.Net or PowerShell.

Using such a routine has downside of keeping 2 instances of the array in memory - as variant array and as byte array. Use it as a guide when applying it to your use case.

Peter Wolf
  • 3,700
  • 1
  • 15
  • 30