类接口简介

类接口是未在托管代码中显式定义的接口,它将公开在 .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。

请参见

参考

ClassInterfaceAttribute

概念

COM 可调用包装

为互操作限定 .NET 类型