【问题标题】:Why isn't there an AutoUnknown value in the System.Runtime.InteropServices.ClassInterfaceType Enumeration?为什么 System.Runtime.InteropServices.ClassInterfaceType 枚举中没有 AutoUnknown 值?
【发布时间】:2013-03-07 01:03:11
【问题描述】:

.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)。实际上,它实际上与 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() 函数中。

谢谢!

【问题讨论】:

  • 你从根本上误解了 COM 的工作原理,从 [ClassInterfaceType] 的含义开始。解释它需要一本书,有很多可用的。
  • 我非常了解 COM 的工作原理。当然,总是要学习更多,感谢您对有很多书的观察。无论如何,重申我的问题:我将需要完全基于我的类的公共非静态成员的IUnknown-派生接口,所以我希望社区提供一些关于自动生成它们的想法,以便我不不必在每次更改时手写它们。我对任何和所有建设性的想法持开放态度,包括在不手写接口的情况下禁止后期绑定客户端的替代方法。谢谢。
  • “我将需要完全基于我的类的公共非静态成员的IUnknown-派生接口”——正是AutoDual 提供的。
  • 对于每个类,AutoDual 生成一个IDispatch 派生接口,该接口不仅包括类的公共非静态成员,还包括基类和每个接口的公共非静态成员。我的类或我的基类(及其基类等)实现。真是一团糟!
  • 另外@ZdeslavVojkovic 我不希望我的基类成员出现在我的界面中的一个原因是基类可以更改它自己的成员。当我的班级的实际成员根本没有改变时,我的整个班级界面(包括它的 IID)突然发生了变化。如果我能够自动生成 just 我的类自己的接口(并让我的祖先类的接口自行处理),那么我每次构建我的程序集时都不必担心“我的客户突然无法调用我了。

标签: .net com interopservices tlbexp


【解决方案1】:

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

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

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

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

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

【讨论】:

  • 我想只支持早绑定客户端正是因为晚绑定客户端面临版本不兼容的严重风险,即二进制不匹配导致崩溃,而早绑定客户端需要 QueryInterface()得到他们需要的接口,所以随着 IID 的改变(因为接口改变了),他们会得到 E_NOINTERFACE ,他们可以优雅地处理它。只要 IID 在接口发生变化时发生变化,我认为没有理由不提供没有后期绑定能力的类接口。
  • 你搞错了。早期绑定的客户端会遇到二进制不匹配,因为它们与 vtable 布局耦合,而不是后期绑定 - 在不匹配的情况下,它们只会返回 DISP_E_MEMBERNOTFOUND(或 DISP_E_BADPARAMCOUNT/DISP_E_BADVARTYPE)。但是,AutoDual 对于早期绑定的客户来说很好,所以只要使用它就可以了。您正在尝试使用某个功能来做一些原本不适合做的事情。
  • 我承认,正确编写的后期绑定客户端也可以避免二进制不匹配,因为它不会缓存接口详细信息(例如 dispid 和参数计数)。我仍然没有看到AutoUnknown 不存在的任何理由:我只是不想支持后期绑定的客户端;我不希望我的接口派生自IDispatch,我不希望我的类型库中出现膨胀。这里没有答案,但我现在正在继续我自己的自动生成,但很想听到更多的想法。感谢(感谢您和所有人)迄今为止的见解!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-08
  • 2019-06-21
  • 1970-01-01
  • 2019-06-24
  • 1970-01-01
相关资源
最近更新 更多