通过COM从VBA传递对象到.NET中存在不一致性

3

我已经定义了以下接口,将一个.NET类暴露给COM:

[InterfaceType(ComInterfaceType.InterfaceIsDual)]
[Guid("6A983BCC-5C83-4b30-8400-690763939659")]
[ComVisible(true)]
public interface IComClass
{
    object Value { get; set; }

    object GetValue();

    void SetValue(object value);
}

这个接口的实现非常简单:
[ClassInterface(ClassInterfaceType.None)]
[Guid("66D0490F-718A-4722-8425-606A6C999B82")]
[ComVisible(true)]
public class ComClass : IComClass
{
    private object _value = 123.456;

    public object Value
    {
        get
        {
            return this._value;
        }

        set
        {
            this._value = value;
        }
    }

    public object GetValue()
    {
        return this._value;
    }

    public void SetValue(object value)
    {
        this._value = value;
    }
}

我已经使用RegAsm进行了注册,然后尝试通过以下代码从Excel调用它:

Public Sub ComInterop()

    Dim cc As ComClass
    Set cc = New ComClass

    cc.SetValue (555.555)

    valueByGetter = cc.GetValue 
    valueByProperty = cc.Value 

    cc.Value = 555.555 

End Sub

当我逐步执行这段代码时,valueByGetter的值为555.5555,valueByProperty的值为555.555,与预期相同。然而,在最后一行出现了“Object required”运行时错误。
为什么通过setter方法设置值可以工作,但通过属性设置失败?我需要改变什么才能让属性按预期工作?
编辑:我已经得到了一些有用的回复,因此我的额外问题是“这个问题是否会在其他语言编写的COM客户端中出现,还是只限于VBA?”

你尝试过将调试器附加到Excel进程并逐步执行,看看它在哪里出错了吗? - Sam Holder
不确定您指的是哪个调试器。如果我通过VS.Net启动Excel作为外部程序来调试.NET代码,我无法到达Value属性设置部分的断点,这表明这是一个Excel问题。如果您是直接调试Excel,我不知道从哪里开始 - 您能否给我更多信息?谢谢。 - Akash
4个回答

4
您的接口导出到类型库的方法如下所示:

dispinterface IComClass {
    properties:
    methods:
        [id(00000000), propget]
        VARIANT Value();
        [id(00000000), propputref]             // <=== problem here
        void Value([in] VARIANT rhs);
        [id(0x60020002)]
        VARIANT GetValue();
        [id(0x60020003)]
        void SetValue([in] VARIANT Value);
};

问题在于Value属性的setter,它被声明为propputref而不是propput。这里有一个COM的一致性问题,由于支持默认属性而带来的。这就是为什么在VBA中必须使用Set关键字的原因。问题是:尽管.NET期望传递一个对象,但你在VBA代码中没有传递一个对象。
经过了一番研究,我没有找到任何解决此问题的现成方案。有一种很小的可能性是Let关键字在VBA中可以起作用,但我没有尝试过。唯一还算不错的解决方法是强制使用晚期绑定,而不是使用ComInterfaceType.InterfaceIsDual,而是使用ComInterfaceType.InterfaceIsIDispatch。或者避免使用“object”。

谢谢提供信息,虽然不是我想要的答案 :) 那么这个问题只会在 VBA 客户端中出现吗?我一直通过 VBA 测试我的 COM 接口,但无法控制客户端将使用的语言。 - Akash
你真的需要支持 Variant 吗?你真的需要支持早期绑定吗?从你的问题中无法得出足够的信息来做出决定。 - Hans Passant
我认为早期绑定 + 智能感知使得我的 API 的使用者更容易上手(因此减少了支持电话!)。至于支持 Variant(对象),鉴于我需要 Value 接受的类型,我可以指定 Value 是一个字符串并进行适当解释,但这对 .NET 客户端来说似乎有点奇怪(也许不比它是对象更奇怪)。无论如何,您认为原始问题是特定于 VBA 还是更普遍的 COM 问题? - Akash

1

这似乎是与 tlbexp 有关的问题,微软建议使用类似于这样的后期绑定调用:

Dim cc As Object ' this one changed from ComClass
Set cc = New ComClass
...
cc.Value = 555.555

你可以保留它的早期绑定并尝试一种更简单的修复方法:

Set cc.Value = CVar(555.555)

你的晚期绑定建议可行,但 CVar 方法仍然出现与之前相同的错误(我已尝试过使用和不使用 Set 关键字)。 - Akash
你知道我是否会在使用不同语言编写的客户端中遇到相同的问题吗?即这是COM问题还是VBA问题? - Akash
这是一个 .Net COM interop 的问题。你确定 CVar 不会改变错误消息吗? - wqw
我再次检查了CVar方法,但它明显失败了 :( 鉴于这是一个.NET COM互操作问题,并且其他客户端可能没有后期绑定选项,因此听起来我需要用getter/setter替换属性或更改属性类型。 - Akash
这个回答和nobugz的回答都非常有帮助。他的回答更多地解释了问题的原因,所以我将他的回答设置为被接受的答案 - 希望这样可以。 - Akash

0
你需要在最后一行使用“Set”关键字:
Set cc.Value = 555.555

当我尝试这样做时,它会出现编译错误:需要对象。 - Akash

0

你尝试过类似这样的代码了吗(VB 有点生疏,但你应该能理解):

Dim newValue as Int
int= 555.555
cc.Value = newValue (or maybe set cc.Value=newValue)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接