简体   繁体   English

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

[英]How to Pass Byte Arrays as UDT Properties from VB6/VBA to C# COM DLL?

I have a C# library that I'm trying to expose to VBA. 我有一个C#库,试图将其公开给VBA。 I can pass parameters to functions just fine (ie "ref byte[] someArray"), but passing objects or structs just won't work. 我可以很好地将参数传递给函数(即“ ref byte [] someArray”),但是传递对象或结构将不起作用。

If I try passing a byte array as a property of a class, I get the following error in VB- 如果我尝试将字节数组作为类的属性传递,则会在VB-中收到以下错误

Function or interface marked as restricted, or the function uses an Automation type not supported in Visual Basic 函数或接口标记为受限,或者函数使用Visual Basic不支持的自动化类型

If I try passing a byte array as a property of a struct, I get the following error in VB- 如果我尝试将字节数组作为结构的属性传递,则会在VB-中收到以下错误

I've been fighting this for two days now and while I keep finding posts that claim to have the answer, none of them have worked for me. 我已经为此奋斗了两天,尽管我不断找到声称有答案的帖子,但没有一个对我有用。

So here's my code as it currently sits: 所以这是我目前的代码:

[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();
}

Calling it from VB6: 从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)

Thanks in advance for any help! 在此先感谢您的帮助!

The C# array property exposes a getter and a setter to COM exactly as you would expect it to (the MarshalAs attribute is unnecessary, the marshaler does detect it correctly by default). C#数组属性完全按照您的期望向COM公开了一个getter和setter( MarshalAs属性是不必要的,封送拆封器默认情况下会正确检测到它)。

The problem is that the setter, like all property setters in .NET, passes the value parameter by value. 问题在于,设置器与.NET中的所有属性设置器一样,按值传递value参数。 Unfortunately, VBA does not support passing arrays by value. 不幸的是,VBA不支持按值传递数组。 It's a fundamental limitation of the language that's been there since day one. 从第一天开始,这就是对这种语言的基本限制。 Even more unfortunately, COM interop does not provide any way to override this behaviour with attributes. 更不幸的是,COM互操作无法提供任何方法来使用属性覆盖此行为。 You have two choices: 您有两种选择:

A - Define your own setter method and call it from VBA instead of the property setter, eg A-定义您自己的设置器方法,然后从VBA而不是属性设置器中调用它,例如

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

B - Change the property type to object and use variant arrays instead of strongly-typed arrays. B-将属性类型更改为object并使用变量数组而不是强类型数组。

PS: Be careful with string properties too. PS:还要注意string属性。 These normally work just fine, but if you pass a null string value to VBA, it will error because the VBA String type can't store null references. 这些通常可以正常工作,但是如果将null字符串值传递给VBA,则会出错,因为VBA String类型不能存储null引用。

In your code class Detail has the ClassInterfaceType set to None, if you set it to AutoDispatch the code you have should work. 在您的代码类中,Detail的ClassInterfaceType设置为None,如果将其设置为AutoDispatch,则您可以运行的代码。 From MSDN: 从MSDN:

Using the class interface is an acceptable option for scripted clients, Microsoft Visual Basic 6.0 clients, or any late-bound client that does not cache the DispIds of interface members." 对于脚本客户端,Microsoft Visual Basic 6.0客户端或任何不缓存接口成员的DispId的后期绑定客户端,使用类接口是可接受的选项。”

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

Since the client you are calling from is VB6 - you can set the ClassInterfaceType to AutoDispatch or even omit it (as default is AutoDispatch). 由于您从中呼叫的客户端是VB6-您可以将ClassInterfaceType设置为AutoDispatch或什至忽略它(默认为AutoDispatch)。 This will generate only the Dispatch only class interface and not include any members from the interface. 这将仅生成“仅分发”类接口,并且不包括该接口中的任何成员。 When calling from VB6, assigning an array directly to a property should work as it uses IDispatch Invoke function (late binding). 从VB6调用时,直接使用属性分配数组应该起作用,因为它使用IDispatch Invoke函数(后期绑定)。

We tested this with a string array and it works. 我们使用字符串数组对此进行了测试,并且可以正常工作。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM