我們從一個(gè)簡(jiǎn)單的 RPC “Hello, world!”的例子開(kāi)始。
參考資料:MSDN: Win32 and COM Development -> Networking -> Network Protocols -> Remote Procedure Calls (RPC)
第1步:編寫(xiě) IDL(Interface Description Language,接口描述語(yǔ)言)文件
-------------------------------------------------------------------------
IDL 是一個(gè)通用的工業(yè)標(biāo)準(zhǔn)語(yǔ)言,大家應(yīng)該不陌生,因?yàn)?COM 里面也是用它來(lái)描述接口的。
Hello.idl:
[
uuid("4556509F-618A-46CF-AB3D-ED736ED66477"), // 唯一的UUID,用 GUIDGen 生成
version(1.0)
]
interface HelloWorld
{
// 我們定義的方法
void Hello([in,string]const char * psz);
void Shutdown(void);
}
一個(gè)可選的文件是應(yīng)用程序配置文件(.acf),它的作用是對(duì) RPC 接口進(jìn)行配置,例如下面的 Hello.acf 文件:
Hello.acf:
[
implicit_handle(handle_t HelloWorld_Binding)
]
interface HelloWorld
{
}
上面定義了 implicit_handle,這樣客戶端將綁定句柄 HelloWorld_Binding 了,后面的客戶端代碼中我們會(huì)看到。
編譯 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
我們可以看到自動(dòng)生成了 Hello.h, Hello_s.c, Hello_c.c 文件,這些叫做 rpc stub 程序,不過(guò)我們可以不管這個(gè)概念,
我們只需要知道 Hello.h 里面定義了一個(gè)
extern RPC_IF_HANDLE HelloWorld_v1_0_s_ifspec;
這個(gè) RPC_IF_HANDLE 將在后面用到。
第2步:編寫(xiě)服務(wù)端程序
-------------------------------------------------------------------------
第1步中我們已經(jīng)約定了調(diào)用的接口,那么現(xiàn)在我們開(kāi)始實(shí)現(xiàn)其服務(wù)端。代碼如下:
server.c
#include <stdlib.h>
#include <stdio.h>
#include "Hello.h" // 引用MIDL 生成的頭文件
/**
* 這是我們?cè)贗DL 中定義的接口方法
* 需要注意一點(diǎn),IDL 里面的聲明是:void Hello([in,string]const char * psz);
* 但是這里變成了const unsigned char *,為什么呢?
* 參見(jiàn)MSDN 中的MIDL Command-Line Reference -> /char Switch
* 默認(rèn)的編譯選項(xiàng),對(duì) IDL 中的char 按照unsigned char 處理
*/
void Hello(const unsigned char * psz)
{
printf("%s\n", psz);
}
/** 這也是我們?cè)贗DL 中定義的接口方法,提供關(guān)閉server 的機(jī)制*/
void Shutdown(void)
{
// 下面的操作將導(dǎo)致 RpcServerListen() 退出
RpcMgmtStopServerListening(NULL);
RpcServerUnregisterIf(NULL, NULL, FALSE);
}
int main(int argc,char * argv[])
{
// 用Named Pipe 作為RPC 的通道,這樣EndPoint 參數(shù)就是Named Pipe 的名字
// 按照Named Pipe 的命名規(guī)范,\pipe\pipename,其中pipename 可以是除了\
// 之外的任意字符,那么這里用一個(gè)GUID 串來(lái)命名,可以保證不會(huì)重復(fù)
RpcServerUseProtseqEp((unsigned char *)"ncacn_np", 20, (unsigned char *)"\\pipe\\{8dd50205-3108-498f-96e8-dbc4ec074cf9}", NULL);
// 注冊(cè)接口,HelloWorld_v1_0_s_ifspec 是在MIDL 生成的Hello.h 中定義的
RpcServerRegisterIf(HelloWorld_v1_0_s_ifspec, NULL, NULL);
// 開(kāi)始監(jiān)聽(tīng),本函數(shù)將一直阻塞
RpcServerListen(1,20,FALSE);
return 0;
}
// 下面的函數(shù)是為了滿足鏈接需要而寫(xiě)的,沒(méi)有的話會(huì)出現(xiàn)鏈接錯(cuò)誤
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++ 優(yōu)化編譯器 14.00.50727.42 版
版權(quán)所有(C) Microsoft Corporation。保留所有權(quán)利。
server.c
Hello_s.c
正在生成代碼...
Microsoft (R) Incremental Linker Version 8.00.50727.42
Copyright (C) Microsoft Corporation. All rights reserved.
/out:server.exe
server.obj
Hello_s.obj
rpcrt4.lib
編譯時(shí)為什么要指定 _WIN32_WINNT=0x500 呢?因?yàn)槿绻麤](méi)有的話會(huì)報(bào)告下面的錯(cuò)誤:
Hello_s.c(88) : fatal error C1189: #error : You need a Windows 2000 or later to
run this stub because it uses these features:
第3步:編寫(xiě)客戶端程序
-------------------------------------------------------------------------
客戶端的代碼:
client.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "Hello.h" // 引用MIDL 生成的頭文件
int main(int argc, char * argv[])
{
unsigned char * pszStringBinding = NULL;
if ( argc != 2 )
{
printf("Usage:%s <Hello Text>\n", argv[0]);
return 1;
}
// 用Named Pipe 作為RPC 的通道。參見(jiàn)server.c 中的RpcServerUseProtseqEp() 部分
// 第3 個(gè)參數(shù)NetworkAddr 如果取NULL,那么就是連接本機(jī)服務(wù)
// 否則要取\\\\servername 這樣的格式,例如你的計(jì)算機(jī)名為jack,那么就是\\jack
RpcStringBindingCompose( NULL, (unsigned char*)"ncacn_np", /*(unsigned char*)"\\\\servername"*/ NULL, (unsigned char*)"\\pipe\\{8dd50205-3108-498f-96e8-dbc4ec074cf9}", NULL, &pszStringBinding );
// 綁定接口,這里要和 Hello.acf 的配置一致,那么就是HelloWorld_Binding
RpcBindingFromStringBinding(pszStringBinding, & HelloWorld_Binding );
// 下面是調(diào)用服務(wù)端的函數(shù)了
RpcTryExcept
{
if ( _stricmp(argv[1], "SHUTDOWN") == 0 )
{
Shutdown();
}
else
{
Hello((unsigned char*)argv[1]);
}
}
RpcExcept(1)
{
printf( "RPC Exception %d\n", RpcExceptionCode() );
}
RpcEndExcept
// 釋放資源
RpcStringFree(&pszStringBinding);
RpcBindingFree(&HelloWorld_Binding);
return 0;
}
// 下面的函數(shù)是為了滿足鏈接需要而寫(xiě)的,沒(méi)有的話會(huì)出現(xiàn)鏈接錯(cuò)誤
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 client.c Hello_c.c rpcrt4.lib
用于 80x86 的 Microsoft (R) 32 位 C/C++ 優(yōu)化編譯器 14.00.50727.42 版
版權(quán)所有(C) Microsoft Corporation。保留所有權(quán)利。
client.c
Hello_c.c
正在生成代碼...
Microsoft (R) Incremental Linker Version 8.00.50727.42
Copyright (C) Microsoft Corporation. All rights reserved.
/out:client.exe
client.obj
Hello_c.obj
rpcrt4.lib
第4步:測(cè)試:
-------------------------------------------------------------------------
運(yùn)行 server.exe,將彈出一個(gè) console 窗口,等待客戶端調(diào)用。
運(yùn)行客戶端 client.exe:
>client hello
可以看到 server.exe 的 console 窗口出現(xiàn) hello 的字符串。
>client shutdown
server.exe 退出。
示例下載