http://msdn.microsoft.com/en-us/library/windows/desktop/aa379010(v=vs.85).aspx
注意:原文版本较老,我更新和改变了部分内容。并提供了完整的程序。编译环境SDK 7.0 WinXP VS2010。
RPC官方教程
此手册可使你从已经存在的单独程序,一步步地创建简单的、单客户端、单服务端的分布式程序。步骤如下:
- 设计接口和创建程序配制文件。
- 使用MIDL编译器来创建C语言客户端桩、服务端桩及相关头文件。
- 写客户端程序。它负责管理链接服务端。
- 写一个包含远程过程的服务端。
- 编译并链接RPC动态库来运行这些分布式程序。
客户端程序通过远程过程调用传递一个字符串给服务端,服务端输出字符串“Hello,World”到命令行控制台中。完成的源码在SDK/RPC/Hello目录中。
此文内容分类如下:
- 单独程序
- 定义接口
- 生成UUID
- IDL文件
- ACF文件
- 生成Stub文件
- 客户端程序
- 服务端程序
单独程序
此脱单独程序由一个函数组成。此程序是我们的分布式程序的样板。函数HelloProc定义在单独的源文件中,这个它可以单独程序或分布式程序中复用它。
- /* file hellop.c */
- #include <stdio.h>
- #include <windows.h>
- void HelloProc(char * pszString)
- {
- printf("%s\n", pszString);
- }
- /* file: hello.c, a stand-alone application */
- #include "hellop.c"
- void main(void)
- {
- char * pszString = "Hello, World";
- HelloProc(pszString);
- }
定义接口
接口定义一个标准规范,定义了客户端与服务端是如何通信的。此接口定义了如何识别对方,客户端程序如何远程调用及如何处理远程调用的参数和返回值。它也指定了数据的传输方式。你以MIDL(Microsoft Interface Definition Language)方式定义的接口包含了C风格的参数,并使用关键字定义这些参数的属性。这些属性描述了数据如何在网络上传送。接口定义(IDL)文件包含了类型定义,属性定义和函数原型定义,这些用于描述数据如何在网络中传输。ACF文件包含配置程序的属性,但不影响网络。
生成UUID
第一步是使用uuidgen工作生成一个UUID。使用UUID客户端和服务端就可以彼此辨别。Uuidgen工具(uuidgen.exe)在安装SDK时就已经自动安装了。
下面的命令创建了一个UUID和Hello.idl临时文件。
uuidgen /i /ohello.idl
你的hello.idl文件内容可能是这样(当然UUID会不同)
- [
- uuid(7a98c250-6808-11cf-b73b-00aa00b677a7),
- version(1.0)
- ]
- interface INTERFACENAME
- {
- }
DIL文件
IDL文件由一个或多个接口定义组成,每一个接口定义都有一个接口头和一个接口体。接口头包含了使用此接口的信息,如UUID。这此信息封装一对中括号之内。之后是interface关键和接口名。接口体包含了C风格的数据类型和函数原型和带属性的参数。这此参数的属性描述了数据如何在网络上传送。此例中,定义头中包含了UUID和版本号。版本号作兼容之用。客户端、服务端的版本只有兼容了,才可以连接。接口体包含了HelloProc函数原型。函数的参数pszString有两个属性[in]和[string]。[in]函数告诉运行库此参数只能从客户端传送到服务端。[string]属性指定了桩要按C风格的字符串来处理此参数。客户端程序可以关闭服务端程序,所以此接口包含了另一个函数Shutdown。
- //file hello.idl
- [
- uuid(7a98c250-6808-11cf-b73b-00aa00b677a7),
- version(1.0)
- ]
- interface hello
- {
- void HelloProc([in, string] unsigned char * pszString);
- void Shutdown(void);
- }
ACF文件
ACF文件使你自定义你的客户端或服务端的RPC接口,但不影响网络特征。例如,如果你的客户端程序包含了一个复杂的数据结构,此数据结构只在本地机上有意义,那么你就可以在ACF文件中指定如何描述独立与机器的数据结构,使用数据结构用于远程过程调用。此教程演示了一个ACF文件 ---指定一个绑定的handle类型,用来代表客户端与服务端的连接。[implicit_handle]属性允许客户端程序为它的远程过程调用选择一个服务端。ACF定义了此句柄为handle_t类型(MIDL基本数据类型)。MIDL编译器将绑定ACF文件指定的句柄名字hello_IfHandle,放在生成的头文件中。注意此ACF文件的体为空。
- //file: hello.acf
- [
- implicit_handle (handle_t hello_IfHandle)
- ]
- interface hello
- {
- }
MIDL编译器有一个选项 /app_config。此选项可使你包含某一个ACF属性,如implicit_handle。在IDL文件中,而不是创建一个ACF文件。如果你的程序不需要指定大量配置和不考虑OSF兼容,则可以考虑使用此选项。
生成Stub文件
在定义了客户端/服务端接口之后,你将编写客户端和服务端源码。之后使用一个makefile文件生成桩和头文件。再编译链接客户端/服务端程序。 但是,如果你第一次探究分布式计算环境。你可能想调用MIDL编译器,来查看MIDL产生了什么文件。 MIDL编译器(Midl.exe)在安装SDK时已自动安装。当你编译这些文件时,确保Hello.idl 和 Hello.acf在同一文件夹中。下面的命令将生成Hello.h头文件和客户端、服务端桩: Hello_c.c and Hello_s.c.
midl hello.idl
你将在服务端程序中提供这两个函数。如果数据从服务端传送到客户端(要使用一个[out]参数),你也要在客户端提供两个内存管理函数。全局变量,hello_IfHandle,和客户端、服务端接口名字:hello_v1_0_c_ifspec 和 hello_v1_0_s_ifspec. 客户端和服务端程序将在运行时使用这些接口句柄。你仅使用桩文件Hello_c.c and hello_s.c就可以了。
- /* this ALWAYS GENERATED file contains the definitions for the interfaces */
- /* File created by MIDL compiler version 7.00.0555 */
- /* at Sun Dec 01 09:32:11 2013
- */
- /* Compiler settings for hello.idl:
- Oicf, W1, Zp8, env=Win32 (32b run), target_arch=X86 7.00.0555
- protocol : dce , ms_ext, c_ext, robust
- error checks: allocation ref bounds_check enum stub_data
- VC __declspec() decoration level:
- __declspec(uuid()), __declspec(selectany), __declspec(novtable)
- DECLSPEC_UUID(), MIDL_INTERFACE()
- */
- /* @@MIDL_FILE_HEADING( ) */
- #pragma warning( disable: 4049 ) /* more than 64k source lines */
- /* verify that the <rpcndr.h> version is high enough to compile this file*/
- #ifndef __REQUIRED_RPCNDR_H_VERSION__
- #define __REQUIRED_RPCNDR_H_VERSION__ 475
- #endif
- #include "rpc.h"
- #include "rpcndr.h"
- #ifndef __RPCNDR_H_VERSION__
- #error this stub requires an updated version of <rpcndr.h>
- #endif // __RPCNDR_H_VERSION__
- #ifndef __hello_h__
- #define __hello_h__
- #if defined(_MSC_VER) && (_MSC_VER >= 1020)
- #pragma once
- #endif
- /* Forward Declarations */
- #ifdef __cplusplus
- extern "C"{
- #endif
- #ifndef __hello_INTERFACE_DEFINED__
- #define __hello_INTERFACE_DEFINED__
- /* interface hello */
- /* [implicit_handle][version][uuid] */
- void HelloProc(
- /* [string][in] */ unsigned char *pszString);
- void Shutdown( void);
- extern handle_t hello_IfHandle;
- extern RPC_IF_HANDLE hello_v1_0_c_ifspec;
- extern RPC_IF_HANDLE hello_v1_0_s_ifspec;
- #endif /* __hello_INTERFACE_DEFINED__ */
- /* Additional Prototypes for ALL interfaces */
- /* end of Additional Prototypes */
- #ifdef __cplusplus
- }
- #endif
- #endif
客户端程序
此例在SDK下RPC目录下Hello目录中。 Hello.c源文件包含了MIDL生成头文件Hello.h。在Hello.h中包含了Rpc.h和rpcndr.h,还有HelloProc和Shutdown,及客户端、服务端使用的数据类型。在此例中必须使用MIDL。.因为此客户端管理与服务端的连接。客户端程序调用运行时函数来建立一个指向服务端的句柄,在远程过程调用完成后,释放这个句柄。函数RpcStringBindingCompose组合此函数的参数生成一个以字符串表示的绑定句柄(binding handle)并为此字符串分配内存。
函数RpcBindingFromStringBinding从字符串表示的绑定句柄,创建了一个服务绑定句柄,hello_ClientIfHandle。调用函数RpcStringBindingCompose,它的参数并不是UUID。因为此教程假定只有一个接口”hello”的实现。另外,此调用没有指定网络地址。因为此程序使用默认机制:本地调用机制。Protocol sequence是一个字符串,表示了具体网络传送。
Endpoint指定了具体的protocol sequence名称。此例使用了命名管道为网络传输方式,所以protocol sequence为”ncacn_np”。Endpoint名字为”\pipe\hello”。
远程过程调用:HelloProc和Shutdown,包含在RPC的异常处理函数中。RPC的异常处理函数是一系列宏,可使程序发生异常时,在代码外处理异常。如果RPC运行模块报告了一个异常,程序就会跳转到RpcExcept代码块中。
在此代码块中,你可以插入代码,来做一些清理工作,然后退出程序。.此例中程序仅简单地知道用户发生了一个异常。如果你不想使用异常,你可以使用ACF属性comm_status和fault_status来报告错误。在远程过程调用完成后,客户端程序首先调用RpcStringFree来释放先前字符串绑定申请的内存。
- /* file: helloc.c */
- #include <stdlib.h>
- #include <stdio.h>
- #include <ctype.h>
- #include "hello.h"
- #include <windows.h>
- void main()
- {
- RPC_STATUS status;
- unsigned char * pszUuid = NULL;
- unsigned char * pszProtocolSequence = "ncacn_np";
- unsigned char * pszNetworkAddress = NULL;
- unsigned char * pszEndpoint = "\\pipe\\hello";
- unsigned char * pszOptions = NULL;
- unsigned char * pszStringBinding = NULL;
- unsigned char * pszString = "hello, server";
- unsigned long ulCode;
- status = RpcStringBindingCompose(pszUuid,
- pszProtocolSequence,
- pszNetworkAddress,
- pszEndpoint,
- pszOptions,
- &pszStringBinding);
- if (status) exit(status);
- //status = RpcBindingFromStringBinding(pszStringBinding, &hello_ClientIfHandle);
- status = RpcBindingFromStringBinding(pszStringBinding, &hello_IfHandle);
- if (status) exit(status);
- RpcTryExcept
- {
- HelloProc(pszString);
- Shutdown();
- }
- RpcExcept(1)
- {
- ulCode = RpcExceptionCode();
- printf("Runtime reported exception 0x%lx = %ld\n", ulCode, ulCode);
- }
- RpcEndExcept
- status = RpcStringFree(&pszStringBinding);
- if (status) exit(status);
- status = RpcBindingFree(&hello_IfHandle);
- if (status) exit(status);
- exit(0);
- }
- /******************************************************/
- /* MIDL allocate and free */
- /******************************************************/
- 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);
- }
此例中必须 使用MIDL。根据你自己的编码风格,你可以一个或多个独立的文件中实现这些远程过程。此教程的程序,Hellos.c源文件包含了主要的服务端框架代码。Hellop.c包含了远程过程。将远程过程单独放在一个单独的文件中有一个好处:在转换成分布程序之前,可以链接它到非分布式程序上调试。在非分布式程序上调通之后,你可以编译链接此文件到分布式服务端。Hello.h头文件为客户端源文件,服务端源码也要包含此头文件。服务端调用RPC运行库函数RpcServerUseProtseqEp和RpcServerRegisterIf来建立有效的客户端。此例中,程序向函数RpcServerRegisterIf传递接口句柄。另一个参数为NULL。之后,服务端调用函数RpcServerListen来等待客户端的请求。
服务端必须包含两个内存分配函数。这两个函数被服务桩程序调用:midl_user_allocate和midl_user_free。这两个函数在服务端分配和释放内存,当远程过程传送参数给服务端时。此例中,midl_user_allocate和midl_user_free简单地封装了C库函数malloc和free。(注意,在MIDL编译器生成的前置声明中,”MIDL”是大小的。头文件Rpcndr.h定义了midl_user_free和midl_user_allocate。这两个函数指向MIDL_user_free 和MIDL_user_allocate。
- /* file: hellos.c */
- #include <stdlib.h>
- #include <stdio.h>
- #include <ctype.h>
- #include "hello.h"
- #include <windows.h>
- handle_t hello_IfHandle;
- void main()
- {
- RPC_STATUS status;
- unsigned char * pszProtocolSequence = "ncacn_np";
- unsigned char * pszSecurity = NULL;
- unsigned char * pszEndpoint = "\\pipe\\hello";
- unsigned int cMinCalls = 1;
- unsigned int fDontWait = FALSE;
- status = RpcServerUseProtseqEp(pszProtocolSequence,
- RPC_C_LISTEN_MAX_CALLS_DEFAULT,
- pszEndpoint,
- pszSecurity);
- if (status) exit(status);
- //RPC_S_INVALID_ENDPOINT_FORMAT
- //status = RpcServerRegisterIf(hello_ServerIfHandle, NULL, NULL);
- //status = RpcServerRegisterIf(hello_IfHandle, NULL, NULL);
- status = RpcServerRegisterIf(hello_v1_0_s_ifspec, NULL, NULL);
- if (status) exit(status);
- status = RpcServerListen(cMinCalls,
- RPC_C_LISTEN_MAX_CALLS_DEFAULT,
- fDontWait);
- if (status) exit(status);
- }
- /******************************************************/
- /* MIDL allocate and free */
- /******************************************************/
- 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);
- }
- e(ptr);
- }
///
参考
- RpcStringBindingCompose function
- The RpcStringBindingCompose function creates a string binding handle.
- Syntax
- C++
- RPC_STATUS RPC_ENTRY RpcStringBindingCompose(
- TCHAR *ObjUuid,
- TCHAR *ProtSeq,
- TCHAR *NetworkAddr,
- TCHAR *EndPoint,
- TCHAR *Options,
- TCHAR **StringBinding
- );
String Binding
The string binding is an unsigned character string composed of strings that represent the binding object UUID, the RPC protocol sequence, the network address, and the endpoint and endpoint options.
ObjectUUID@ProtocolSequence:NetworkAddress[Endpoint,Option]