Dual
Interfaces
双重接口
自定义接口可以提供最佳的性能,而调度接口可以被脚本客户端调用。为了两全其美,微软设计出了双重接口。双重接口从IDispatch接口派生,但是添加了一些新的方法到vtable中(见图4-6)。只知道IDispatch的客户端可以使用它的GetIDsOfNames和Invoke方法来得到和访问那些方法指针,但是可以访问自定义接口的客户端也可以直接使用它们。

图4-6 一个双重接口的列表
要把一个.NET接口定义为双重接口,你可以使用特性[InterfaceType]然后在其构造函数中设定ComInterfaceType.InterfaceIsDual,如代码段4-7所示。是否定义这个特性是可选的,因为这在接口上是默认值。
代码段4-7 定义一个双重接口
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface ICourseManagement
{
CourseInfo GetCourse();
void SetCourse(CourseInfo course);
CustomerControl GetCustomerControl();
}
在IDL定义中,ICourseManagement已经是一个双重接口,里面的方法被记录在vtable中,并且也带有调度ID。晚期绑定会使用映射表里的这些调度ID。
[
odl,
uuid(11BC9B79-C02C-39D6-A70F-32E83B18DD0E),
version(1.0),
dual,
oleautomation,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9,
Demos.COMInterop.ICourseManagement)
]
interface ICourseManagement : IDispatch {
[id(0x60020000)]
HRESULT GetCourse([out, retval] _CourseInfo** pRetVal);
[id(0x60020001)]
HRESULT SetCourse([in] _CourseInfo* course);
[id(0x60020002)]
HRESULT GetCustomerControl([out, retval]
_CustomerControl** pRetVal);
};
和调度接口一样,你也可以在类级别上定义双重接口,如代码段4-8所示。
代码段4-8 通过类特性定义一个双重接口
[EventTrackingEnabled]
[PrivateComponent]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class CustomerControl : ServicedComponent
{
public CustomerControl()
{
}
public Customer GetCustomer()
{
Customer c = new Customer("Stephane", "Addison Wesley");
return c;
}
}
在类CustomerControl上添加了ClassInterfaceType.AutoDual声明后,就会创建一个双重接口_CustomerControl。这个接口不但带有已经在类CustomerControl中定义的公有方法,还会带上来自基类Object、MarshalByRefObject和ServicedComponent的方法。
[
odl,
uuid(D0E44CA4-8D41-3860-8E5F-C36EE7893A59),
hidden,
dual,
nonextensible,
oleautomation,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9,
Demos.COMInterop.CustomerControl)
]
interface _CustomerControl : 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 GetLifetimeService([out, retval] VARIANT* pRetVal);
[id(0x60020005)]
HRESULT InitializeLifetimeService([out, retval] VARIANT* pRetVal);
[id(0x60020006)]
HRESULT CreateObjRef(
[in] _Type* requestedType,
[out, retval] _ObjRef** pRetVal);
[id(0x60020007)]
HRESULT Dispose();
[id(0x60020008)]
HRESULT GetCustomer([out, retval] _Customer** pRetVal);
};
双重接口同时具有调度接口和自定义接口的优点,这看上去双重接口是一种万能的COM接口类型。然而,双重接口也带来了新的问题。它限制了数据类型必须能被打包到
VARIANT中,因为Invoke方法只能处理VARIANT类型。使用调度接口时当然也有同样的问题。另外一个问题在于脚本客户端只能使用一个接口,而如果把所有的方法声明都放在一个接口中则不是一个好的面向对象的设计。现在有一些模式来设计和实现多个接口,并且可以为脚本客户端提供所有的方法。在 .NET之前,实现这样的行为比较难。但是,在 .NET中你自动就能得到这种能力。调度接口会随着服务组件的类型自动创建,这个类型会实现接口上的所有方法,所以你就可以在脚本客户端访问所有的方法。
很多书描述了如何处理COM接口类型,并且介绍了如何使用各种技巧避免COM客户端的问题。不过,在这本书中,我们将不会对这些接口作更细致的讨论,因为你已经了解了在企业服务中从 .NET的角度使用这些接口时需要注意哪些最重要的问题。
事实查证:应该使用何种接口类型?
作为一个总结,对于COM客户端你有三种不同的接口类型可以选择:自定义接口、调度接口和双重接口。你的服务组件应该用什么样的接口类型?这取决于客户端的类型。如果是一个脚本客户端,IDispatch接口是必须的。所以对于脚本客户端,可以选择调度接口或者双重接口。而用Visual Basic 6和用C++编写的客户端可以接受所有的接口类型,但是在C++ 中使用调度接口会比较困难。
如果不需要脚本客户端,那么最佳选择是自定义接口。






