Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Object doesn't support this property or method" with VBScriptEngine #386

Closed
EtienneLaneville opened this issue Jun 14, 2022 · 8 comments
Closed
Assignees
Labels

Comments

@EtienneLaneville
Copy link

EtienneLaneville commented Jun 14, 2022

I've been troubleshooting a Type Mismatch exception when trying to use object properties that are enums with VBScript. In trying to reproduce this, I started with the following enum and class (from another ticket):

public enum Kingdom { Plant, Animal };
public class Being
{
    public String Name { get; set; } = "Being";
    public Kingdom Kingdom { get; set; }
}

Then, I created a VBScriptEngine instance to check if I can access the Being.Kingdom property:

using (var host = new Microsoft.ClearScript.Windows.VBScriptEngine())
{

    host.HostWindow = new HostWindow();

    Being cat = new Being();
    cat.Name = "Felix";
    cat.Kingdom = Kingdom.Animal;

    host.AddHostType(typeof(Kingdom));
    host.AddHostObject("cat", cat);

    host.Execute("MsgBox \"Test\"");
    host.Execute("MsgBox cat.Name");
    host.Execute("MsgBox cat.Kingdom");
    host.Execute("MsgBox cat.Kingdom = Kingdom.Animal");

}

The first Execute (MsgBox "Test" but the next one (MsgBox cat.Name) throws an exception:
Object doesn't support this property or method: cat.Kingdom

For reference, this is the HostWindow class used:

class HostWindow : Microsoft.ClearScript.Windows.IHostWindow
{
    public IntPtr OwnerHandle
    {
        get { return IntPtr.Zero; }
    }

    public void EnableModeless(bool enable)
    {
        return;
    }
}

Originally posted by @EtienneLaneville in #156 (comment)

@ClearScriptLib
Copy link
Collaborator

Hi @EtienneLaneville,

We tested your sample code and observed the following:

  • MsgBox "Test" works as expected, showing "Test".

  • MsgBox cat.Name works as expected, showing "Felix".

  • MsgBox cat.Kingdom fails with a "Type mismatch" error. That's because ClearScript exposes .NET enums as objects, and VBScript can't convert objects to strings. MsgBox cat.Kingdom.ToString() works as expected, showing "Animal".

  • MsgBox cat.Kingdom = Kingdom.Animal fails with a "Type mismatch" error. That's because VBScript's equality operator doesn't work with objects. MsgBox cat.Kingdom is Kingdom.Animal works as expected, showing "True".

All of the observed behavior seems correct. Would you mind retesting? Are you still seeing "Object doesn't support this property or method" errors? Do the modifications above work for you?

Thanks!

@EtienneLaneville
Copy link
Author

In Visual Studio 2022, I created two projects, one in C# and one in VB.NET (thinking there could be a difference) with the following:

  • Packages Microsoft.ClearScript.Core (7.3.0) and Microsoft.ClearScript.Windows (7.3.0)
  • Windows Forms project
  • .NET 6

Both projects give me the same exception (Object doesn't support this property or method: cat.Kingdom). Here's the entire code I have been testing with:

C# Program.cs

namespace ClearScriptTesting
{
    internal static class Program
    {

        public enum Kingdom { Plant, Animal };
        public class Being
        {
            public String Name { get; set; } = "Being";
            public Kingdom Kingdom { get; set; }
        }

        class HostWindow : Microsoft.ClearScript.Windows.IHostWindow
        {
            public IntPtr OwnerHandle
            {
                get { return IntPtr.Zero; }
            }

            public void EnableModeless(bool enable)
            {
                return;
            }
        }

        /// <summary>
        ///  The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {

            // To customize application configuration such as set high DPI settings or default font,
            // see https://aka.ms/applicationconfiguration.
            ApplicationConfiguration.Initialize();


            using (var host = new Microsoft.ClearScript.Windows.VBScriptEngine())
            {

                host.HostWindow = new HostWindow();

                Being cat = new Being();
                cat.Name = "Felix";
                cat.Kingdom = Kingdom.Animal;

                host.AddHostType(typeof(Being));
                host.AddHostType(typeof(Kingdom));
                host.AddHostObject("cat", cat);

                host.Execute("MsgBox \"Test\"");
                host.Execute("MsgBox cat is Nothing");
                host.Execute("MsgBox cat.Name");
                host.Execute("MsgBox cat.Kingdom");
                host.Execute("MsgBox cat.Kingdom = Kingdom.Animal");

            }

            Application.Run(new Form1());
        }

    }
}

VB.NET ApplicationEvents.vb:

Imports System.Runtime.InteropServices
Imports Microsoft.ClearScript.Windows
Imports Microsoft.VisualBasic.ApplicationServices

Namespace My

    Partial Friend Class MyApplication

        Public Enum Kingdom
            Plant
            Animal
        End Enum

        Public Class Being
            Public Property Name As String = "Being"
            Public Property Kingdom As Kingdom
        End Class

        Public Class HostWindow
            Implements Microsoft.ClearScript.Windows.IHostWindow

            Public ReadOnly Property OwnerHandle As IntPtr Implements IHostWindow.OwnerHandle
                Get
                    Return IntPtr.Zero
                End Get
            End Property

            Public Sub EnableModeless(enable As Boolean) Implements IHostWindow.EnableModeless
                Return
            End Sub

        End Class

        Private Sub MyApplication_Startup(sender As Object, e As StartupEventArgs) Handles Me.Startup

            Using host As New Microsoft.ClearScript.Windows.VBScriptEngine

                host.HostWindow = New HostWindow()

                Dim cat As Being = New Being()
                cat.Name = "Felix"
                cat.Kingdom = Kingdom.Animal

                host.AddHostType(GetType(Being))
                host.AddHostType(GetType(Kingdom))
                host.AddHostObject("cat", cat)

                host.Execute("MsgBox ""Test""")
                host.Execute("MsgBox cat is Nothing")
                host.Execute("MsgBox cat.Name = ""Jimbo""")
                host.Execute("MsgBox cat.Name")
                host.Execute("MsgBox cat.Kingdom")
                host.Execute("MsgBox cat.Kingdom = Kingdom.Animal")

            End Using

        End Sub

    End Class

End Namespace

Ultimately I have been trying to work around the difference in Enum properties compared to MSScriptControl. With MSScriptControl, enums seem to be handled as their Integer value and that's where my scripts have been causing Type Mismatch errors with ClearScript. Your answer has been helpful in understanding how Enums are handled so I will try to adjust my scripts but it sounds like having scripts compatible with both ClearScript and MSScriptControl is not going to be possible. My goal is to move away from MSScriptControl so backwards-compatibility is not a problem.

I am going to close this ticket because you have answered my question but I wanted to provide the complete code that caused the exception in case someone else runs into the same issue.

@ClearScriptLib
Copy link
Collaborator

Hi @EtienneLaneville,

With MSScriptControl, enums seem to be handled as their Integer value and that's where my scripts have been causing Type Mismatch errors with ClearScript.

That's correct. ClearScript doesn't convert enums to integers, because such conversion is lossy. One potential problem is that it could break method invocation. For example, consider the following .NET method:

public void Foo(Kingdom arg) { ... }

If ClearScript converted enums to integers, a call such as Foo(Kingdom.Animal) would fail, because there's no implicit conversion from integer back to enum. Worse, if the method were overloaded, such a call could invoke the wrong overload:

public void Foo(Kingdom arg) { ... }
public void Foo(SomeOtherEnum arg) { ... }
public void Foo(int arg) { ... }

it sounds like having scripts compatible with both ClearScript and MSScriptControl is not going to be possible

Well, not exactly. You have control over what you expose to the script engine. If strongly typed enums are a problem in your scenario, you could expose a facade that uses integers instead.

Please feel free to reopen this issue if you have additional questions or thoughts about this topic.

Good luck!

@EtienneLaneville
Copy link
Author

EtienneLaneville commented Jun 14, 2022

I ran into the "Object doesn't support this property or method: cat.Name" issue again as I was upgrading my scripts. In a separate .NET application I have a function that returns an object or Nothing. I am able to deal with the Nothing value using what was explained in issue #106. When I detect that the function has returned Nothing, I want to use another function to create the object. When I try accessing properties on the object the second function returns, I get the same problem where it doesn't recognize the object's properties.

Here is some code that should replicate this problem:

public enum Kingdom { Plant, Animal };
public class Being
{
    public String Name { get; set; } = "Being";
    public Kingdom Kingdom { get; set; }

    public Being(string name) 
    { 
        this.Name = name;
    }

}
public class Creator
{
    public Being CreateBeing(string name)
    {
        return new Being(name);
    }
}

I've added a Creator class from which I want to create new objects and use them on the script side:

using (var host = new Microsoft.ClearScript.Windows.VBScriptEngine())
{

    host.HostWindow = new HostWindow();

    Creator creator = new Creator();
    Being cat = new Being("Felix");
    cat.Kingdom = Kingdom.Animal;

    host.AddHostType(typeof(Creator));
    host.AddHostType(typeof(Being));
    host.AddHostType(typeof(Kingdom));

    host.AddHostObject("creator", creator);
    host.AddHostObject("cat", cat);

    host.Execute("MsgBox \"Test\"");
    host.Execute("MsgBox cat is Nothing");
    host.Execute("MsgBox creator.CreateBeing(\"Test\").Name");
    host.Execute("MsgBox cat.Name");
    host.Execute("MsgBox cat.Kingdom");
    host.Execute("MsgBox cat.Kingdom = Kingdom.Animal");

}

With this code I get essentially the same exception:
image

@ClearScriptLib
Copy link
Collaborator

ClearScriptLib commented Jun 14, 2022

Hello @EtienneLaneville,

With this code I get essentially the same exception

That's because "creator" actually refers to the Creator host type, which has no static method CreateBeing.

Here's what's going on. Your setup code includes the following:

host.AddHostType(typeof(Creator));
// other code
host.AddHostObject("creator", creator);

These lines create two host items: Creator referring to the type, and creator referring to an instance of that type. However, VBScript is case-insensitive, so the name "creator" ends up binding to the former rather than the latter, probably because it was created first.

The script code above doesn't actually use Creator, so you can delete the line that adds it to eliminate the exception.

Cheers!

@EtienneLaneville
Copy link
Author

EtienneLaneville commented Jun 14, 2022

Thanks for your response. I've removed the line that add the Creator type to the engine but I am not getting any further, I get the same Object doesn't support this property or method: creator.CreateBeing exception.

Here are the steps that I take, hopefully something here will stand out:

  1. Start Visual Studio 2022.
  2. Select Create New Project
  3. Select Windows Forms App
  4. Select .NET Framework 4.7.2
  5. In Solution Explorer, right-click References, then select "Manage NuGet Packages..."
  6. Browse for ClearScript
  7. Install Microsoft.ClearScript (7.3.0)
  8. In Program.cs, add the following in the Program class:
public class Being
{
    public String Name { get; set; } = "Being";

    public Being(string name)
    {
        this.Name = name;
    }

}

class HostWindow : Microsoft.ClearScript.Windows.IHostWindow
{
    public IntPtr OwnerHandle
    {
        get { return IntPtr.Zero; }
    }

    public void EnableModeless(bool enable)
    {
        return;
    }
}
  1. Update Main:
static void Main()
{
    using (var host = new Microsoft.ClearScript.Windows.VBScriptEngine())
    {

        host.HostWindow = new HostWindow();

        Being cat = new Being("Felix");
        host.AddHostType(typeof(Being));

        host.AddHostObject("cat", cat);

        host.Execute("MsgBox \"Test\"");
        host.Execute("MsgBox cat.Name");

    }

}
  1. When running the project, you should see a Test message box which confirms the VBScript engine is working and then an exception on the host.Execute("MsgBox cat.Name"); line:

image

To eliminate any system specific differences, I have tried these steps using Hyper-V with the Windows 11 dev environment image and I see the same problem there.

@EtienneLaneville
Copy link
Author

Figured out the problem: The code doesn't work when it is in the internal static class Program class. Using the following class, everything works as expected:

public class EngineTest
{

    private Microsoft.ClearScript.Windows.VBScriptEngine _host = new Microsoft.ClearScript.Windows.VBScriptEngine();

    public EngineTest()
    { 
        
        _host.HostWindow = new HostWindow();

    }

    public void Test()
    {

        Being cat = new Being("Felix");
        _host.AddHostType(typeof(Being));

        _host.AddHostObject("cat", cat);

        _host.Execute("MsgBox \"Test\"");
        _host.Execute("MsgBox cat.Name");
    }
    public class Being
    {
        public String Name { get; set; } = "Being";

        public Being(string name)
        {
            this.Name = name;
        }

    }

    class HostWindow : Microsoft.ClearScript.Windows.IHostWindow
    {
        public IntPtr OwnerHandle
        {
            get { return IntPtr.Zero; }
        }

        public void EnableModeless(bool enable)
        {
            return;
        }
    }
}

@ClearScriptLib
Copy link
Collaborator

The code doesn't work when it is in the internal static class Program class.

Ah, yes. Member visibility enforcement is often the hidden cause of such issues. Thanks for posting the solution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants