【发布时间】: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