Problem with nullable property of object

Mar 12, 2014 at 3:37 PM
Hi,

There is problem with is Nothing keyword in VBScript when object property is null. In my case
MyClass.Class2 property is null
public class MyClass
{
    public MyClass2 Class2 { get; set; } 
}

public class MyClass2
{
     
}

using(var scripter = new VBScriptEngine(WindowsScriptEngineFlags.EnableDebugging))
{
    scripter.Script.Class1 = new MyClass();
    scripter.Execute(@"
            function Test
            if Class1.Class2 is Nothing then 
                Test = true
            else 
                Test = false
            end if
            end function ");
    Console.WriteLine(scripter.Script.Test());  
}
Regards,

Igor
Coordinator
Mar 12, 2014 at 4:56 PM
Hi Igor,

You have to use the IsNull function in this case:
function Test
if IsNull(Class1.Class2) then
    Test = true
else 
    Test = false
end if
end function
Thanks!
Mar 12, 2014 at 5:46 PM
That break compatibility with legacy application. We can't change client scripts.
Coordinator
Mar 12, 2014 at 7:35 PM
Edited Mar 13, 2014 at 5:05 AM
Understood. Unfortunately there seems to be no way to marshal nothing into the script engine. That is, there's no value we can return from the host that VBScript will interpret as nothing. We can marshal in null and empty, but not nothing. And marshaling nothing out of VBScript doesn't work; the result on the host is null.
Mar 12, 2014 at 8:18 PM
Edited Mar 12, 2014 at 8:27 PM
Try use DispatchWrapper.
Coordinator
Mar 12, 2014 at 9:02 PM
Edited Mar 13, 2014 at 5:05 AM
You're absolutely right! A value of new DispatchWrapper(null) is picked up as nothing on the VBScript side. Thank you!

We'll be happy to add null <-> nothing marshaling to ClearScript, but it'll have to be optional to avoid breaking existing code. A new engine flag seems like a sensible approach.

Thanks again!
Mar 12, 2014 at 9:54 PM
Thanks. Is there nightly builds of the ClearScript?
Coordinator
Mar 13, 2014 at 5:12 AM
Hi Igor,

Is there nightly builds of the ClearScript?

No, but we just posted a source code update that includes the new marshaling options. If you get a chance, please give them a try and let us know if they work for you.

Thanks again!
Mar 13, 2014 at 7:10 PM
Edited Mar 13, 2014 at 7:11 PM
Hi,

It's work. Thanks a lot.
If you have some progress with cloning of script engine, please update the source code. I use with source code of script engine and have modified version of the ClearScript.
Jun 4, 2014 at 12:36 PM
Edited Jun 4, 2014 at 12:37 PM
Hi,

Small bug with the new MarshalNullAsDispatch option.
String property in case of null value should always return the DBNull value instead of DispatchWrapper(null).
Following code does not work :
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.ClearScript.Windows;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var scriptEngine = new VBScriptEngine(WindowsScriptEngineFlags.MarshalNullAsDispatch))
            {
                var script = @"
                    function getName
                    getName = person.Name
                    end function
                ";
                scriptEngine.Execute(script);
                scriptEngine.Script.person = new Person();
                Console.WriteLine("Person name is '{0}'", scriptEngine.Script.getName());
                Console.ReadKey();
            }
        }
    }

    public class Person
    {
        public string Name { get; set; } 
    }
}
Without MarshalNullAsDispatch option the getName() function return Undefined object. Is it behavior by design?
Is it not should return to .NET the null value?

Thanks
Coordinator
Jun 4, 2014 at 3:39 PM
Hi ifle,

It looks like the code above fails because this line:
getName = person.Name
can't handle the case where person.Name returns nothing, and that's because nothing is a special object reference that requires a set statement rather than simple assignment. Is that your understanding as well?

Without MarshalNullAsDispatch option the getName() function return Undefined object. Is it behavior by design?

Hmm, we're not seeing that behavior. Without MarshalNullAsDispatch we get:
Person name is ''
Have you made any modifications to ClearScript that could have changed the behavior?

Thanks!
Jun 4, 2014 at 4:55 PM
Edited Jun 4, 2014 at 4:58 PM
can't handle the case where person.Name returns nothing, and that's because nothing is a special object reference that requires a set statement rather than simple assignment. Is that your understanding as well?
String value cannot be nothing, nothing keyword can be used for class objects only.
ClearScript changes the .NET default marshaling of string value from null to nothing.
Person name is ''
Have you made any modifications to ClearScript that could have changed the behavior?
Sorry, that my mistake, ignore.
Coordinator
Jun 4, 2014 at 7:26 PM
OK, so:

  1. The problem occurs only when MarshalNullAsDispatch is in effect.
  2. Host members (fields, properties, methods) that return strings should override MarshalNullAsDispatch and marshal null return values as DBNull.
If all of that is correct, what other return types should override MarshalNullAsDispatch? One thing that comes to mind is nullable numerics such as int?. Anything else?
Jun 4, 2014 at 8:15 PM
Edited Jun 4, 2014 at 8:23 PM
If all of that is correct, what other return types should override MarshalNullAsDispatch? One thing that comes to mind is nullable numerics such as int?. Anything else?
Only host members with strongly typed objects that derive from the Object class with specific reference type can return DispatchWrapper(null) in case of null.
Host members like string, object, nullable<T> should return DBNull.
I'm not sure about DBNull, because .NET marshal a null value as VT_EMPTY and not as VT_NULL like DBNull

