Wednesday, August 15, 2007

JScript arrays and COM objects, part 2

In the previous post I have been discussing the scenario where we have COM object and the script in JScript and we need to interchange the array of data between them. I have demonstrated how to convert, in your JScript code, the vb-style array received from call to ActiveX object to the array native to JScript. To that, fortunately, the architects of JScript provided us with a tool - VBScript class. However, in reverse situation we don't have any object or routine in JScript scripting engine that would let us or help us converting JScript array to VBArray.

Eric Lippert in his blog states that, although he started writing code to manage conversion to VBArray, it was eventually abandoned and there is no way to do this.
And, actually, this is a truth. JScript engine does not provide us with any way to do that conversion. But why not try looking for it outside the engine but in what is by default available?
Fortunately, here our quest is successful and what we find is that there a COM object in WSH called Scripting.Dictionary. It is, well, as its name says, a dictionary, but what would be most important for you is that it has a method called Items which returns all the items in the dictionary as a VBArray object. So now, I am sure, you know what to do. Here is a code snippet of the function that converts JScript arrays into VBArray:

function GetVBArray(jsArray)
{
var dict =
new
ActiveXObject("Scripting.Dictionary");

for (i in jsArray)
{
dict.add(i, objJSArray[i]);
}

return dict.Items();
}

However, it may have some drawbacks like efficiency. But if it is not critical to your script it will suffice. On the other hand, if you are also an author of the COM object it would be better to just pass JScript array and deal with it in the code, wouldn't it?

If you read my previous post you probably remember that I wrote that in JScript arrays are nothing more than objects. You may also remember that JScript passes objects to automation objects as a pointer to IDispatch interface. Therefore, we may query the IDispatch object about the elements of array, can't we?

Below there is a piece of code in pascal (delphi) of the method that can both accept and process VBArrays and JScript arrays (to be precise, one-dimensional one):

procedure TSomeObject.ProcessArray(SomeArr: OleVariant);
var
i: integer;
ind: array [0..0] of integer;
begin
if VarIsArray(SomeArr) then // (1)
begin
for i := VarArrayLowBound(SomeArr, 1)
to
VarArrayHighBound(SomeArr, 1) do
begin
ind[0] := i;
ProcessElement(VarArrayGet(SomeArr, ind));
end;
end
else if VarIsType(SomeArr, VT_DISPATCH) then // (2)
begin
for i := 0 to SomeArr.length - 1 do
begin
ProcessElement(SomeArr.pop());
end;
end
else
raise EOleException.Create
('Parameter is not an array!',
E_INVALIDARG, '', '', 0);
end;

My example is in Delphi, but if you prefer, for instance, programming in C++ and ATL you can follow the same reasoning. However it will consume more code, since C++ semantics do not allow that you do late binding and you need to do that manually by pairs of calls to IDispatch's GetIDsOfNames and Invoke. In Delphi, we could use its magic of having IDispatch reference in Variant variable and leave it to Delphi to play with name resolving and calls to IDispatch methods.

Now let us analyze the above code. Firstly, in the if in (1) we are checking if the variant contains the traditional VBArray. If so, we process as we normally do with safe arrays. If not, we again examine our variant parameter if its content is IDispatch object. If so, it's probably the JScript array. Therefore, we can try getting the length property or get all elements by calls to pop method. You may also get the value at the given index, but it cannot be done automatically in Delphi. All you need to do is to extract an IDispatch interface, invoke GetIDsOfNames for the given index (which as you know, need not be an integer) and then Invoke retrieved DISPID to get the actual value.

That is all about arrays in JScript and COM object, I wanted to say for now. I hope somebody will find it usable. Maybe, I will also post in the future sample in C++ and ATL, stay tuned ;).

2 comments:

Jérôme said...

Good articles and 1000 thanks! it's exactly what i was searching for...

Anonymous said...

An amazing post indeed. However, when trying to use the piece of JS code to convert JS Array to VB Array, it yield an error that objJSArray was not defined.
Maybe it was meant to be jsArray instead?

There were visits to this blog since 20.08.2007.