注册接口
本部分详细介绍了注册 RPC 接口的过程。
本节中的信息在以下主题中提供:
- 接口注册函数
- 入口点向量
- 经理 EPV
- 注册接口的单个实现
- 注册接口的多个实现
- 调用管理器例程的规则
- 将远程过程调用调度到 Server-Manager 例程
- 提供自己的 Object-Inquiry 函数
接口注册函数
服务器通过调用 RpcServerRegisterIf 函数来注册其接口。 复杂的服务器程序通常支持多个接口。 服务器应用程序必须为它们支持的每个接口调用此函数一次。
此外,服务器可以支持同一接口的多个版本,每个版本都有自己的接口函数实现。 如果服务器程序执行此操作,则必须提供一组入口点。 入口点是调度接口版本的调用的管理器例程。 每个版本的接口都必须有一个入口点。 入口点组称为入口点向量。 有关详细信息,请参阅 入口点矢量。
除了标准函数 RpcServerRegisterIf 外,RPC 还支持其他接口注册函数。 RpcServerRegisterIf2 函数通过允许指定一组注册标志 (请参阅接口注册标志) 、服务器可以接受的最大并发远程过程调用请求数以及传入数据块的最大大小(以字节为单位)来扩展 RpcServerRegisterIf 的功能。
RPC 库还包含一个名为 RpcServerRegisterIfEx 的函数。 与 RpcServerRegisterIf 函数一样,此函数注册接口。 服务器程序还可以使用此函数指定一组注册标志 (请参阅 接口注册标志) 、服务器可以接受的最大并发远程过程调用请求数和安全回调函数。
RpcServerRegisterIf、RpcServerRegisterIfEx 和 RpcServerRegisterIf2 函数在内部接口注册表表中设置值。 此表用于将接口 UUID 和对象 UUID 映射到管理器 EPV。 管理器 EPV 是一个函数指针数组,它只包含 IDL 文件中指定的接口中的每个函数原型的一个函数指针。
有关提供多个 EPV 以提供接口的多个实现的信息,请参阅 多个接口实现。
运行时库使用接口注册表表 (通过调用 函数 RpcServerRegisterIf、 RpcServerRegisterIfEx 或 RpcServerRegisterIf2) 设置,而对象注册表表 (通过调用函数 RpcObjectSetType) 设置,将接口和对象 UUID 映射到函数指针。
如果希望服务器程序从 RPC 运行时库注册表中删除接口,请调用 RpcServerUnregisterIf 函数。 从注册表中删除接口后,RPC 运行时库将不再接受该接口的新调用。
入口点向量
(EPV) 管理器入口点矢量是指向 IDL 文件中指定的函数实现的函数指针数组。 数组中的元素数对应于 IDL 文件中指定的函数数。 RPC 支持表示接口中指定的函数的多个实现的多个入口点向量。
MIDL 编译器自动生成管理器 EPV 数据类型,用于构造管理器 EPV。 数据类型命名为 if-name**_SERVER_EPV**,其中 if-name 指定 IDL 文件中的接口标识符。
MIDL 编译器自动创建和初始化默认管理器 EPV,前提是接口中的每个过程都存在同名的管理器例程,并在 IDL 文件中指定。
当服务器提供同一接口的多个实现时,服务器必须为每个实现创建一个额外的管理器 EPV。 对于 IDL 文件中定义的每个过程,每个 EPV 必须正好包含一个入口点 (函数) 地址。 服务器应用程序为接口的每个附加实现声明并初始化一个 类型为 if-name**_SERVER_EPV** 的管理器 EPV 变量。 若要注册EPV,请为其支持的每个对象类型调用 RpcServerRegisterIf、 RpcServerRegisterIfEx 或 RpcServerRegisterIf2 。
当客户端对服务器进行远程过程调用时,将根据接口 UUID 和对象类型选择包含函数指针的 EPV。 对象类型派生自对象 UUID 的对象查询函数或 RpcObjectSetType 控制的表驱动映射。
经理 EPV
默认情况下,MIDL 编译器使用接口 IDL 文件中的过程名称来生成管理器 EPV,编译器将其直接放置到服务器存根中。 此默认 EPV 是使用接口定义中声明的过程名称静态初始化的。
若要使用默认 EPV 注册管理器,请在调用 RpcServerRegisterIf、RpcServerRegisterIfEx 或 RpcServerRegisterIf2 函数时将 NULL 指定为 MgrEpv 参数的值。 如果管理器使用的例程名称对应于接口定义的名称,则可以使用 MIDL 编译器生成的接口的默认 EPV 注册此管理器。 还可以使用服务器应用程序提供的 EPV 注册管理器。
服务器可以 (,有时必须) 为接口创建和注册非 null 管理器 EPV。 若要选择服务器应用程序提供的 EPV,请将其值已由服务器声明为 MgrEpv 的值作为参数传递 EPV 的地址。 参数 MgrEpv 的非 null 值始终替代服务器存根中的默认 EPV。
MIDL 编译器自动生成管理器 EPV 数据类型 (RPC_MGR_EPV) ,供服务器应用程序用于构造管理器 EPV。 对于 IDL 文件中定义的每个过程,管理器 EPV 必须正好包含一个入口点 (函数地址) 。
在以下情况下,服务器必须提供非 null EPV:
- 当管理器例程的名称与接口定义中声明的过程名称不同时
- 当服务器使用默认 EPV 注册接口的另一个实现时
服务器通过为接口的每个实现初始化 if-name**_SERVER_EPV** 类型的变量来声明管理器 EPV。
注册接口的单个实现
当服务器只提供接口的一个实现时,服务器只调用 RpcServerRegisterIf、 RpcServerRegisterIfEx 或 RpcServerRegisterIf2 一次。 在标准情况下,服务器使用默认管理器 EPV。 (当管理器使用的例程名称不同于 interface 中声明的名称时,例外)
对于标准情况,为对 RpcServerRegisterIf、RpcServerRegisterIfEx 或 RpcServerRegisterIf2 的调用提供以下值:
经理 EPV
若要使用默认 EPV,请为 MgrEpv 指定一个空值参数。
管理器类型 UUID
使用默认 EPV 时,请通过为 MgrTypeUuid 提供 null 值或 nil UUID 参数,将接口注册到 nil 管理器类型 UUID 。 在这种情况下,所有远程过程调用(无论其绑定句柄中的对象 UUID 如何)都会调度到默认 EPV,前提是尚未进行 RpcObjectSetType 调用。
还可以提供非 nil 管理器类型 UUID。 在这种情况下,还必须调用 RpcObjectSetType 例程。
注册接口的多个实现
可以 (IDL 文件中指定的) 提供远程过程的多个实现。 服务器应用程序调用 RpcObjectSetType 将对象 UUID 映射到类型 UUID,并调用 RpcServerRegisterIf、 RpcServerRegisterIfEx 或 RpcServerRegisterIf2 以将管理器EPV与 UUID 类型相关联。 当远程过程调用与其对象 UUID 一起到达时,RPC 服务器运行时库会将对象 UUID 映射到 UUID 类型。 然后,服务器应用程序使用类型 UUID 和接口 UUID 来选择管理器 EPV。
还可以指定自己的函数来解析从对象 UUID 到管理器类型 UUID 的映射。 调用 RpcObjectSetInqFn 时指定映射函数。
若要提供接口的多个实现,服务器必须通过单独调用 RpcServerRegisterIf、 RpcServerRegisterIfEx 或 RpcServerRegisterIf2 来注册每个实现。 对于服务器注册的每个实现,它向相同的 IfSpec 提供参数,但为不同的 MgrTypeUuid 和 MgrEpv 对提供参数。
对于多个管理器,请使用 RpcServerRegisterIf、 RpcServerRegisterIfEx 或 RpcServerRegisterIf2 ,如下所示:
经理 EPV
若要提供接口的多个实现,服务器必须:
- 为每个附加实现创建一个非 null 管理器 EPV。
- 在 RpcServerRegisterIf、RpcServerRegisterIfEx 或 RpcServerRegisterIf2 中为 MgrEpv 指定参数的非 null 值。
请注意,服务器还可以向默认管理器 EPV 注册。
管理器类型 UUID
为接口的每个 EPV 提供管理器类型 UUID。 可以为其中一个管理器 EPV 指定参数) 的 nil 类型 UUID (或 null 值。 每种 UUID 类型必须不同。
调用管理器例程的规则
RPC 运行时库将传入的远程过程调用调度到提供请求的 RPC 接口的管理器。 为一个接口注册多个管理器时,RPC 运行时库必须选择其中一个管理器。 若要选择管理器,RPC 运行时库使用由调用的绑定句柄指定的对象 UUID。
运行时库在解释远程过程调用的对象 UUID 时应用以下规则:
Nil 对象 UUID
nil 对象 UUID 将自动分配 nil 类型 UUID, (在 RpcObjectSetType 例程) 指定 n 个对象 UUID 是非法的。 因此,其绑定句柄包含 nil 对象 UUID 的远程过程调用会自动调度到使用 nil 类型 UUID 注册的管理器(如果有)。
非 nil 对象 UUID
原则上,其绑定句柄包含非 nil 对象 UUID 的远程过程调用应由其类型 UUID 与对象 UUID 类型匹配的管理器进行处理。 但是,标识正确的管理器需要服务器通过调用 RpcObjectSetType 例程指定该对象 UUID 的类型。
如果服务器无法为非 nil 对象 UUID 调用 RpcObjectSetType 例程,则针对该对象 UUID 的远程过程调用将转到为远程过程调用提供服务的管理器 EPV,该管理器 EPV 具有 nil 对象 UUID (即 nil 类型 UUID) 。
如果服务器通过调用 RpcObjectSetType 例程为非 nil 对象 UUID 分配了类型 UUID,但未通过调用 RpcServerRegisterIf、 RpcServerRegisterIfEx 或 RpcServerRegisterIf2 为该类型 UUID 注册管理器 EPV,则无法执行绑定句柄中具有非 nil 对象 UUID 的远程过程调用。
下表汇总了运行时库用于选择管理器例程的操作。
调用的对象 UUID | 对象 UUID 的服务器集类型? | 服务器注册的 EPV 类型? | 调度操作 |
---|---|---|---|
零 | 不适用 | 是 | 使用具有 nil 类型 UUID 的管理器。 |
零 | 不适用 | 否 | 错误 (RPC_S_UNSUPPORTED_TYPE) ;拒绝远程过程调用。 |
非 nil | 是 | 是 | 使用具有相同类型 UUID 的管理器。 |
非 nil | 否 | 忽略 | 使用具有 nil 类型 UUID 的管理器。 如果没有具有 nil 类型 UUID 的经理,则错误 (RPC_S_UNSUPPORTEDTYPE) ;拒绝远程过程调用。 |
非 nil | 是 | 否 | 错误 (RPC_S_UNSUPPORTEDTYPE) ;拒绝远程过程调用。 |
调用的对象 UUID 是在远程过程调用的绑定句柄中找到的对象 UUID。
服务器通过调用 RpcObjectSetType 为对象指定 UUID 类型来设置对象 UUID 的类型。
服务器使用同一类型 UUID 调用 RpcServerRegisterIf、 RpcServerRegisterIfEx 或 RpcServerRegisterIf2 ,为管理器 EPV 注册类型。
注意
nil 对象 UUID 始终自动分配 nil 类型 UUID。 在 RpcObjectSetType 例程中指定 nil 对象 UUID 是非法的。
调度对服务器管理器例程的远程过程调用
下表显示了 RPC 运行时库将远程过程调用调度到服务器管理器例程所需的步骤。
下表描述了服务器注册默认管理器 EPV 的简单情况。
接口注册表表
接口 UUID | 管理器类型 UUID | 入口点矢量 |
---|---|---|
uuid1 | 零 | 默认 EPV |
对象注册表表
对象 UUID | 对象类型 |
---|---|
零 | 零 |
(任何其他对象 UUID) | 零 |
将绑定句柄映射到入口点矢量 (EPV)
来自客户端绑定句柄的接口 UUID () | 来自客户端绑定句柄的对象 UUID () | 从对象注册表表) (对象类型 | 从接口注册表表 (管理器 EPV) |
---|---|---|---|
uuid1 | 零 | 零 | 默认 EPV |
同上 | uuidA | 零 | 默认 EPV |
以下步骤描述了 RPC 服务器的运行时库在具有接口 UUID uuid1 的客户端调用该库时执行的操作,如上表所示。
服务器调用 RpcServerRegisterIf、 RpcServerRegisterIfEx 或 RpcServerRegisterIf2 ,以将其提供的接口与 nil 管理器类型 UUID 和 MIDL 生成的默认管理器 EPV 相关联。 此调用在接口注册表表中添加一个条目。 接口 UUID 包含在 IfSpec 参数中。
默认情况下,对象注册表表将所有对象 UUID 与 nil 类型 UUID 相关联。 在此示例中,服务器不调用 RpcObjectSetType。
服务器运行时库接收远程过程代码,其中包含调用所属的接口 UUID 和调用的绑定句柄中的对象 UUID。
有关如何将对象 UUID 设置为绑定句柄的讨论,请参阅以下函数引用条目:
使用远程过程调用中的接口 UUID,服务器的运行时库在接口注册表表中查找该接口 UUID。
如果服务器未使用 RpcServerRegisterIf、 RpcServerRegisterIfEx 或 RpcServerRegisterIf2 注册接口,则远程过程调用将使用RPC_S_UNKNOWN_IF状态代码返回给调用方。
使用绑定句柄中的对象 UUID,服务器的运行时库在对象注册表表中查找该对象 UUID。 在此示例中,所有对象 UUID 都映射到 nil 对象类型。
服务器的运行时库在接口注册表表中查找 nil 管理器类型。
将接口注册表表中的接口 UUID 和 nil 类型组合在一起会解析为默认 EPV,其中包含要为远程过程调用中找到的接口 UUID 执行的服务器管理器例程。
假设服务器提供多个接口和每个接口的多个实现,如下表所述。
接口注册表表
接口 UUID | 管理器类型 UUID | 入口点矢量 |
---|---|---|
uuid1 | 零 | epv1 |
uuid1 | uuid3 | epv4 |
uuid2 | uuid4 | epv2 |
uuid2 | uuid7 | epv3 |
对象注册表表
对象 UUID | 对象类型 |
---|---|
uuidA | uuid3 |
uuidB | uuid7 |
uuidC | uuid7 |
uuidD | uuid3 |
uuidE | uuid3 |
uuidF | uuid8 |
零 | 零 |
(任何其他 UUID) | 零 |
将绑定句柄映射到入口点向量
来自客户端绑定句柄的接口 UUID () | 来自客户端绑定句柄的对象 UUID () | 从对象注册表表) (对象类型 | 从接口注册表表 (管理器 EPV) |
---|---|---|---|
uuid1 | 零 | 零 | epv1 |
uuid1 | uuidA | uuid3 | epv4 |
uuid1 | uuidD | uuid3 | epv4 |
uuid1 | uuidE | uuid3 | epv4 |
uuid2 | uuidB | uuid7 | epv3 |
uuid2 | uuidC | uuid7 | epv3 |
以下步骤描述了服务器的运行时库执行的操作,如上表中所示,当具有接口 UUID uuid2 和对象 UUID uuidC 的客户端调用它时。
服务器调用 RpcServerRegisterIf、 RpcServerRegisterIfEx 或 RpcServerRegisterIf2 ,以将其提供的接口与不同的管理器EPV相关联。 接口注册表表中的条目反映 RpcServerRegisterIf、 RpcServerRegisterIfEx 或 RpcServerRegisterIf2 的四个调用,以提供两个接口,每个接口的两个实现 (EPV) 。
服务器调用 RpcObjectSetType 来建立它提供的每个对象的类型。 除了 nil 对象与 nil 类型的默认关联外,在对象注册表表中未显式找到的所有其他对象 UUID 也映射到 nil 类型 UUID。
在此示例中,服务器调用 RpcObjectSetType 例程六次。
服务器运行时库接收远程过程调用,其中包含调用所属的接口 UUID 和调用的绑定句柄中的对象 UUID。
使用远程过程调用中的接口 UUID,服务器的运行时库在接口注册表表中查找接口 UUID。
使用绑定句柄中的 uuidC 对象 UUID,服务器的运行时库在对象注册表表中查找对象 UUID,并发现它映射到 uuid7 类型。
为了查找管理器类型,服务器的运行时库在接口注册表表中组合了接口 UUID、 uuid2 和类型 uuid7 。 这解析为 epv3,其中包含要为远程过程调用执行的服务器管理器例程。
epv2 中的例程永远不会执行,因为服务器未调用 RpcObjectSetType 例程,将 UUID 类型为 uuid4 的任何对象添加到对象注册表表。
使用接口 UUID uuid2 和对象 UUID uuidF 的远程过程调用将返回到具有RPC_S_UNKNOWN_MGR_TYPE状态代码的调用方,因为服务器未调用 RpcServerRegisterIf、 RpcServerRegisterIfEx 或 RpcServerRegisterIf2 以将接口注册到 管理器类型的 uuid8。
返回值
此函数返回以下值之一。
值 | 含义 |
---|---|
RPC_S_OK | 成功 |
RPC_S_TYPE_ALREADY_REGISTERED | 键入 UUID 已注册 |
提供自己的对象查询函数
假设有一个服务器,该服务器管理数千个不同类型的对象。 每当服务器启动时,服务器应用程序必须为每个对象调用函数 RpcObjectSetType ,即使客户端可能仅引用其中几个对象 (或花费很长时间才能) 引用它们。 这些数千个对象可能位于磁盘上,因此检索其类型将非常耗时。 此外,将对象 UUID 映射到管理器类型 UUID 的内部表实质上会复制用对象本身维护的映射。
为方便起见,RPC 函数集包括函数 RpcObjectSetInqFn。 使用此函数,可以提供自己的对象查询函数。
例如,在将对象 100-199 映射到类型 1、将 200–299 映射到类型 2 等时,可以提供自己的对象查询函数。 对象查询函数也可以扩展到分布式文件系统,其中服务器应用程序没有所有文件列表 (对象 UUID) 可用,或者当对象 UUID 在文件系统中命名文件时,并且你不希望预加载对象 UUID 与类型 UUID 之间的所有映射。
相关主题