为什么System.Runtime.InteropServices.ClassInterfaceType枚举中没有AutoUnknown值?

时间:2013-03-18 15:52:53

标签: .net com interopservices tlbexp

.NET中的System.Runtime.InteropServices.ClassInterfaceType枚举值AutoDispatchAutoDual值,但没有AutoUnknown值。 为什么不呢,有没有人已经提出了一种相对自动化的方法来完成它,所以我不必重新发明轮子?

对于更多背景,以下是当前枚举中的三个值以及它们的作用:

  • ClassInterfaceType.None不会为您创建类接口。这是Microsoft推荐的值。没有类接口,您必须使用您想要的任何成员创建自己的接口,并让您的类实现它。然后,您可以使用接口上的System.Runtime.InteropServices.ClassInterfaceAttribute属性使其成为调度接口(支持后期绑定客户端),未知接口(支持早期绑定客户端)或双重(支持早期和后期绑定客户端)。
  • ClassInterfaceType.AutoDispatch创建一个派生自IDispatch的类接口,没有明确的成员。没有明确的成员,它只能支持后期绑定的呼叫者。
  • ClassInterfaceType.AutoDual创建一个派生自IDispatch的类接口,确实具有该类的所有公共非静态成员以及其基类的所有成员的显式成员。任何实现的接口。 Microsoft强烈反对使用此值“因为版本限制”,因为如果任何类的成员更改,类接口也将更改。 (因此,在类更改后不重新绑定的调用者最终可能会调用错误的方法或传递错误的参数。)

所以我想知道的是为什么没有一个名为ClassInterfaceType.AutoUnknown的值会创建一个派生自IUnknown的类接口,它明确地声明了类的成员(不是基类或它实现的其他接口。)

虽然我们正在使用它,但我也希望有类似于ClassInterfaceType.AutoDualDirect的东西类似于AutoDual,除了不暴露基类中的所有成员和所有实现的接口,它只会暴露类的直接成员,因为其他成员可以通过其他COM接口检索。

那么这里的故事是什么,我错过了什么?我知道微软表示,由于“版本化挑战”,它建议不要使用AutoDual,并且这些问题在某种程度上也适用于AutoUnknown;但尽管有这样的建议,他们仍然拥有 AutoDual。为什么他们也没有AutoUnknown?

关于那些“版本化挑战”的说法:这些问题中最危险的方面仅适用于后期绑定的来电者。 AutoUnknown将生成IUnknown派生的接口,而不是IDispatch派生的接口,因此我们只需关注早期绑定客户端可能具有的版本问题。并且由于任何自动生成的类接口的IID都会在类的公共非静态成员发生更改时发生更改,因此早期绑定的客户端将无法对现有成员进行“错误调用”,它将无法使用QueryInterface( )用于类接口。仍然是一个失败,但一个可管理的,不是崩溃或任何事情。这甚至没有打破永远不允许接口改变的COM规则;它没有改变,它正在成为一个新的界面(新的IID)。实际上,它实际上与AutoDual机制的IUnknown一半(减去派生成员)没有什么不同。事实上,它比AutoDual遭受更少的“版本化挑战”,因为AutoDual也有所有后期挑战。

所以我不得不问:如果AutoUnknown没有,为什么AutoDual会存在!

要提供一些实际示例,请考虑以下C#代码:

using System.Runtime.InteropServices;

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class F
{
    public int foo()
    {
        return 5;
    }
}

生成以下idl:

[
  uuid(1F3A7DE1-99A1-37D4-943E-1BF5CFDF7DFA),
  version(1.0),
  custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "F")
]
coclass F {
    [default] interface _F;
    interface _Object;
};

[
  odl,
  uuid(3D0A1144-1C0B-3877-BE45-AD8318898790),
  hidden,
  dual,
  nonextensible,
  oleautomation,
  custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "F")    

]
interface _F : IDispatch {
    [id(00000000), propget,
      custom(54FC8F55-38DE-4703-9C4E-250351302B1C, 1)]
    HRESULT ToString([out, retval] BSTR* pRetVal);
    [id(0x60020001)]
    HRESULT Equals(
                    [in] VARIANT obj, 
                    [out, retval] VARIANT_BOOL* pRetVal);
    [id(0x60020002)]
    HRESULT GetHashCode([out, retval] long* pRetVal);
    [id(0x60020003)]
    HRESULT GetType([out, retval] _Type** pRetVal);
    [id(0x60020004)]
    HRESULT foo([out, retval] long* pRetVal);
};

没关系,但我建议AutoUnknown会帮助生成这个idl:

[
  uuid(1F3A7DE1-99A1-37D4-943E-1BF5CFDF7DFA),
  version(1.0),
  custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "F")
]
coclass F {
    [default] interface _F;
    interface _Object;
};

[
  odl,
  uuid(BC84F393-DACC-353F-8DBE-F27CB2FB4757),
  version(1.0),
  oleautomation,
  custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "_F")    

]
interface _F : IUnknown {
    HRESULT _stdcall foo([out, retval] long* pRetVal);
};

如果现在无法自动执行此操作,我将编写一些使用反射来执行此操作的内容,因此我也非常感谢有关此方面的任何建议。例如,我需要检查方法的任何属性吗?关于参数?我挖掘了System.Runtime.InteropServices.TypeLibConverter的IL以查看它是如何做到的,但遗憾的是所有的好东西都在私人内部调用nConvertAssemblyToTypeLib()函数中,我无法得到它。

谢谢!

1 个答案:

答案 0 :(得分:0)

真正的问题是你试图实现什么目标?

来自ClassInterface docs:“表示为公开给COM的类生成的类接口的类型,如果完全生成接口的话。”

ClassInterface背后的想法是将类的编程接口提供给COM客户端 - 这就是为什么你看到完整的接口,包括基类方法。目标不是提供类功能子集的视图。这就是接口(和ClassInterfaceType.None

的用途

提供没有后期绑定功能的类接口(即IDispatch)是你不应该做的事情(并且ClassInterface幸运地没有启用它),因为它直接取决于布局你的班级正常运作。这意味着类和基类都不应该改变。这是一个难以保证的事情,以及为什么这一般是坏主意,除了它违背COM的整体哲学。早期绑定的客户端与界面的vtable布局紧密耦合 - 布局中的每次更改都会破坏它。后期绑定客户端不会出现此问题,因为方法是通过名称解析的,并且可以正确处理丢失的方法。

如果您真的需要,使用AutoDual已经提供了对方法的早期绑定访问,因此我不明白您为什么要重新发明轮子。