1

.NET 中的System.Runtime.InteropServices.ClassInterfaceType枚举有一个AutoDispatch值和一个AutoDual值,但没有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)。的确,IUnknownAutoDual 机制的一半(减去派生成员)。事实上,它比 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);
};

如果现在没有办法自动执行此操作,我将编写一些使用反射来执行此操作的内容,因此我也会很感激有关这方面的任何建议。例如,我需要检查方法上的任何属性吗?关于参数?我深入研究了 ILSystem.Runtime.InteropServices.TypeLibConverter以了解它是如何做到的,但不幸的是,所有好东西都在我无法使用的私有内部调用 nConvertAssemblyToTypeLib() 函数中。

谢谢!

4

1 回答 1

0

真正的问题是你想达到什么目标?

来自ClassInterface文档:“指示要为暴露给 COM 的类生成的类接口的类型,如果生成了接口的话。”

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

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

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

于 2013-03-18T17:14:10.810 回答