【问题标题】:How to Pass Byte Arrays as UDT Properties from VB6/VBA to C# COM DLL?如何将字节数组作为 UDT 属性从 VB6/VBA 传递到 C# COM DLL?
【发布时间】:2012-10-22 12:25:19
【问题描述】:

我有一个尝试向 VBA 公开的 C# 库。我可以很好地将参数传递给函数(即“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)

提前感谢您的帮助!

【问题讨论】:

    标签: c# vba com vb6 marshalling


    【解决方案1】:

    C# 数组属性向 COM 公开了一个 getter 和一个 setter,正如您所期望的那样(MarshalAs 属性是不必要的,默认情况下封送拆收器会正确检测到它)。

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

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

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

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

    PS:也要小心string 属性。这些通常工作得很好,但是如果你将 null 字符串值传递给 VBA,它会出错,因为 VBA String 类型不能存储 null 引用。

    【讨论】:

    • 感谢您的快速回复。我害怕这会是答案。使用选项 B 的缺点是什么?
    • 我自己没有尝试过,所以我不能肯定地说,除了你失去了一点类型安全之外。
    【解决方案2】:

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

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

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

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

    我们用一个字符串数组对此进行了测试,它可以工作。

    【讨论】:

    • 谢谢,但是这个项目已经到了一个更好的地方(别人的手)。也许这对其他人有用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-10-08
    • 2012-07-13
    • 2013-08-09
    • 2011-11-20
    • 2023-04-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多