Search code examples
delphivbscriptcom

How to interpret arrays passed from VBscript to a Delphi COM server App


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?


Solution

  • 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.