> I need to implement the length property, indexer [] and
> support the (for xx in xx) construct.
>
> I've seen the IExpando interface and think that may be
> the way forward, but there's very little information
> around on how you go about implementing it.
AFAIK the IExpando interface has nothing to do with the
scripting collection framework while it is used to implement
objects that can have members added dynamically, like the
IDispatchEx interface in the ActiveX world.
> Am I going about this the right way or is there a simpler
> approach? I already have a quite a lot of script which
[quoted text clipped - 3 lines]
> particularly as it is still used in situations with the
> Array class.
I think you need to implement something like
[propget] IUnknown* Item([in] VARIANT Key);
[propget] long Count();
and not a specific interface (at least, this is how the
collections in the scripting runtime seem to be
implemented).
The signatures are in MIDL-like language, so you need to
implement read-only properties in C# and long is the same as
a C# int. Also, get_Item() is reserved in C# and you need to
implement an indexer to produce such a getter name instead
of a normal property like in the get_Count() case. I don't
know how to effectively marshal the VARIANT, maybe you can
try to define the property getter so that it takes an int as
argument (depending on what you want to support maybe you
need to overload the indexer to accept also a string).

Signature
// Alessandro Angeli
// MVP :: Digital Media
// a dot angeli at psynet dot net
Mark Fishpool - 11 Jan 2005 16:11 GMT
> I think you need to implement something like
>
[quoted text clipped - 4 lines]
> collections in the scripting runtime seem to be
> implemented).
Thanks for responding quickly :-)
I had tried something similar to this, but I've tried it again anyway. What
I haven't been able to work out though is how to get the IUnknown* return
type on the Item. I've tried it with object and string but it doesn't seem to
work. Here's my class:
using System;
using System.Collections;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Expando;
namespace TestCOMEnum
{
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IMyCollection
{
object this[int val] { get; }
int Count { get; }
int length { get; }
}
[ClassInterface(ClassInterfaceType.None)]
public class tcoll : IMyCollection
{
private Hashtable mkData = new Hashtable();
public tcoll()
{
mkData.Add(0,"42");
mkData.Add(1,"99");
mkData.Add(2,"007");
}
public int Count
{
get { return mkData.Count; }
}
public int length
{
get { return mkData.Count; }
}
public object this[int val]
{
get { return mkData[val]; }
}
}
}
And here's the HTML page/script I'm trying to run against it:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html><head>
<script language="jscript">
<!--
function test()
{
var k = new ActiveXObject("TestCOMEnum.tcoll");
alert('length = '+k.length+', item 1 = '+k.item(1));
alert('item[2] = '+k[2]);
for (var ii in k)
{
alert(ii);
}
}
-->
</script></head>
<body>
<input type="button" value="test" onclick="test();"/>
</body></html>
When I run the page the first alert gives the expected response, but the
second doesn't and the loop doesn't execute at all.
Mark
Mark Fishpool - 12 Jan 2005 11:29 GMT
> > I need to implement the length property, indexer [] and
> > support the (for xx in xx) construct.
[quoted text clipped - 7 lines]
> objects that can have members added dynamically, like the
> IDispatchEx interface in the ActiveX world.
I've been doing some more investigation using Visual C++ 6 as I can see
exactly what the script is trying to do. It is asking for the IDispatchEx
interface, and then calling methods on that to get at specific array items.
I've found some C++ code which implements this and have got a rudimentary
array working.
So my original suggestion was not too far off. I believe that implementing
IExpando gives you IDispatchEx support, but implementing that looks quite
involved.
Mark
Alessandro Angeli [MVP::DigitalMedia] - 12 Jan 2005 13:51 GMT
> I've been doing some more investigation using Visual C++
> 6 as I can see exactly what the script is trying to do.
[quoted text clipped - 6 lines]
> that implementing IExpando gives you IDispatchEx support,
> but implementing that looks quite involved.
Yes, the script runtime checks for IDispatchEx, but it falls
back to IDispatch when it's not supported by the object,
since it doesn't really need IDispatchEx but for expando
properties. In fact, the collections in the script runtime
extend IDispatch and not IDispatchEx. That's was I think you
do not need to implement IExpando at all. Besides,
implementing it would not provide any collection-specific
method or property, but only an extended dispatch interface,
just like the one provided by the CCW.
Unless you find out a solution yourself, I'll try to get
something working next week (I'm sorry I can't do anything
now, but I don't have the time before next Wednesday).

Signature
// Alessandro Angeli
// MVP :: Digital Media
// a dot angeli at psynet dot net
Mark Fishpool - 13 Jan 2005 22:31 GMT
I've come up with a partial solution which _almost_ works how I want it to.
I've found that it's possible to create a JScript array like this:
object[] kData = { "42", "99", "007" };
ArrayObject oA = Microsoft.JScript.GlobalObject.Array.ConstructArray(kData);
This also works for other types. There are two problems I can see with this.
Firstly it's probably unsupported, so I've no idea if it will work in future.
Secondly, when you use the for...in construct in script it enumerates
everything, properties and methods include, like this (read as: field =
[type] 'value'):
length = [number] '3'
constructor = [unknown]
0 = [string] '42'
1 = [string] '99'
2 = [string] '007'
concat = [object] 'function concat() { [native code] }'
join = [object] 'function join() { [native code] }'
pop = [object] 'function pop() { [native code] }'
push = [object] 'function push() { [native code] }'
reverse = [object] 'function reverse() { [native code] }'
shift = [object] 'function shift() { [native code] }'
slice = [object] 'function slice() { [native code] }'
sort = [object] 'function sort() { [native code] }'
splice = [object] 'function splice() { [native code] }'
toLocaleString = [object] 'function toLocaleString() { [native code] }'
toString = [object] 'function toString() { [native code] }'
unshift = [object] 'function unshift() { [native code] }'
hasOwnProperty = [object] 'function hasOwnProperty() { [native code] }'
isPrototypeOf = [object] 'function isPrototypeOf() { [native code] }'
propertyIsEnumerable = [object] 'function propertyIsEnumerable() { [native
code] }'
valueOf = [object] 'function valueOf() { [native code] }'
> > I've been doing some more investigation using Visual C++
> > 6 as I can see exactly what the script is trying to do.
[quoted text clipped - 20 lines]
> something working next week (I'm sorry I can't do anything
> now, but I don't have the time before next Wednesday).
Alessandro Angeli [MVP::DigitalMedia] - 22 Jan 2005 19:44 GMT
> I've come up with a partial solution which _almost_ works
> how I want it to. I've found that it's possible to create
> a JScript array like this:
>
> object[] kData = { "42", "99", "007" };
> ArrayObject oA =
Microsoft.JScript.GlobalObject.Array.ConstructArray(kData);
> This also works for other types. There are two problems I
> can see with this. Firstly it's probably unsupported, so
> I've no idea if it will work in future. Secondly, when
> you use the for...in construct in script it enumerates
> everything, properties and methods include, like this
That's the expected behavior of the for...in statement in
JScript. Quoting from the documentation:
<<<
Executes one or more statements for each property of an
object [...]
Use the Enumerator object to iterate members of a
collection.
I've come up with this solution:
------------- test.cs -------------
/// csc /target:library test.cs
/// regasm /verbose /nologo /codebase test.dll
[System.Runtime.InteropServices.ProgId("Axl.EnumTest")]
public class EnumTest
{
private string[] list = { "a", "b", "c", };
private class ThisEnum : System.Collections.IEnumerator
{
private string[] list;
private int index = -1;
public ThisEnum(string[] list) { this.list = list; }
public object Current { get { return list[index]; } }
public bool MoveNext() { return ++index < list.Length; }
public void Reset() { index = -1; }
}
public System.Collections.IEnumerator GetEnumerator()
{
return new ThisEnum(list);
}
}
---------------------------------------
---------- test.js ------------------
/// cscript test.js
var list = new ActiveXObject("Axl.EnumTest");
for(var e = new Enumerator(list); !e.atEnd(); e.moveNext())
{
WScript.Echo(e.item());
}
--------------------------------------

Signature
// Alessandro Angeli
// MVP :: Digital Media
// a dot angeli at psynet dot net
Mark Fishpool - 23 Jan 2005 16:05 GMT
Thanks for that, but I was hoping to have something which I can implement
both in script and in C# - I have situations where both are required. At the
moment the JScript array creation seems to give the best match between the
two environments.
Mark
> > I've come up with a partial solution which _almost_ works
> > how I want it to. I've found that it's possible to create
[quoted text clipped - 57 lines]
> }
> --------------------------------------
Alessandro Angeli [MVP::DigitalMedia] - 23 Jan 2005 17:13 GMT
> Thanks for that, but I was hoping to have something which
> I can implement both in script and in C# - I have
> situations where both are required. At the moment the
> JScript array creation seems to give the best match
> between the two environments.
You want a class which implements a collection that can be
enumerated using the foreach statement in C# and the
for(Enumerator) statement in JScript (or the for each
statement in VBS), did I get it right?
The sample class already implements the GetEnumerator()
method to work in JScript [for(new Enumerator())] and
VBScript [For Each...In...Next]. You can just tell the .NET
runtime it implements such a method by making it explicitly
inherit System.Collections.IEnumerable and it will work
with C#'s foreach as well.

Signature
// Alessandro Angeli
// MVP :: Digital Media
// a dot angeli at psynet dot net
Mark Fishpool - 24 Jan 2005 07:53 GMT
> > Thanks for that, but I was hoping to have something which
> > I can implement both in script and in C# - I have
[quoted text clipped - 6 lines]
> for(Enumerator) statement in JScript (or the for each
> statement in VBS), did I get it right?
Almost - I want something where I can implement the same collection in
JScript and C# and call both implementations from JScript in the same way. I
was also trying to keep it as close as possible to my existing implementation
as possible (there's quite a bit of script already working this way).
The reason for this is that I have a lot of script running in the browser
which in an online environment uses a JScript object model. In the offline
environment I already have a C# object model and I want the C# code to
directly replace the JScript.
Thanks for your time and patience in coming up with these! It has certainly
been interesting getting to understand more how the two side communicate with
one another.
Mark