Trouble assigning array values in Variant to matching array

Let’s assume SubClass is a sub class of MyClass

Now consider this code:

dim src() as MyClass src.Append new SubClass dim v as Variant = src dim dest() as SubClass = v

This code used to work a few years ago (e.g. in REAL Studio 2012r2.1) but doesn’t any more with 2015r4.1 - it throws a TypeMismatchException when assigning v to dest.

And from a runtime point of view, there should be no exception, because the dest array gets exactly the right types assigned.

I have to be able to store various sub classes in a Variant and then assign them back to a matching sub class array later. The Variant is a must because I’m using Introspection.PropertyInfo.Value() to assign these arrays, and that function works with Variants. The other bottleneck is where I collect objects into an array before assigning it to the Variant, and at that point the specific sub class type can’t be known, as it’s a central point for any sub classes of their main super class. So I have to declare the array of the common super class, there’s no way around it as I can tell.

Is there any way to make this work? As this worked in older versions, I wonder if there’s a new solution now?

the problem is that while members may be of a sub type the declared type of the array is not appropriate for such a down cast

this is probably as case of “it used to work but never should have”

imagine if your code was

dim src() as MyClass
src.Append new SubClass
src.Append new MyClass
dim v as Variant = src
dim dest() as SubClass = v

obviously this last line would HAVE to raise an exception by checking each member as what you’re trying to do is not far from

dim src() as MyClass
src.Append new Subclass
  
dim dest() as Subclass
dest = src

yet the reverse is fine

dim src() as MyClass
dim dest() as Subclass
  
dest.Append new Subclass
  
src = dest

As Norman said, up-casting of arrays are fine but I don’t think down-casting ever did. Going through a Variant seems to be an unintended trick.

I was thinking you could upcast the Variant to Object() then downcast that but Variant won’t implicitly upcast. For arrays you have to pull them out of Variants with the exact type they were put in.

If you can retrieve the exact array type from the Variant then you can do this (otherwise maybe you can use Object arrays in place of Variants)

[code]dim src() as MyClass
src.Append new SubClass
dim v as Variant = src
//…
dim retrievedSrc() As MyClass = v
dim dest() as SubClass
downCast(retrievedSrc, dest)

Sub downCast(src() As Object, dest() As Object)
if src = nil or dest = nil then return
dim last As integer = src.Ubound
redim dest(last)
for i As integer = 0 to last
dest(i) = src(i)
next
End Sub[/code]

Note, this isn’t really downcasting, it’s copying two upcasted arrays.

Yeah, Will, that’s not going to work because the code assigning the values to an array thru the Variant type doesn’t know the actual destination’s type at compile time, but only at runtime.

I think the real issue here is that neither Introspection nor the Variant type gives full access to arrays. If Variant had a ArrayElementBaseType() as TypeInfo and an accessor to get/set each array element individually, I could make this work, but that’s not going to happen any time soon, if ever. Even though several people have pointed out that weakness with Arrays in Variants for years.

So, the only working solution is to remind users of my code to declare all their arrays with the base class type, never with the actual sub class types and then manually cast the types when they access them in the array, which is rather cumbersome.

I’ll try to check at start, when I see the array property the first time, to warn the user if he has declared it of the wrong type, at least, so he won’t notice it only when we tries to use it later on.

What I’m reading is that a user supplies an object array to your code, of a subclass of your super, and then this array is stored via introspection so it ends up in a Variant. Later to return the array to the user you were passing back the Variant directly from introspection, which was magically downcasting on the users side.

If this is right then you can still have users type arrays the way they want, but they’d have to pass the array into the getter instead of assigning a returned value.

[code]dim userArray() As MyType = TTsCode.GetArray //no way

dim userArray() As MyType //ok
TTsCode.GetArray(userArray)[/code]

Hi Will,
I appreciate you giving this more thought. So let me give you what I hope is a more complete picture.

First off, this is all for my new Scriptability framework, making it super easy to add Applescript handling to a Xojo app, even existing ones that were not designed with scriptability in mind.

My framework has to be able to map accesses to properties as Apple’s scripting engine (Cocoa Scripting framework) see them. So, it may be told (by the script vocabulary written for the app) that the “application” object has an element for “windows”, which means it’s an array of something that’s a sciptable object (and that scritable object’s super class is called ScriptableObject on my end).

Now, the Xojo app can declare any kind of ScriptableObject subclasses. One representing the app (ScriptableApp), and many more for other elements, e.g. for windows there could be a class ScriptableWindow, subclass of ScriptableObject.

The application instance (ScriptableApp) has a property declared as allWindows() as ScriptableWindow .

To make Introspection work inside my framework code, every ScriptableObject subclass must be registered at app launch with my framework, by calling “RegisterClass GetTypeOf (the classname)”. That allows me code to preflight every class, identifying the scriptable properties in it and register some ObjC methods with it that the scripting engine with call to access the classes and their methods and properties.

When such a request comes in, it may say: “set the allWindows array to the given array of object references”. The obj refs are ObjC objects, and I have mapped them to the Xojo objects using a Dictionary. So, when my framework gets a call like that, it can identify the related Xojo objects.

The trouble comes when I have to assign those objects to the array property allWindows. For that to work, I have to create an array of objects and then assign that array to the allWindows property using the Introspection.PropertyInfo.Value() method. And that’s where the trouble lies: As you can see, I have to create an array of known objects and assign that to a Variant. And I can’t know the specific types of the array at compile time, obviously. And as far as I can tell, your suggestions won’t help with that. Or do you see otherwise?

