类接口简介
更新:2007 年 11 月
类接口是未在托管代码中显式定义的接口,它将公开在 .NET 对象上显式公开的所有公共方法、属性、字段和事件。此接口可以是双绑定接口,也可以是仅调度接口。类接口将接收 .NET 类本身的名称,并在名称前加有下划线。例如,对于 Mammal 类,类接口为 _Mammal。
对于派生类,类接口还会公开基类的所有公共方法、属性和字段。派生类也会为每个基类公开一个类接口。例如,如果 Mammal 类扩展 MammalSuperclass 类,而 MammalSuperclass 本身又扩展 System.Object,.NET 对象将向 COM 客户端公开三个名为 _Mammal、_MammalSuperclass 和 _Object 的接口。
例如,考虑以下 .NET 类:
' Applies the ClassInterfaceAttribute to set the interface to dual.
<ClassInterface(ClassInterfaceType.AutoDual)> _
' Implicitly extends System.Object.
Public Class Mammal
Sub Eat()
Sub Breathe()
Sub Sleep()
End Class
// Applies the ClassInterfaceAttribute to set the interface to dual.
[ClassInterface(ClassInterfaceType.AutoDual)]
// Implicitly extends System.Object.
public class Mammal
{
void Eat();
void Breathe():
void Sleep();
}
COM 客户端可以获取指向 _Mammal 类接口的指针,该类接口在类型库导出程序 (Tlbexp.exe) 工具生成的类型库中进行描述。如果 Mammal 类实现一个或多个接口,这些接口将显示在 coclass 下。
[odl, uuid(…), hidden, dual, nonextensible, oleautomation]
interface _Mammal : IDispatch
{
[id(0x00000000), propget] HRESULT ToString([out, retval] BSTR*
pRetVal);
[id(0x60020001)] HRESULT Equals([in] VARIANT obj, [out, retval]
VARIANT_BOOL* pRetVal);
[id(0x60020002)] HRESULT GetHashCode([out, retval] short* pRetVal);
[id(0x60020003)] HRESULT GetType([out, retval] _Type** pRetVal);
[id(0x6002000d)] HRESULT Eat();
[id(0x6002000e)] HRESULT Breathe();
[id(0x6002000f)] HRESULT Sleep();
}
[uuid(…)]
coclass Mammal
{
[default] interface _Mammal;
}
是否生成类接口是可选的。默认情况下,COM 互操作将为导出到类型库的每个类生成一个仅调度的接口。可以通过将 ClassInterfaceAttribute 应用于类,阻止或修改此接口的自动创建。虽然类接口可以为向 COM 公开托管类这一任务提供方便,但其用途相当有限。
警告: |
---|
如果使用类接口而不是显式定义自己的接口,可能会使以后的托管类版本控制变得较为复杂。在使用类接口之前,请阅读以下指南。 |
为 COM 客户端定义显式的接口,而不是生成类接口。
由于 COM 互操作会自动生成类接口,当对类的后期版本进行更改时,可能会改变公共语言运行库所公开的类接口的布局。由于 COM 客户端通常未准备处理对接口布局的更改,所以当更改类的成员布局时,它们就会中断。
此指南强调了向 COM 客户端公开的接口必须保持不变的原则。要避免因无意地对接口布局重新排序而中断 COM 客户端,应通过显式定义接口来使对类的所有更改隔离于接口布局。
使用 ClassInterfaceAttribute 可解除类接口自动生成并实现类的显式接口,如以下代码片段所示:
<ClassInterface(ClassInterfaceType.None)>Public Class LoanApp
Implements IExplicit
Sub M() Implements IExplicit.M
…
End Class
[ClassInterface(ClassInterfaceType.None)]
public class LoanApp : IExplicit {
void M();
}
当类元数据导出到类型库时,ClassInterfaceType.None 值将禁止生成类接口。在上例中,COM 客户端只能通过 IExplicit 接口来访问 LoanApp 类。
避免缓存调度标识符 (DispId)。
对于用脚本编写的客户端、Microsoft Visual Basic 6.0 客户端或任何不缓存接口成员的 DispId 的后期绑定客户端,使用类接口是一种可以接受的选择。DispId 将标识接口成员,以支持后期绑定。
对于类接口,将根据成员在接口中的位置来生成 DispId。如果更改成员的顺序并将类导出到类型库中,就将更改在类接口中生成的 DispId。
要避免在使用类接口时中断后期绑定的 COM 客户端,请以 ClassInterfaceType.AutoDispatch 值来应用 ClassInterfaceAttribute。该值将实现仅调度的类接口,但会从类型库中省略接口说明。由于没有接口说明,客户端将无法在编译时缓存 DispId。虽然这是类接口的默认接口类型,但也可以显式地应用该属性值。
<ClassInterface(ClassInterfaceType.AutoDispatch)> Public Class LoanApp
Implements IAnother
Sub M() Implements IAnother.M
…
End Class
[ClassInterface(ClassInterfaceType.AutoDispatch]
public class LoanApp : IAnother {
void M();
}
要在运行时获取接口号的 DispId,COM 客户端可以调用 IDispatch.GetIdsOfNames。要针对接口调用某种方法,请将返回的 DispId 作为参数传递到 IDispatch.Invoke。
限制将双绑定接口选项用于类接口。
双绑定接口支持 COM 客户端对接口成员进行早期和后期绑定。在设计时和测试期间,您可能会发现将类接口设置为双绑定非常有用。对于从不会修改的托管类(及其基类),此选项也是可以接受的。在其他所有情况下,应避免将类接口设置为双绑定。
自动生成的双绑定接口可能会适用于少数的情况,而在多数情况下,它会带来与版本相关的复杂性。例如,使用派生类的类接口的 COM 客户端很容易在遇到对基类的更改时中断。当第三方提供基类时,您将无法控制类接口的布局。此外,与仅调度接口不同,双绑定接口 (ClassInterface.AutoDual) 在导出的类型库中提供了类接口的说明。这样的说明会促使后期绑定的客户端在运行时缓存 DispId。