Categories: delphi, vbscript, com

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

1 answer

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?

Received answer to this question:
The best answer according to the author of the question:

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.