RPC属于本机或网络IPC机制。
3种进程:32,64,wow64
RPC中,函数调用用多了2种元素:
如图所示,客户端应用程序调用本地stub/存根过程,stub代码不是包含实现远程过程的实际代码,而是:
服务器执行以下步骤来调用远程过程。
远程过程,可能会生成输出参数和返回值,类似的步骤发回给客户端。
客户端通过接受网络上的数据并将其返回给调用函数来完成该过程。
运行时库分为两个部分:
PC包含以下主要组件:
MIDL编译器生成将本地过程调用转换为远程过程调用的存根。存根是占位符函数,用于调用运行时库函数,该库函数管理远程过程调用。
winsdk中包含:
系统则包含:
导入库:rpcns4.lib, rpcrt4.lib
根据平台、编译器和api库可能有所不同。
接口由接口名称,一些属性,可选类型或常量定义以及一组过程声明组成。每个过程声明必须包含一个过程名称,返回类型和参数列表。
接口定义在idl文件中,MIDL编译器生成1个头文件,和两个C源文件作为客户端和服务器stub。
默认情况下,客户端和服务器存根具有相同的名称,如果客户端与服务器存根链接,则可能引起问题,反之亦然。使用MIDL / prefix选项可防止发生此常见错误。
还需要一个应用程序配置文件(ACF),用来输入到MIDL编译器。
Uuidgen生成 通用唯一标识符(UUID,可与术语GUID互换)
网络上的接口必须有唯一标识才能被客户端找到。UUID是十六进制数字字符串,以下示例是有效的UUID字符串:
ba209999-0c6c-11d2-97cf-00c04f8eea45
各成员长度比8,4,4,4,12,即一共32个十六进制数。
空的UUID被称为nil UUID,而不是NULL UUID。
uuidgen -i -oMyApp.idl
注意不要加空格
生成内容:
[
uuid(9315f815-63b0-4852-955d-7fcf0d786f25),
version(1.0)
]
interface INTERFACENAME
{
}
也可以用vs工具里的Guid生成工具。
然后替换INTERFACENAME,完成主体即可.
[
implicit_handle(handle_t bindingHandle)
]
interface MyInterface
{
MyRemoteProc();
}
可以用midl命令行编译,或者vs直接编译
midl MyApp.idl
然后目录下就会生成MyApp_c.c, MyApp_s.c, MyApp_h.h
acf这里msdn上没有,因为后来客户端报错,1722, The RPC server is unavailable.
在ACF文件中定义一个handle类型,用来代表客户端与服务端的连接。
[
implicit_handle(handle_t bindingHandle)
]
interface MyInterface
{
}
midl生成的头文件中,实际上还包含了两个rpc头文件:
#include "rpc.h"
#include "rpcndr.h"
如果不以OSF兼容模式/ osf
编译IDL文件,则服务端和客户端程序必须提供分配内存的功能和取消分配内存的功能。
以下等同try-finally
RpcTryFinally / RpcFinally / RpcEndFinally
以下等同try-except
RpcTryExcept / RpcExcept / RpcEndExcept
vista及更高版本中,建议使用RpcExceptionFilter处理异常。
下图展示了如何将异常从服务器返回到客户端:
协议顺序,Protocol Sequence
终点,endpoint,服务器监听的端口。 服务机上的数据库称为端点映射。
RPC_STATUS status;
status = RpcServerRegisterIf(MyInterface_v1_0_s_ifspec, NULL, NULL);
第一个参数是由midl生成的。
//MyApp_h.h
extern RPC_IF_HANDLE MyInterface_v1_0_c_ifspec;
extern RPC_IF_HANDLE MyInterface_v1_0_s_ifspec;
//MyApp_s.c
RPC_IF_HANDLE MyInterface_v1_0_s_ifspec = (RPC_IF_HANDLE)& MyInterface___RpcServerInterface;
服务器程序在接口中提供同一过程的多个实现时,将使用后两个参数。
RpcServerRegisterIfEx提供了安全回调函数保护接口。
希望在本地接收呼叫的服务器应使用ncalrpc
。接受远程呼叫的服务器应使用ncacn_ip_tcp
。
大多数服务器程序使用网络上所有可用的协议序列。为此,它们调用RpcServerUseProtseq函数。其它还有RpcServerUseAllProtseqs,RpcServerUseProtseqEx,RpcServerUseProtseqEp或RpcServerUseProtseqEpEx
下一步,绑定一个vector指针。用完记着调用RpcBindingVectorFree
目前的代码:
long StartServer()
{
wchar_t wszProtocolSequence[] = { L"ncacn_ip_tcp" };
RPC_STATUS status;
status = RpcServerRegisterIf(MyInterface_v1_0_s_ifspec, NULL, NULL);
status = RpcServerUseProtseqW(RPC_WSTR(L"ncacn_ip_tcp"), RPC_C_PROTSEQ_MAX_REQS_DEFAULT, NULL);
RPC_BINDING_VECTOR *rpcBindingVector;
status = RpcServerInqBindings(&rpcBindingVector);
RpcBindingVectorFree(&rpcBindingVector);
return 0;
}
status = RpcEpRegister(
MyInterface_v1_0_s_ifspec,
rpcBindingVector,
NULL, //本例没有要注册的对象UUID
RPC_CSTR(L"note")); //注释字符串,建议使用
相关函数:RpcServerListen, RpcMgmtStopServerListening, RpcMgmtWaitServerListen
status = RpcServerListen(
1, // 最小线程数
RPC_C_LISTEN_MAX_CALLS_DEFAULT, // 最大线程数
0); //默认DCE行为
以上实际编译时会提示无法解析midl_user_allocate, midl _user_free
,这两个函数在服务端分配和释放内存。
#include "MyApp_h.h"
//#include "../RpcTest/MyApp_h.h"
//#include "../RpcTest/MyApp_s.c"
#pragma comment(lib, "Rpcrt4.lib")
long StartServer();
int main()
{
StartServer();
return 0;
}
long StartServer()
{
wchar_t wszProtocolSequence[] = { L"ncalrpc" };
wchar_t wszEndPoint[] = { L"12888" };
RPC_STATUS status;
//RPC_BINDING_VECTOR *rpcBindingVector;
do
{
status = RpcServerUseProtseqEpW(
RPC_WSTR(wszProtocolSequence),
RPC_C_PROTSEQ_MAX_REQS_DEFAULT,
RPC_WSTR(wszEndPoint),
NULL);
if (RPC_S_OK != status)
break;
status = RpcServerRegisterIf(MyInterface_v1_0_s_ifspec, NULL, NULL);
if (RPC_S_OK != status)
break;
//status = RpcServerInqBindings(&rpcBindingVector);
//if (RPC_S_OK != status)
// break;
//status = RpcEpRegisterW(
// MyInterface_v1_0_s_ifspec,
// rpcBindingVector,
// NULL,
// NULL); //注释字符串,建议使用
//if (RPC_S_OK != status)
// break;
status = RpcServerListen(
1, // 最小线程数
RPC_C_LISTEN_MAX_CALLS_DEFAULT, // 最大线程数
0); //默认DCE行为
if (RPC_S_OK != status)
break;
} while (0);
if (RPC_S_OK != status)
exit(status);
//RpcBindingVectorFree(&rpcBindingVector);
return 0;
}
void MyRemoteProc(
/*[in]*/ int param1,
/*[out]*/ int *outParam)
{
*outParam = param1 + 1;
}
void __RPC_FAR * __RPC_USER midl_user_allocate(size_t cBytes)
{
return((void __RPC_FAR *) malloc(cBytes));
}
void __RPC_USER midl_user_free(void __RPC_FAR * p)
{
free(p);
}
分布式应用程序的客户端程序需要创建一个绑定句柄,该句柄告诉RPC运行时应联系哪个服务器以及应如何联系该服务器。
unsigned short *pszUUID = NULL;
unsigned short *StringBinding;
RPC_BINDING_HANDLE bindingHandle;
RPC_STATUS status;
do
{
status = RpcStringBindingComposeW(
pszUUID,
RPC_WSTR(L"ncacn_ip_tcp"),
NULL,
NULL,
NULL,
&StringBinding);
if (RPC_S_OK != status)
break;
status = RpcBindingFromStringBindingW(StringBinding, &bindingHandle);
if (RPC_S_OK != status)
break;
RpcStringFree(&StringBinding);
} while (0);
然后在RpcTryExcept中调用就行了。
注意,代码中全程没有出现uuid。
#include "MyApp_h.h"
//#include "../RpcTest/MyApp_h.h"
//#include "../RpcTest/MyApp_c.c"
#include
#pragma comment( lib, "Rpcrt4.lib" )
long CallServer();
int main()
{
CallServer();
return 0;
}
long CallServer()
{
wchar_t wszProtocolSequence[] = { L"ncalrpc" };
wchar_t wszEndPoint[] = { L"12888" };
wchar_t *pwszStringBinding = NULL;
//handle_t bindingHandle = 0;
RPC_STATUS status;
do
{
status = RpcStringBindingComposeW(
NULL,
RPC_WSTR(wszProtocolSequence),
NULL, //RPC_WSTR(L"127.0.0.1"),
RPC_WSTR(wszEndPoint),
NULL,
(RPC_WSTR*)&pwszStringBinding);
if (RPC_S_OK != status)
break;
status = RpcBindingFromStringBindingW(RPC_WSTR(pwszStringBinding), &bindingHandle);
if (RPC_S_OK != status)
break;
} while (0);
if (RPC_S_OK != status)
exit(status);
int nInt = 0;
RpcTryExcept
{
MyRemoteProc(1, &nInt);
printf("%d\n", nInt);
}
RpcExcept(1)
{
int a = RpcExceptionCode();
printf("Rpc Exception Code: %d\n", a);
}
RpcEndExcept
RpcStringFreeW((RPC_WSTR*)&pwszStringBinding);
RpcBindingFree(&bindingHandle);
getchar();
return 0;
}
void __RPC_FAR * __RPC_USER midl_user_allocate(size_t cBytes)
{
return((void __RPC_FAR *) malloc(cBytes));
}
void __RPC_USER midl_user_free(void __RPC_FAR * p)
{
free(p);
}