Windows RPC编程详解
分类: Window内核编程 2010-05-18 23:10 6469人阅读 评论(2) 收藏 举报 windows编程microsoftnullbindinginterface 目录(?)[+] 一、什么是远程过程调用
什么是远程过程调用 RPC(Remote Procedure Call)? 你可能对这个概念有点陌生, 而你可能非常熟悉 NFS, 是的,
NFS 就是基于 RPC 的. 为了理解远程过程调用,我们先来看一下过程调用。
所谓过程调用,就是将控制从一个过程 A 传递到另一个过程 B, 返回时过程 B 将控制进程交给过程 A。目前大多数系统
中, 调用者和被调用者都在给定主机系统中的一个进程中, 它们是在生成可执行文件时由链接器连接起来的, 这类过程调用称 为本地过程调用。
远程过程调用(RPC)指的是由本地系统上的进程激活远程系统上的进程, 我们将此称为过程调用是因为它对程序员来说表现 为常规过程调用。处理远程过程调用的进程有两个, 一个是本地客户进程, 一个是远程服务器进程。对本地进程来说, 远程过
程调用表现这对客户进程的控制, 然后由客户进程生成一个消息, 通过网络系统调用发往远程服务器。网络信息中包括过程调
用所需要的参数, 远程服务器接到消息后调用相应过程, 然后将结果通过网络发回客户进程, 再由客户进程将结果返回给调用 进程。因此, 远程系统调用对调用者表现为本地过程调用, 但实际上是调用了远程系统上的过程。
二、远程过程调用模型
本地过程调用: 一个传统程序由一个或多个过程组成。它们往往按照一种调用等级来安排。如下图所示:
远程过程调用: 使用了和传统过程一样的抽象, 只是它允许一个过程的边界跨越两台计算机。如下图所示:
三、远程过程和本地过程的对比
首先, 网络延时会使一个远程过程的开销远远比本地过程要大
其次, 传统的过程调用因为被调用过程和调用过程运行在同一块内存空间上, 可以在过程间传递指针。而远程过程不能够将
指针作为参数, 因为远程过程与调用者运行在完全不同的地址空间中。
再次, 因为一个远程调用不能共享调用者的环境, 所以它就无法直接访问调用者的 I/O 描述符或操作系统功能。
四、远程过程调用的几种版本 (1) Sun RPC (UDP, TCP) (2) Xerox Courier (SPP) (3) Apollo RPC (UDP, DDS)
其中 Sun RPC 可用于面向连接或非面向连接的协议; Xerox Courier 仅用于面向连接的协议; Apollo RPC 仅用于非连接的协议
五、如何编写远程过程调用程序
为了将一个传统的程序改写成 RPC 程序, 我们要在程序里加入另外一些代码, 这个过程称作 stub 过程。我们可以想象一
个传统程序, 它的一个过程被转移到一个远程机器中。在远程过程一端, stub 过程取代了调用者。这样 stub 实现了远程过 程调用所需要的所有通信。因为 stub 与原来的调用使用了一样的接口, 因此增加这些 stub 过程既不需要更改原来的调用过
程, 也不要求更改原来的被调用过程。如下图所示:
Win32 RPC 编程(一)
我们从一个简单的 RPC “Hello, world!”的例子开始。
参考资料:MSDN: Win32 and COM Development -> Networking -> Network Protocols -> Remote Procedure Calls (RPC)
第1步:编写 IDL(Interface Description Language,接口描述语言)文件 -------------------------------------------------------------------------
IDL 是一个通用的工业标准语言,大家应该不陌生,因为 COM 里面也是用它来描述接口的。 Hello.idl: [
uuid(\唯一的UUID,用 GUIDGen 生成 version(1.0) ]
interface HelloWorld {
// 我们定义的方法
void Hello([in,string]const char * psz); void Shutdown(void); }
一个可选的文件是应用程序配置文件(.acf),它的作用是对 RPC 接口进行配置,例如下面的 Hello.acf 文件: Hello.acf: [
implicit_handle(handle_t HelloWorld_Binding) ]
interface HelloWorld { }
上面定义了 implicit_handle,这样客户端将绑定句柄 HelloWorld_Binding 了,后面的客户端代码中我们会看到。
编译 IDL 文件:
>midl Hello.idl
Microsoft (R) 32b/64b MIDL Compiler Version 6.00.0366
Copyright (c) Microsoft Corporation 1991-2002. All rights reserved. Processing ./Hello.idl Hello.idl
Processing ./Hello.acf Hello.acf
我们可以看到自动生成了 Hello.h, Hello_s.c, Hello_c.c 文件,这些叫做 rpc stub 程序,不过我们可以不管这个概念, 我们只需要知道 Hello.h 里面定义了一个
extern RPC_IF_HANDLE HelloWorld_v1_0_s_ifspec;
这个 RPC_IF_HANDLE 将在后面用到。
第2步:编写服务端程序
-------------------------------------------------------------------------
第1步中我们已经约定了调用的接口,那么现在我们开始实现其服务端。代码如下:
server.c
#include
#include \引用MIDL 生成的头文件 /**
* 这是我们在IDL 中定义的接口方法
* 需要注意一点,IDL 里面的声明是:void Hello([in,string]const char * psz);
* 但是这里变成了const unsigned char *,为什么呢?
* 参见MSDN 中的MIDL Command-Line Reference -> /char Switch * 默认的编译选项,对 IDL 中的char 按照unsigned char 处理 */
void Hello(const unsigned char * psz) {
printf(\}
/** 这也是我们在IDL 中定义的接口方法,提供关闭server 的机制*/ void Shutdown(void) {
// 下面的操作将导致 RpcServerListen() 退出 RpcMgmtStopServerListening(NULL);
RpcServerUnregisterIf(NULL, NULL, FALSE); }
int main(int argc,char * argv[]) {
// 用Named Pipe 作为RPC 的通道,这样EndPoint 参数就是Named Pipe 的名字
// 按照Named Pipe 的命名规范,/pipe/pipename,其中pipename 可以是除了/
// 之外的任意字符,那么这里用一个GUID 串来命名,可以保证不会重复 RpcServerUseProtseqEp((unsigned char *)\char *)\
// 注册接口,HelloWorld_v1_0_s_ifspec 是在MIDL 生成的Hello.h 中定义的
RpcServerRegisterIf(HelloWorld_v1_0_s_ifspec, NULL, NULL);
// 开始监听,本函数将一直阻塞 RpcServerListen(1,20,FALSE); return 0; }
// 下面的函数是为了满足链接需要而写的,没有的话会出现链接错误 void __RPC_FAR* __RPC_USER midl_user_allocate(size_t len) {
return(malloc(len)); }
void __RPC_USER midl_user_free(void __RPC_FAR *ptr) {
free(ptr); }
编译:
>cl /D_WIN32_WINNT=0x500 server.c Hello_s.c rpcrt4.lib
用于 80x86 的 Microsoft (R) 32 位 C/C++ 优化编译器 14.00.50727.42 版 版权所有(C) Microsoft Corporation。保留所有权利。