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

ClearScript execution of VBS code does not support ByRef parameter passing #511

Closed
Rison-Hub opened this issue May 11, 2023 · 15 comments
Closed
Assignees
Labels

Comments

@Rison-Hub
Copy link

Rison-Hub commented May 11, 2023

VBS CODE:
Option Explicit
Sub Main
dim reftest
reftest=0
zp.testRef 5,reftest
zp.print reftest

End Sub
Main
VB.NET CODE:
Public Class VBClass
Public Sub Print(ByVal OutStr As Object, ByVal Optional iShow As Integer = 1)
Console.WriteLine("OutStr: " & OutStr & "!")
End Sub

Public Sub testRef(ByVal OutStr As Integer, Optional ByRef iShow As Integer= 0)
    iShow = iShow + OutStr
    Console.WriteLine("testRef: " & iShow & "!")
End Sub

End Class

C# Code:
private void button10_Click(object sender, EventArgs e)
{
var VBscript = new VBClass();
using (var engine = new VBScriptEngine())
{
engine.AddHostObject("zp", VBscript);
var scriptCode = File.ReadAllText("D:/Script/abc.vbs");
engine.Execute(scriptCode);

        }

    }
@ClearScriptLib
Copy link
Collaborator

Hello @Rison-Hub,

When a script invokes managed code, ClearScript uses the C# compiler to select the correct method – or construct a suitable generic method – based on the call signature. Unlike Visual Basic and VBScript, C# forces the caller to specify the passing mechanism for each argument.

Because script languages usually give you no way to do that, ClearScript provides its own solution. To pass something by reference to a managed method, you must use a host variable. Here's an example based on your code above:

