COM Interop does NOT like uninitialized arrays!

I've created a simple reproduction case highlighting a problem I recently had with COM interop.  I've got a VB6 app using a .NET 2.0 component exposed to COM.  The component in this sample is pretty simple:

using System;
using System.Runtime.InteropServices;

namespace DotNetLibrary
{
    [Guid("FF7E820A-9F60-4B4C-BE17-93704377A65F")]
    public interface IClass1
    {
        [DispId(0x60030011)]
        int TheMethod(
            [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)]
            ref string[] Parameters);
    }

    [Guid("FFA1B994-66A3-4715-91C9-91468961ADB2")]
    [ClassInterface(ClassInterfaceType.None)]
    public class Class1 : IClass1  
    {
        public int TheMethod(
            [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)]
            ref string[] Parameters)
        {
            return 0;
        }
    }
}

This assembly is strong-named.  I use REGASM with the /codebase switch and generate a type library with the /tlb switch.  Here's the sample VB6 code that highlights the problem (the VB6 project has a reference to the generated type library).  NOTE: If you're going to test this, make sure you save your project before running since this sample blows up VB6.EXE...   ;)

Dim lateObj As Object
Dim earlyObj As DotNetLibrary.Class1

Dim parameters() As String

Set lateObj = CreateObject("DotNetLibrary.Class1")
Set earlyObj = CreateObject("DotNetLibrary.Class1")

Debug.Print earlyObj.TheMethod(parameters)
Debug.Print lateObj.TheMethod(parameters)

As you see I create two instance of the object.  One is early-bound, one is late-bound.  The first call (via the early-bound object) works fine.  The second (via the late-bound object) kills VB6.EXE (or kills the compiled application when run outside the VB6 IDE).  If I run the .NET component in debug mode inside VS.NET and set "VB6.EXE" as the startup project, the Managed Debugging Assistant pops up with a FatalExecutionEngineError:

-----------------
FatalExecutionEngineError was detected

Message: The runtime has encountered a fatal error. The address of the error was at 0x7a03d1be, on thread 0x1498. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack.
-----------------

Two things that don't make sense to me:

1) If I change the declaration of "parameters" in the VB6 code to include an upperbound (i.e. "Dim parameters(0) As String"), the code works in both early and late-bound.

2) #1 above leads me to believe it's something to do with null/non-null values -- but then why would it work when early-bound and not late-bound?

Has anyone ever seen anything like this before?  And why doesn't the framework catch this "bad interop" and handle it gracefully?

PS - The obvious solution of "always define an upperbound" isn't an option since there's already tons of code written (both by my client and other third parties) using the above style.  The .NET component is being developed to be binary-compatible with the old COM component so the VB6 client apps are not going to be recompiled.

1 Comment

  • I can't recall either whether VB treats that as a null pointer or an actual string array that is empty.

    If it's a null pointer, I would think the framework shouldn't have a problem since there's nothing to marshal. So it must be some kind of uninitialized/empty array.

    I just don't know why it's not caught by the runtime and handled gracefully.

Comments have been disabled for this content.