C# property exposed to VBA (COM) : Run-time error

2019-02-20 00:04发布

This C# code is in a .NET 4.5 ComVisible assembly:

C# Code

[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[Guid("22341123-9264-12AB-C1A4-B4F112014C31")]
public interface IComExposed
{
    double[] DoubleArray { get; set; }
    object[] ObjectArray { get; set; }
    object PlainObject { get; set; }
    double ScalarDouble { get; set; }
}

[ClassInterface(ClassInterfaceType.None)]
[Guid("E4F27EA4-1932-2186-1234-111CF2722C42")]
[ProgId("ComExposed")]
public class ComExposed : IComExposed
{
    public double[] DoubleArray { get; set; }
    public object[] ObjectArray { get; set; }
    public object PlainObject { get; set; }
    public double ScalarDouble { get; set; }
}

From Excel 2010 32bit VBA, I've got the following behavior:

VBA Code

Dim VBArray(1 To 3) As Double
VBArray(1) = 1
VBArray(2) = 2
VBArray(3) = 3

Dim oComExposedEarlyBinding As New ComExposed

' Works
oComExposedEarlyBinding.ScalarDouble = 5

' Compile Error: Function or interface marked as restricted,
' or the function uses an Automation type not supported in Visual Basic
oComExposedEarlyBinding.DoubleArray = VBArray

' Compile Error: Function or interface marked as restricted,
' or the function uses an Automation type not supported in Visual Basic
oComExposedEarlyBinding.ObjectArray = VBArray

' Run-time error '424': Object required
oComExposedEarlyBinding.PlainObject = VBArray

' Run-time error '424': Object required
oComExposedEarlyBinding.PlainObject = 5

Dim oComExposedLateBinding As Variant
Set oComExposedLateBinding = New ComExposed

' Works
oComExposedLateBinding.ScalarDouble = 5

' Run-time error '5': Invalid procedure call or argument
oComExposedLateBinding.DoubleArray = VBArray

' Run-time error '13':  Type mismatch
oComExposedLateBinding.ObjectArray = VBArray

' Works
oComExposedLateBinding.PlainObject = VBArray

' Works
oComExposedLateBinding.PlainObject = 5

As you've noticed the PlainObject is working in late binding mode but, obviously, on the expense of losing typing and therefore losing auto complete (IntelliSense) in VBA which is not acceptable in my scenario.

The lines that I care for in my example are the following lines:

oComExposedEarlyBinding.DoubleArray = VBArray
oComExposedEarlyBinding.ObjectArray = VBArray
oComExposedEarlyBinding.PlainObject = VBArray

Getting any of the three lines above working would satisfy my need, so do you have any workaround or a solution that would make this work (note that I am not interested in passing the array as a parameter to a function)?

Update: After submitting this issue to Microsoft's support and waiting for almost three weeks. They confirmed that it is a bug and this is the KB: http://support.microsoft.com/kb/327084 and the only workaround within C# is what is marked as the solution below. However, I am able to confirm that this code works as expected if written in C++/CLI.

2条回答
ら.Afraid
2楼-- · 2019-02-20 00:36

VBA array must be zero based and in c# use ref parameter, sample:

Option Explicit

Sub test()
    Dim VBArray(0 To 2) As Double
    VBArray(0) = 1
    VBArray(1) = 2
    VBArray(2) = 3

    Dim oComExposedEarlyBinding As New ComExposed
    oComExposedEarlyBinding.SetDoubleArray VBArray

End Sub

using System.Runtime.InteropServices;

namespace COMVisibleTest
{
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    [Guid("22341123-9264-12AB-C1A4-B4F112014C31")]
    public interface IComExposed
    {
        void SetDoubleArray(ref double[] doubleArray);
    }

    [ClassInterface(ClassInterfaceType.None)]
    [Guid("E4F27EA4-1932-2186-1234-111CF2722C42")]
    [ProgId("ComExposed")]
    public class ComExposed : IComExposed
    {
        private double[] _doubleArray;

        public void SetDoubleArray(ref double[] doubleArray)
        {
            _doubleArray = doubleArray;
        }
    }
}
查看更多
乱世女痞
3楼-- · 2019-02-20 01:02

VBA always passes arrays wrapped in a variant by reference (VT_VARIANT | VT_BYREF), with another variant inside that contains the actual array, so you cannot use arrays in properties while specifing the element types, you need to use a method so that you can specify the parameters as "by reference".

[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] 
[Guid("22341123-9264-12AB-C1A4-B4F112014C31")] 
public interface IComExposed
{ 
     void setDoubleArray(ref double[] myArray); 
     //(...) 
} 

A similar question:
Pass an array from vba to c# using com-interop

An answer to that question mentions the option of using a user-defined collection instead of a array of primitive types, maybe that can also be a solution for your issue.

Relevant references in the documentation:

Marshaling ByRef Variants

VARIANT and VARIANTARG in WinAPI

MarshalAsAttribute class in .Net

Passing Arrays to COM in .Net

查看更多
登录 后发表回答