using (var engine = new VBScriptEngine()) {
    engine.AddHostObject("zp", new VBClass());
    engine.AddHostObject("host", new HostFunctions());
    engine.Execute(@"
        Option Explicit
        Sub Main
            dim reftest
            reftest = host.newVar(clng(0))
            zp.testRef 5, reftest.ref
            zp.print reftest
        End Sub
        Main
    ");
}

Good luck!

@Rison-Hub
Copy link
Author

Executing the code snippet you provided will result in an exception: "The most suitable overload method for 'VBLibrary.VBClass.testRef(int, ref object)' has some invalid arguments."

@ClearScriptLib
Copy link
Collaborator

Executing the code snippet you provided will result in an exception: "The most suitable overload method for 'VBLibrary.VBClass.testRef(int, ref object)' has some invalid arguments."

Oops, sorry. We had modified testRef slightly. The following should work with your original definition:

using (var engine = new VBScriptEngine()) {
    engine.AddHostObject("zp", new VBClass());
    engine.AddHostObject("host", new HostFunctions());
    engine.AddHostType(typeof(object));
    engine.Execute(@"
        Option Explicit
        Sub Main
            dim reftest
            reftest = host.newVar(object, clng(0))
            zp.testRef 5, reftest.ref
            zp.print reftest
        End Sub
        Main
    ");
}

@Rison-Hub
Copy link
Author

Rison-Hub commented May 12, 2023

Thank you for your suggestion, but there is another question. My second parameter in the VB.NET method is an optional parameter. If I want to pass only one argument, how can I achieve a ByRef parameter that is not specified and request the method?
I made a small adjustment to the VB.NET method

Public Sub testRef(ByVal OutStr As Integer, Optional ByRef iShow As Integer = 0)
        iShow = iShow + OutStr
        Console.WriteLine("testRef: " & iShow & "!")
    End Sub

C# code

using (var engine = new VBScriptEngine()) {
    engine.AddHostObject("zp", new VBClass());
    engine.AddHostObject("host", new HostFunctions());
    engine.Execute(@"
        Option Explicit
        Sub Main
            dim reftest
            zp.testRef 5
        End Sub
        Main
    ");
}

Error message: Microsoft.ClearScript.ScriptEngineException: "The method 'testRef' does not have an overload that takes '1' parameter."

@ClearScriptLib
Copy link
Collaborator

Hi again,

Error message: Microsoft.ClearScript.ScriptEngineException: "The method 'testRef' does not have an overload that takes '1' parameter."

Yes, unfortunately, C# doesn't support optional by-reference parameters, and, as we discussed above, ClearScript uses C#'s invocation semantics.

One workaround is to add an overload without the optional parameter:

Public Sub testRef(ByVal OutStr As Integer)
    testRef(OutStr, 0)
End Sub
Public Sub testRef(ByVal OutStr As Integer, Optional ByRef iShow As Integer = 0)
    iShow = iShow + OutStr
    Console.WriteLine("testRef: " & iShow & "!")
End Sub

Cheers!

@Rison-Hub
Copy link
Author

Thank you very much for your guidance

@ClearScriptLib
Copy link
Collaborator

Please reopen this issue if you have additional questions about this topic. Thank you!

@Rison-Hub
Copy link
Author

Rison-Hub commented May 18, 2023

Please reopen this issue if you have additional questions about this topic. Thank you!

@ClearScriptLib I have encountered another issue. I want to pass a two-dimensional array and process it in VB.NET. How can I call and output the processed result values in VBS?

VBS code

Option Explicit
dim timeGap(1,1)
	 timeGap(0,0)=37      
	 timeGap(0,1)=1   
	 timeGap(1,0)=39
	 timeGap(1,1)=2

Sub Main
	dim ByReftest
       ByReftest = host.newVar(object,timeGap)
       zp.testArr 5, ByReftest.ref
      zp.print ByReftest(0,0)
End Sub
Main

VB.NET Code

Public Sub Print(ByVal strInfo As Object)
        Console.WriteLine("strInfo : " & strInfo & "!")
    End Sub

Public Sub testArr(ByVal Ipar As Integer, ByRef arr As Object)
       arr(0, 0)=arr(0, 0)+Ipar 
        Console.WriteLine("testRef: " & arr(0, 0) & "!")
    End Sub

C# Code

using (var engine = new VBScriptEngine())
            {
                engine.AddHostObject("host", new HostFunctions());
                engine.AddHostObject("zp", VBscript);
                engine.AddHostType(typeof(object));
                var scriptCode = File.ReadAllText("D:/Script/abc.vbs");
                engine.Execute(scriptCode);

            }

Executing zp.print ByReftest(0,0) throws an error: Microsoft.ClearScript.ScriptEngineException: "The object does not support the requested invocation operation."

@ClearScriptLib
Copy link
Collaborator

Hi @Rison-Hub,

Executing zp.print ByReftest(0,0) throws an error: Microsoft.ClearScript.ScriptEngineException: "The object does not support the requested invocation operation."

Hmm. ByRefTest is a host variable holding a .NET array (converted from a VBScript array). So, it's essentially a .NET array. Sadly, ClearScript doesn't support multidimensional indexing for .NET arrays via native VBScript syntax. It's a scenario that hasn't come up before, probably because most ClearScript users work with JavaScript, which has no native support for multidimensional arrays. We'll take a closer look at this for a future release. Thanks!

In the meantime, in this particular case, there's no need to use a host variable at all. Here's a working sample:

Public Sub Print(strInfo As Object)
    Console.WriteLine("strInfo : " & strInfo & "!")
End Sub
Public Sub testArr(Ipar As Integer, arr As Object)
    arr(0, 0) = arr(0, 0) + Ipar
    Console.WriteLine("testRef: " & arr(0, 0) & "!")
End Sub

C# code:

engine.AddHostObject("zp", new VBClass());
engine.Execute(@"
    Option Explicit
    dim timeGap(1,1)
        timeGap(0,0)=37      
        timeGap(0,1)=1   
        timeGap(1,0)=39
        timeGap(1,1)=2
    Sub Main
        zp.testArr 5, timeGap
        zp.print timeGap(0,0)
    End Sub
    Main
");

Please let us know if this solution works for you.

Thanks again!

@Rison-Hub
Copy link
Author

Hi @Rison-Hub,

Executing zp.print ByReftest(0,0) throws an error: Microsoft.ClearScript.ScriptEngineException: "The object does not support the requested invocation operation."

Hmm. ByRefTest is a host variable holding a .NET array (converted from a VBScript array). So, it's essentially a .NET array. Sadly, ClearScript doesn't support multidimensional indexing for .NET arrays via native VBScript syntax. It's a scenario that hasn't come up before, probably because most ClearScript users work with JavaScript, which has no native support for multidimensional arrays. We'll take a closer look at this for a future release. Thanks!

In the meantime, in this particular case, there's no need to use a host variable at all. Here's a working sample:

Public Sub Print(strInfo As Object)
    Console.WriteLine("strInfo : " & strInfo & "!")
End Sub
Public Sub testArr(Ipar As Integer, arr As Object)
    arr(0, 0) = arr(0, 0) + Ipar
    Console.WriteLine("testRef: " & arr(0, 0) & "!")
End Sub

C# code:

engine.AddHostObject("zp", new VBClass());
engine.Execute(@"
    Option Explicit
    dim timeGap(1,1)
        timeGap(0,0)=37      
        timeGap(0,1)=1   
        timeGap(1,0)=39
        timeGap(1,1)=2
    Sub Main
        zp.testArr 5, timeGap
        zp.print timeGap(0,0)
    End Sub
    Main
");

Please let us know if this solution works for you.

Thanks again!

Thank you again for your guidance.

@Rison-Hub
Copy link
Author

@ClearScriptLib I made a slight modification to the format and noticed another issue
VB Code

Public Sub Print(strInfo As Object)
    Console.WriteLine("strInfo : " & strInfo & "!")
End Sub
    Public Sub testByVal(ByVal OutStr As Integer, ByVal iShow As Object)
        iShow = iShow + 5
        Console.WriteLine("testByVal: " & iShow & "!")
    End Sub
Public Sub testArr(Ipar As Integer, arr As Object)
    arr(0, 0) = arr(0, 0) + Ipar
    Console.WriteLine("testRef: " & arr(0, 0) & "!")
End Sub

C# Code

engine.AddHostObject("host", new HostFunctions());
 engine.AddHostObject("zp", VBscript);
engine.AddHostType(typeof(object));
engine.Execute(@"
    Option Explicit
    dim ByValtest
	ByValtest=3
    Sub Main
        zp.testByVal 5, ByValtest
        zp.print ByValtest
    End Sub
    Main
");

output :
8
3
but I would like it output
8
8

@ClearScriptLib
Copy link
Collaborator

ClearScriptLib commented May 25, 2023

Hi @Rison-Hub,

Because iShow is a by-value parameter, testByVal can't change the external variable that provides iShow's argument. For that, you must use a by-reference parameter:

Public Sub testByRef(ByVal OutStr As Integer, ByRef iShow As Object)
    iShow = iShow + 5
    Console.WriteLine("testByRef: " & iShow & "!")
End Sub

And then:

engine.Execute(@"
    Option Explicit
    dim ByReftest
    ByReftest = host.newVar(object, 3)
    Sub Main
        zp.testByRef 5, ByReftest.ref
        zp.print ByReftest
    End Sub
    Main
");

Output:

testByRef: 8!
strInfo : 8!

Note that the situation above with the array is different because .NET arrays are reference types, meaning that you can modify the contents of a .NET array without modifying the variable that holds a reference to it.

In this case, even though the Object type forces the integer argument to be boxed and passed by reference, boxed values are immutable, so iShow = iShow + 5 creates a new boxed value instead of modifying the one to which ByValtest refers.

Good luck!

@Rison-Hub
Copy link
Author

Rison-Hub commented May 26, 2023

engine.AddHostObject("host", new HostFunctions());

@ClearScriptLib I need to integrate with HALCON, where HObject is a reference type.
VB Code


Public Function EmptyHObj() As Object
        Dim image As HObject = Nothing
        HOperatorSet.GenEmptyObj(image)
        Return image
End Function

'Read Image
Public Sub New_ReadImage(ByVal Template_path As String, ByVal image As Object)
        Dim ho_Image As HObject = Nothing
        Dim hv_Template_path As HTuple = Nothing
        hv_Template_path = New HTuple(Template_path)
        HOperatorSet.ReadImage(image , hv_Template_path)
End Sub

'Display  Image
Public Sub display_open(ByVal Image As Object)
        Dim HWindow As HWindowControl = frm_open.HWindowControl1
        Dim Window As HTuple = HWindow.HalconWindow
        HWindow.HalconWindow.ClearWindow()
        Dim hv_Width As HTuple = Nothing
        Dim hv_Height As HTuple = Nothing
        If IsNothing(ho_Image) = False AndAlso ho_Image.IsInitialized Then
            HOperatorSet.GetImageSize(ho_Image, hv_Width, hv_Height)
            HOperatorSet.SetPart(Window, 0, 0, hv_Height, hv_Width)
            HOperatorSet.DispObj(ho_Image, Window)
        End If
        hv_Width = Nothing
        hv_Height = Nothing
End Sub

C# Code


engine.AddHostObject("host", new HostFunctions());
engine.AddHostObject("zp", VBscript);
engine.AddHostType(typeof(object));
engine.Execute(@"
    Option Explicit
Sub Main
	dim Image_Path,Image
	Image_Path="D:\AUTO Test System\Write\Image_Correction\MB2701-A1\Image\0.bmp"
	Image=zp.EmptyHObj()
	call zp.New_ReadImage(Image_Path,Image)
	call zp.display_open(Image)	
End Sub
Main
");

The value passed to display_open is the return value of EmptyHObj, not the value read from New_ReadImage.

However, if you use the following code, the value passed is correct.

VB Code


Public Function EmptyHObj() As Object
        Dim image As HObject = Nothing
        HOperatorSet.GenEmptyObj(image)
        Return image
End Function

'Read Image
Public Sub New_ReadImage(ByVal Template_path As String, ByRef image As Object)
        Dim ho_Image As HObject = Nothing
        Dim hv_Template_path As HTuple = Nothing
        hv_Template_path = New HTuple(Template_path)
        HOperatorSet.ReadImage(image , hv_Template_path)
End Sub

'Display  Image
Public Sub display_open(ByVal Image As Object)
        Dim HWindow As HWindowControl = frm_open.HWindowControl1
        Dim Window As HTuple = HWindow.HalconWindow
        HWindow.HalconWindow.ClearWindow()
        Dim hv_Width As HTuple = Nothing
        Dim hv_Height As HTuple = Nothing
        If IsNothing(ho_Image) = False AndAlso ho_Image.IsInitialized Then
            HOperatorSet.GetImageSize(ho_Image, hv_Width, hv_Height)
            HOperatorSet.SetPart(Window, 0, 0, hv_Height, hv_Width)
            HOperatorSet.DispObj(ho_Image, Window)
        End If
        hv_Width = Nothing
        hv_Height = Nothing
End Sub

C# Code


engine.AddHostObject("host", new HostFunctions());
engine.AddHostObject("zp", VBscript);
engine.AddHostType(typeof(object));
engine.Execute(@"
    Option Explicit
Sub Main
	dim Image_Path,Image
	Image_Path="D:\AUTO Test System\Write\Image_Correction\MB2701-A1\Image\0.bmp"
	Image=host.newVar(object, zp.EmptyHObj())
	call zp.New_ReadImage(Image_Path,Image.ref)
	call zp.display_open(Image)	
End Sub
Main
");

@ClearScriptLib
Copy link
Collaborator

Hi @ClearScriptLib,

The behavior you're seeing is correct.

According to the HALCON documentation, the ReadImage method looks like this (in C#):

static void ReadImage(out HObject image, HTuple fileName)

Note that image is an out – or output – parameter. At the .NET level, that's just a by-reference parameter. However, in C#, out – unlike ref – additionally specifies that the method ignores any passed-in value and replaces it on successful return.

In this case, ReadImage creates a new object and stores a reference to it in image. If some other object is passed in, the method ignores it. It does not modify it or operate on it in any way.

Therefore, you don't have to call GenEmptyObj. However, because Main needs the new object, New_ReadImage must either store it in a by-reference parameter or simply return it.

Cheers!

@Rison-Hub
Copy link
Author

Rison-Hub commented May 29, 2023

Hi @ClearScriptLib,

The behavior you're seeing is correct.

According to the HALCON documentation, the ReadImage method looks like this (in C#):

static void ReadImage(out HObject image, HTuple fileName)

Note that image is an out – or output – parameter. At the .NET level, that's just a by-reference parameter. However, in C#, out – unlike ref – additionally specifies that the method ignores any passed-in value and replaces it on successful return.

In this case, ReadImage creates a new object and stores a reference to it in image. If some other object is passed in, the method ignores it. It does not modify it or operate on it in any way.

Therefore, you don't have to call GenEmptyObj. However, because Main needs the new object, New_ReadImage must either store it in a by-reference parameter or simply return it.

Cheers!

Thank you again for your guidance.

ClearScriptLib added a commit that referenced this issue Jun 1, 2023
…tors (mentioned in GitHub Issue #444); fixed COM-related memory leak on .NET Framework (GitHub Issue #510); enabled multidimensional array manipulation via VBScript indexing syntax (GitHub Issue #511); improved stability on Apple Silicon devices. Tested with V8 11.4.183.17.
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