Marshall C# string[] to VBScript

Viewed 87

I have a Com-Visible -Net-Assembly which I want to use from VBScript. Most things works fine except one property thats returns a string[] to VBS.

The interface:

[Guid("25267107-CFD3-4A1B-8D94-639A7F189C0B"),
InterfaceType(ComInterfaceType.InterfaceIsDual),
ComVisible(true)]
public interface IComMethods : IDisposable
{
    string[] Interlockings { get; }
}

The implementation:

public string[] Interlockings
{
    get
    {
        return new string[] { "abc", "def" };
    }
}

The VBScript-Client:

Set mms2spc = CreateObject("Promess.mms2spc.ComMethods")
Dim testLCodes : testLCodes = mms2spc.Interlockings
If Not isEmpty(testLCodes) Then
    If isArray(testLCodes) Then
    Dim iCount : iCount = Ubound(testLCodes) + 1
        Stop
        For iLCode = 0 To Ubound(testLCodes)
            sTextInterlock = testLCodes(iLCode)
        Next
    End If
End If

However, when I try to use this in VBScript I see the Strings comming as an Array but I cannot access the elements. You can see this when looking at the debugger - every (n)-access gives Empty:

enter image description here

I think the marshalling from C# from VBS is wrong so I added an attribute but that doens't change anything:

public string[] Interlockings
{
    [return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)]`
    get{return new string[] { "abc", "def" };}
}

As a workaround I can declare everything in C# as object. That way it works in VBS but thats kind of awkward to me. So how to get this string[] to VBS?

1 Answers

Other languages do support it, but you cannot use an array of strings from VBScript (internally and in the type library it will be represented as a SAFEARRAY of BSTR).

It only support an array of objects, but you can also use the old ArrayList .NET class (which also allows for each type enumeration), for example in C#:

public interface IComMethods
{
    ArrayList Interlockings { get; }
    object[] InterlockingsAsObjects { get; }
}

public class MyClass: IComMethods
{
    public ArrayList Interlockings => new ArrayList(new string[] { "abc", "def" });
    public object[] InterlockingsAsObjects => new object[] { "abc", "def" };
}

And in VBScript:

set x = CreateObject("MyClassLibrary.MyClass")

' ArrayList
WScript.Echo "Count " & x.Interlockings.Count

for each il in x.Interlockings
    WScript.Echo il
next

WScript.Echo x.Interlockings.Item(1) ' def

' array of objects
ar = x.InterlockingsAsObjects
WScript.Echo "Count " & ubound(ar) - lbound(ar) + 1

for i = lbound(ar) to ubound(ar)
    WScript.Echo ar(i)
next

WScript.Echo ar(1) ' def

Another trick is to declare a COM interface as VBScript expects, but implement it privately so it looks better to .NET callers, something like this:

public class MyClass : IComMethods
{
    // better for .NET clients
    public string[] Interlockings => new string[] { "abc", "def" };

    // explicit implementation
    object[] IComMethods.Interlockings => Interlockings.ToArray<object>();
}

public interface IComMethods
{
    object[] Interlockings { get; }
}

Obviously, you can do the same with ArrayList instead of object[].

Related