Or, in short: my code has a Dictionary of various SuperClass-derived items, from which it has to pick a subset of items, each being of type SubClass, and assign them to an array of SubClass. And at compile time my part of the code does not know the sub classes, so I cannot hard code this operation.

Of course, I could provide an accessor event in every sub class that the user has to implement, and which performs the type conversion. But that would defeat the entire purpose of the code I wrote, as the purpose is that the user is NOT required to write special code for such accessors.

It all comes down to the fact that the Variant type is missing methods to access single elements of an array stored in the Variant.

Actually, I wonder if the Plugin API provides such access. Does someone know?

Make the array an array typed to Variant:

[code]Dim v As Variant = Array(1, 2, 3)
Dim vv() As Variant = v // will not compile

Dim v As Variant = Array(“”, 1, 2, 3)
Dim vv() As Variant = v // will compile
// Now one can use introspection on each element of vv[/code]

Eli, I need the other way around. I need to assign an array of objects to a specific “array of subclass”. What you propose is not that way as far as I can think right now with 100 other things on my mind :wink:

In other words, this still won’t work:

dim src() as Variant
src.Append new SubClass
dim dest() as SubClass = src // - won’t work

Here’s another thought: Would it be possible, perhaps, to create the fitting array at runtime using Introspection? I guess not, but perhaps there’s a way? I mean, I can instantiate classes of any type using Introspection at runtime, so maybe the same is possible for arrays?

No, arrays are not objects.

I don’t know if it will help you, but Auto is more flexible with arrays than Variant. For example, this works:

  dim b() as SuperClass
  b.Append new SubClass
  
  dim a as auto = b
  dim o() as Object = a

You can also solve the problem by starting with an array property of the required class, then going through Auto to access the elements of each array as Object. You can then use Introspection to work around the issue.

Thanks for clarifying, there’s a lot more going on than I imagined.

OK, so these ObjC calls are coming in and your framework is automatically mapping objc ids to xojo instances and performing some action. I don’t know how the action is handled (a select case?) but the problem is that the “given array of object references” gets mapped to a property typed as ScriptableObject().

Does it have to be the actual array or does copying the elements work? For actual array assignment there’s no way, but with the downCast/copying method I think this should work.

dim objs() As ScriptableObject = getMapped("given array of object references")

downCast(objs, allWindows)

Maybe only try downCast if assignment doesn’t work

dim v As Variant = getMapped("given array of object references") try allWindows = v catch dim objs() As ScriptableObject = v downCast(objs, allWindows) end

I’m not clear on where this initial array of subclass comes from, it can’t be dynamically made …

oh wait. I think I see it now. A user of your framework would create MyScriptableApp (subclass of ScriptableApp), and say, add the property “widgets() As ScriptableWidget” which inherits ScriptableObject. But the problem is users can’t type the property as ScriptableWidget, it has to be the super type. Other than this property type the user of your framework doesn’t interact. I was thinking the user was calling setters and getters.

With this in mind I think downCast could work, it’s just resizing then copying elements into the prexisting array. Ugg, I’m still thinking about it like before. If they’ve typed the property as ScriptableWidget, then Introspection returns a Variant, and then the only way to get at that ‘thing’ in the Variant is assignment to exactly that type, ScriptableWidget(), which isn’t doable. Yeah, maybe you’re screwed.

Or maybe, could you mirror the arrays as the super type? The user types the property as ScriptableWidget but during registration an internal array of ScriptableObject is made and assigned from it. Mapping the objc ids goes to these internal arrays whose manipulations are reflected in the real property array.

[code] dim orig() As Class2
dim origMirror() As Class1 = orig

dim newElems() As Class1
newElems.Append( new Class2 )
newElems.Append( new Class2 )

//map objc id to origMirror
dim dest() As Class1 = origMirror

//copy over elements to origMirror(), orig() is modified too
dim last As integer = newElems.Ubound
redim dest(last)
for i As integer = 0 to last
dest(i) = newElems(i)
next[/code]

It depends how classes are registered though if this will work or not. I think every property will have to be individually registered, as discovering them via Introspection is the same old problem of Variant to subtype array. And it’d only work if you don’t need true assignment, just element copying.

you can workaround the problem with MBS Plugin methods:

  • GetVariantArrayMBS
  • GetVariantArrayUboundMBS
  • GetVariantArrayValueMBS

I guess the plugin API does provide access :stuck_out_tongue:

Will,

Let me re-explain this part even though I suspect you’ve already grasped this later on:

I may access the array only through Introspection. So, I have the object and the property’s Introspection.PropertyInfo. If the property is an array, then I have to use the Value() function to set it, and for that I have to declare a matching array at compile time, into which I collect the values and then assign the array to the value.

[quote=256613:@Christian Schmitz]you can workaround the problem with MBS Plugin methods:

  • GetVariantArrayMBS
  • GetVariantArrayUboundMBS
  • GetVariantArrayValueMBS[/quote]
    I don’t think this will help unless there is also a way to set single elements in a Variant array?

Plus, I guess that the problem is still at the PropertyInfo.Value() setter method - it will only accept an array of identical type. So, my earlier thought of it being only Variant lacking some accessors is wrong. It’s Introspection that needs to give me access to a property’s elements individually so that I can insert, remove and replace single items of it.

Introspection works only on objects. An array in Xojo is not an object.

I can add a function to set.

The key thing here is that Xojo performs type checking which the plugin doesn’t do as it returns a variant.

You can certainly access and manipulate an array property through Introspection.