Search code examples
c#vbscriptmarshallingcom-interop

Marshall C# string[] to VBScript


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?


Solution

  • 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[].