http://msdn.microsoft.com/en-us/library/2x07fbw8%28v=vs.110%29.aspx
Coordinator
Jun 5, 2014 at 3:55 AM
Edited Jun 5, 2014 at 4:03 AM
Hi ifle,

Only host members with strongly typed objects that derive from the Object class with specific reference type can return DispatchWrapper(null) in case of null.

Well, not quite, because that description applies to .NET strings :)

Host members like string, object, nullable<T> should return DBNull.

That doesn't seem completely correct either. A value type T that isn't numeric or boolean turns into an object reference on the script side, so a Nullable<T> without a value should probably be marshalled as nothing. And object is completely ambiguous; ideal null marshaling in that case depends entirely on what the script expects.

I'm not sure about DBNull, because .NET marshal a null value as VT_EMPTY and not as VT_NULL like DBNull

Here's what we've found in our testing:
  • DBNull.Value appears as null in both JScript and VBScript.
  • null appears as undefined in JScript and empty in VBScript.
  • new DispatchWrapper(null) appears as undefined in JScript and nothing in VBScript.
Note that this describes standard .NET marshaling. ClearScript adds another layer to ensure that, for example, host-side null appears as null on the script side. In any case, we agree that (at least) strings, nullable booleans, and nullable numerics should override MarshalNullAsDispatch, and we'll make that fix ASAP.

Thanks again for your input!
Jun 5, 2014 at 11:36 AM
Edited Jun 5, 2014 at 1:15 PM
Null in .NET and Null in VbScript are not similar things.
Suppose you have code that concatenate strings and one of them is null.
In case of VBScript the result string is null (because ClearScript instead of VT_EMPTY return VT_NULL) and in .NET is not.
Marshall behaviour of ClearScript is different to default marshaller of .NET and other languages like Delphi. For legacy application that is critical.
Maybe MarshalNullAsDispatch flag need to replace to the new one like MarshalNullAsNetDefault and marshal values as .NET runtime or expose marshal API that allows override default ClearScript marshaling.
I don't know maybe only I have these troubles because I have big legacy application, anyway It's not very critical for me like memory leaks that you fixed.
I use modificated version of ClearScript and have my own marshaling, because our objects exposed as COM both to VBScript and Delphi.
using System;
using Microsoft.ClearScript.Windows;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var person = new Person { FirstName = "Igor"};
            Console.WriteLine("C# full name is '{0}'", person.FirstName + "," + person.LastName);
            using (var scriptEngine = new VBScriptEngine())
            {
                var script = @"
                    function getFullName
                        getFullName = person.FirstName + "","" + person.LastName
                    end function
                ";
                scriptEngine.Execute(script);
                scriptEngine.Script.person = new Person { FirstName = "Igor"};
                Console.WriteLine("VBScript full name is '{0}'", scriptEngine.Script.getFullName());
                Console.ReadKey();
            }
        }
    }

    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }

    }
}
Sorry for my bad english
Coordinator
Jun 5, 2014 at 2:51 PM
Hi ifle,

Our view is that these issues are simply the result of bringing together two very different object models.

It isn't just a matter of null having different meanings. Strings, for example, are full-blown objects in .NET, but in VBScript they're more like primitive values. We could have kept them separate, with no mapping between them, but that would have made ClearScript very painful to use in most scenarios.

So we tried to choose reasonable mappings for the most common .NET and VBScript types. We know these mappings aren't perfect, and we'll be happy to add flexibility to accommodate legacy scripts as much as possible.

Maybe MarshalNullAsDispatch flag need to replace to the new one like MarshalNullAsNetDefault and marshal values as .NET runtime

If we understand correctly, standard .NET marshaling is wrong for your original scenario at the top of this thread, because it always turns .NET null into VBScript empty, whereas your script code expects nothing.

Our current plan is to go ahead with the change described above. Please let us know if you have any remaining concerns.

Cheers!
Jun 5, 2014 at 4:25 PM
Edited Jun 5, 2014 at 4:25 PM
I understand that, It's not simply and I really really appreciate your great work. We convert very big module in legacy application to .NET and will bring together 3 different worlds (.NET, VBScript, Delphi). I don't know which troubles I will meet tomorrow.
Small example .NET interop does not support interface inheritance, but Delphi do :)
For now I haven't any remaining concerns.
I'd like ask if you have any plans expose .NET COM objects as is without wrappers?
Jun 5, 2014 at 8:10 PM
Edited Jun 5, 2014 at 8:16 PM
If we understand correctly, standard .NET marshaling is wrong for your original scenario at the top of this thread, because it always turns .NET null into VBScript empty, whereas your script code expects nothing.
No, standard .NET marshaling works as expected, .NET Com object (not string) in case of null marshaled as Nothing, because property Class2 have type of MyClass2 and not string, object and etc.
That problem of ClearScript marshaling
public class MyClass
{
    public MyClass2 Class2 { get; set; } 
}

public class MyClass2
{
     
}
Coordinator
Jun 5, 2014 at 8:55 PM
Right, sorry about the confusion. In any case, our planned change should provide the behavior you need when MarshalNullAsDispatch is enabled. Thanks again!
Coordinator
Jun 11, 2014 at 3:29 AM
The update is now available. Cheers!
Jun 13, 2014 at 4:16 PM
Thanks.