以向后兼容的方式更改接口

RPC 和 COM 的版本控制理论中介绍的方法可能出于许多原因而不可接受。 根据规则更改接口版本实质上要求新客户端不与旧服务器通信。 对于在现场部署的商业软件,这通常是不可能的。 有时,Windows 会引入接口更改,而没有更改的 GUID 或版本。 这是新客户端需要与旧服务器通信的结果,并且新客户端支持旧接口和新接口的解决方案被视为不可取。

最佳做法

当无法更改接口 GUID 和版本时,这些是解决线路不兼容问题的合理方法。

  1. 让应用程序了解另一方的功能。

    客户端和服务器具有一个协议,使每个 (或至少允许新客户端) 建立合作伙伴的标识。 通常,让新客户端了解新旧服务器支持的功能就足够了。 当应用程序保留到连接上下文时,可以轻松地完成此操作,并在执行任何 RPC 操作之前,通过客户端执行的 XxxGetInfo 类型的函数调用提供支持。 当应用程序按服务器发布管理功能时,与旧服务器/客户端不兼容的调用永远不会发生,因为应用程序控制向哪个服务器发出哪些调用。 底线是应用程序主动防止发生不匹配。 这可以与第二个练习一起执行。

  2. 引入新的远程 API。

    如果在接口的末尾添加新的远程方法,则它不会与现有方法发生冲突。 旧客户端可以像往常一样调用新服务器。 新客户端可以在不知道服务器标识的情况下调用新方法,前提是它监视来自所调用服务器的错误。 RPC 运行时始终在调度之前检查每个接口的方法编号,以确保该方法位于适当的 v 表中。 对于服务器未知的方法,RPC 运行时会引发异常RPC_S_PROCNUM_OUT_OF_RANGE。 此异常仅在此特定情况下引发。 因此,新客户端可以watch异常作为调用已访问旧服务器的标志,并可以正常修改其行为。

  3. 仅在新方法中引入新参数或新数据类型。

    引入新方法的原因之一是避免数据不兼容。 如果引入或只是修改了新数据类型,原则上应仅在新方法 (或) 方法中使用。 有关 不兼容数据类型更改的示例 ,请参阅不兼容更改示例。 此规则的唯一值得注意的例外在项 4 中进行了介绍。

  4. 通过包装器映射新参数或新数据类型。

    当必须向用户公开新参数或数据类型,但实际上不必单独远程或可以映射到旧数据类型或参数时,此解决方案适用。 例如,许多系统 API 会转而执行远程调用。 它们可能执行也可能不执行从用户已知数据类型到基础 RPC 调用中实际使用的数据类型的某种映射。 因此,始终值得检查用户界面中的更改是否需要作为更改传播到远程接口。

    当用户直接调用远程 API 时,可能会发生类似的情况,但可能会引入包装器来执行新的类型映射或一些其他必要的其他操作。 接口定义语言 (IDL) 有多种方法来促进此类重新映射,即 [call_as]、[transmit_as] 和 [wire_marshal]。 [call_as] 属性在客户端和服务器上引入函数包装器。 两者都放置在用户代码和封送处理程序之间。 其他属性处理直接类型映射。 对于扩展问题,[call_as] 是最常用的,并且最容易理解和操作,没有缺陷。

  5. 通过无默认值联合修改数据类型。

    更改属性或数据类型通常会导致线路不兼容。 有关 示例,请参阅不兼容更改 的示例。 但是,在没有 default 子句的联合的情况下,可以采用类似于过程范围外的情况(如前所述)的方式管理不兼容。 此方案很容易适用于使用联合的常用 XxxINFO 类型。

    例如,如下所示的调用

    XxxGetInfo( [in] level, [out] XxxINFO  * pInfo );
    

    可以返回级别 1、2 或 3 的信息,其中 XxxINFO 是具有三个分支的联合:1、2 和 3。

  6. 使用 [range] 属性指定范围。

    可以在简单缩放类型上指定 [range] 属性,而不会中断向后兼容性。 此属性不会影响线路格式,但在取消封送期间,RPC 会检查线路上的值,以确认它是否在 .idl 文件中指定的范围内。 否则,将引发RPC_X_INVALID_BOUND异常。 如果服务器知道大小已调整的数组的最大大小,这尤其有用。

    例如:

    HRESULT Method1( [in, range(0,100)] ULONG m, [size_is(m)] ULONG *plong); 
    

当指示的级别为 4 且缺少 arm 时,RPC 行为取决于联合的定义。 对于已定义默认子句的联合,RPC 将传输默认子句中指示的类型,该类型与本例中已知的 arm 标签 (不同,即除 1、2 或 3 以外的任何) 。 对于无默认值联合,unmarshaler 会引发异常,因为根据定义,没有可回退到的默认值。 RPC_S_INVALID_TAG例外。

同样,新客户端可以在发现它调用旧服务器时调整其行为。

这些建议的做法是,如果必须设计将来可以扩展的远程数据类型,请在 IDL 文件中使用无默认值联合。 选择后,封装的联合会稍微更干净一些。

由于 NDR64 线路协议的内部表示形式存在怪异之处,本节前面提供的添加臂的建议需要限定为:要添加的新分支不能更改并集的对齐方式,特别是臂的最大对齐方式不应更改。 这通常不是问题,因为臂中的指针强制对齐到 8。 每个臂都是指向臂类型的指针的设计是满足要求的一种干净方式。