如何将字节数组作为UDT属性从VB6 / VBA传递到C#COM DLL?

时间:2012-11-01 20:43:51

标签: c# vba com vb6 marshalling

我有一个C#库,我正在尝试向VBA公开。我可以很好地将参数传递给函数(即“ref byte [] someArray”),但传递对象或结构只是不起作用。

如果我尝试传递一个字节数组作为类的属性,我在VB中得到以下错误 -

标记为受限制的函数或接口,或者该函数使用Visual Basic中不支持的自动化类型

如果我尝试传递一个字节数组作为结构的属性,我在VB中得到以下错误 -

我已经打了两天了,而且我一直在寻找声称有答案的帖子,但没有一个对我有效。

所以这是我目前的代码:

[ComVisible(true)]
[Guid("7F53F7A5-15C9-4A99-A855-38F5E87702D0")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]       // Tried as InterfaceIsDual and as InterfaceIsIDispatch
public interface IDetail
{
    [DispId(1)]     // Tried with and without these
    int SomeInt { get; set; }

    [DispId(2)]
    string SomeString { get; set; }

    [DispId(3)]
    byte[] SomeByteArray { 
        return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UI1)]
        get;
        [param: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UI1)]
        set; 
    }
}

[ComVisible(true)]
[Guid("F77FB3D4-27E0-4BFA-A21E-5ACB671151E9")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("G4COMTest.Detail")]
public class Detail:IDetail
{
    public int SomeInt { get;set; }
    public string SomeString { get; set; }

    // Tried MarshalAs in all combinations of class and interface
    public byte[] SomeByteArray {
        [return: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UI1)]
        get; 
        [param: MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UI1)]
        set;
    }
}

[ComVisible(true)]
[Guid("5E8F9FF0-3156-479E-A91D-0DADD43881FB")]
[ClassInterface(ClassInterfaceType.None)]
public class Worker:IWorker
{
    // works with the 'ref'
    public int ReturnIntWByteArrayParam(ref byte[] testByteArray)
    {
        return testByteArray.Count();
    }

    public int ReturnIntWObjParam(IDetail detail)
    {
        return detail.SomeInt;
    }

    public IDetail ReturnObjNoParams()
    {
        var o = new Detail();
        o.SomeInt = 87;
        o.SomeString = "What are you doing Dave";
        return o;
    }
}

[ComVisible(true)]
[Guid("04962F29-DBBD-48AC-B4FB-180EEF562771")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IWorker
{
    int ReturnIntWByteArrayParam(ref byte[] testByteArray);
    int ReturnIntWObjParam(IDetail detail);
    IDetail ReturnObjNoParams();
}

从VB6调用它:

Dim o As New G4COMTest.Worker
Dim d As New G4COMTest.Detail
Dim byt(2) As Byte

d.SomeInt = 356                     '// Works
d.SomeString = "Hello from client"  '// Works
d.SomeByteArray = byt               '// Errors as either class or struct
MsgBox mWorker.ReturnIntWObjParam(d)

提前感谢您的帮助!

2 个答案:

答案 0 :(得分:1)

C#数组属性完全按照预期向COM公开getter和setter(不需要MarshalAs属性,默认情况下封送器会检测到它。)

问题是,与.NET中的所有属性设置器一样,setter按值传递value参数。不幸的是,VBA不支持按值传递数组。这是从第一天起就一直存在的语言的基本限制。更不幸的是,COM interop没有提供任何方法来使用属性覆盖此行为。你有两个选择:

A - 定义你自己的setter方法并从VBA而不是属性setter中调用它,例如

void SetSomeByteArray(ref byte[] value) { SomeByteArray = value; }

B - 将属性类型更改为object并使用变量数组而不是强类型数组。

PS:也要小心string属性。这些通常都可以正常工作,但如果您将null字符串值传递给VBA,则会出错,因为VBA String类型无法存储null引用。

答案 1 :(得分:0)

在您的代码类中,Detail将ClassInterfaceType设置为None,如果将其设置为AutoDispatch,则应使用您应该使用的代码。来自MSDN:

  

对于脚本化客户端,Microsoft Visual Basic 6.0客户端或任何不缓存接口成员DispIds的后期绑定客户端,使用类接口是可接受的选项。"

http://msdn.microsoft.com/en-us/library/4fcadw4a(v=vs.110).aspx

由于您调用的客户端是VB6 - 您可以将ClassInterfaceType设置为AutoDispatch,甚至可以省略它(默认为AutoDispatch)。这将仅生成仅Dispatch类接口,并且不包括接口中的任何成员。从VB6调用时,将数组直接分配给属性应该可以正常使用IDispatch Invoke函数(后期绑定)。

我们用字符串数组对它进行了测试,结果正常。