1、創建IDL文件,定義接口。
IDL文件可以由uuidgen.exe創建。
首先找到系統中uuidgen.exe的位置,如:C:\Program Files\Microsoft Visual Studio 9.0\Common7\Tools。在此目錄下運行命令"uuidgen
/i /ohello.idl",即可在該位置生成一個IDL文件:hello.idl。文件內容如下:
//hello.idl
[
uuid(b2617491-ba5a-48a9-b388-9f0cee8ec882),
version(1.0)
]
interface INTERFACENAME
{
}
然後,增加接口。如下:
//hello.idl
[
uuid(b2617491-ba5a-48a9-b388-9f0cee8ec882),
version(1.0)
]
interface INTERFACENAME
{
void HelloProc([in,string]unsigned char* szhello);
void ShutDown(void);
}
2、創建acf文件。
hello.acf文件內容如下:
//hello.acf
[
implicit_handle (handle_t hello_IfHandle)
]
interface INTERFACENAME
{
}
注意: 1)hello.idl文件與hello.acf文件中的接口名稱(INTERFACENAME)應一致,否則接下來編譯的時候會報錯。
2)hello.idl文件與hello.acf文件應放在同一目錄下。
3、編譯IDL文件。
有資料說可以用"midl hello.idl"命令直接進行編譯,但是我試過之後,總是提示MIDL1005 的錯誤,沒辦法,用vs2008進行編譯的,步驟如下
。
首先,創建一個空的項目,如RpcTest將編輯好的hello.idl文件添加至RpcTest項目中。
然後,直接進行編譯。
這時就可以看到RpcTest項目的生成目錄下有了hello_h.h, hello_c.c, hello_s.c三個文件。其中,hello_h.h文件是客戶端和服務器端程序共
同要用到的,hello_c.c是客戶端程序需要的,hello_s.c是服務器程序所需要的。
在hello_h.h文件中可以看到hello.idl中所定義的接口實體,一個全局句柄變量(handle_t)以及客戶端與服務端的接口句柄名
INTERFACENAME_v1_0_c_ifspec和INTERFACENAME_v1_0_s_ifspec。客戶端、服務端應用程序在實時調用將使用接口句柄名。
:
/* interface INTERFACENAME */
/* [implicit_handle][version][uuid] */
void HelloProc(/* [string][in] */ unsigned char *szhello);
void ShutDown( void);
extern handle_t hello_IfHandle;
extern RPC_IF_HANDLE INTERFACENAME_v1_0_c_ifspec;
extern RPC_IF_HANDLE INTERFACENAME_v1_0_s_ifspec;
4、編寫服務器程序。
服務端通過調用RPC實現函數RpcServerUseProtseqEp 與RpcServrRegisterIf捆綁信息並提供給客戶端,例子程序傳遞接口句柄名給
RpcServerRegisterIf,其它的參數被置爲空,客戶端然後調用RpcServerListen函數等待客戶端的請求。
服務端應用程序必須包含兩個內存管理函數midl_user_allocate與midl_user_free。當遠端過程調用向服務端傳遞參數時,調用這兩個函數分
配及釋放內存。
除此之外,服務端還應實現具體的接口函數功能。詳細代碼如下。
//server.cpp
#include <iostream>
using namespace std;
#include "hello_h.h"
int main(void)
{
RPC_STATUS status = 0;
unsigned int mincall = 1;
unsigned int maxcall = 20;
status = RpcServerUseProtseqEp(
(unsigned char *)"ncacn_np",
maxcall,
(unsigned char *)"\\pipe\\hello",
NULL);
if(status != 0){
cout<<"RpcServerUseProtseqEp returns: "<<status<<endl;
return -1;
}
status = RpcServerRegisterIf(
INTERFACENAME_v1_0_s_ifspec,
NULL,
NULL);
if(status != 0){
cout<<"RpcServerRegisterIf returns: "<<status<<endl;
return -1;
}
cout<<"Rpc Server Begin Listening..."<<endl;
status = RpcServerListen(mincall, maxcall, FALSE);
if(status != 0){
cout<<"RpcServerListen returns: "<<status<<endl;
return -1;
}
cin.get();
return 0;
}
/************************************************************************/
/* MIDL malloc & free */
/************************************************************************/
void * __RPC_USER MIDL_user_allocate(size_t len)
{
return (malloc(len));
}
void __RPC_USER MIDL_user_free(void*ptr)
{
free(ptr);
}
/************************************************************************/
/* Interfaces */
/************************************************************************/
void HelloProc(unsigned char *szhello)
{
cout<<szhello<<endl;
}
void ShutDown(void)
{
RPC_STATUS status = 0;
status = RpcMgmtStopServerListening(NULL);
if(status != 0){
cout<<"RpcMgmtStopServerListening returns: "<<status<<"!"<<endl;
}
status = RpcServerUnregisterIf(NULL, NULL, FALSE);
if(status != 0){
cout<<"RpcServerUnregisterIf returns: "<<status<<"!"<<endl;
}
}
5、編譯服務端程序。
再次利用剛纔的空項目RpcTest。
1)首先將剛剛加入的hello.idl文件從項目中移除。
2)然後加入hello_h.h, hello_s.c, server.cpp三個文件。
3)爲項目加入rpc庫文件:rpcrt4.lib。
4)編譯生成RpcTest.exe,更名爲server.exe。
6、編寫客戶端程序。
hello_c.c 源文件中定義了hello_h.h,它由MIDL生成,在它內部又預定義了rpc.h與rncndr.h它們包含了客戶端、服務端應用程序所使用的實時
程序及數據類型,客戶端管理着它到服務端的連接,客戶端應用程序調用實時函數建立用來連接服務端的句柄,當遠端過程調用完成時再釋放
它。RpcStringBindingCompose 把代表句柄和爲字符串綁定而配置內存的成份組裝成字符串。RpcBindingFromStringBinding 根據上一個字符
串爲客戶端應用程序創建一個服務端綁定句柄。接口端點的指定,方法很多,最終方式取決於使用的協議,例子中使用的是Named pipes,它使
用的IDL字符串是“ncacn_np”,則終點名稱就填寫”\\pipes\\idlfilename”。
RPC異常處理通過一整套宏處理可以使你控制外部應用程序代碼出錯引起的異常現象,如有發生,將會調用RpcExcept模塊,在這裏你需要清除
內存並安全退出。遠端過程調用結束後,客戶端首先調用RpcStringFree函數,釋放設置字符串捆綁的內存,然後調用RpcBindgFree()去釋放句
柄。
詳細代碼如下。
//client.cpp
#include <iostream>
#include <string>
using namespace std;
#include "hello_h.h"
void doRpcCall();
int main(int argc, char** argv)
{
int i = 0;
RPC_STATUS status = 0;
unsigned char * pszNetworkAddr = NULL;
unsigned char * pszStringBinding = NULL;
for(i = 1; i < argc; i++){
if(strcmp(argv[i], "-ip") == 0){
pszNetworkAddr = (unsigned char*)argv[++i];
break;
}
}
status = RpcStringBindingCompose(NULL,
(unsigned char *) "ncacn_np",
pszNetworkAddr,
(unsigned char *)"\\pipe\\hello",
NULL,
&pszStringBinding);
if(status != 0){
cout<<"RpcStringBindingCompose returns: "<<status<<"!"<<endl;
return -1;
}
cout<<"pszStringBinding = "<<pszStringBinding<<endl;
status = RpcBindingFromStringBinding(pszStringBinding, &hello_IfHandle);
if(status != 0){
cout<<"RpcBindingFromStringBinding returns: "<<status<<"!"<<endl;
return -1;
}
doRpcCall();
status = RpcStringFree(&pszStringBinding);
if(status != 0)
cout<<"RpcStringFree returns: "<<status<<"!"<<endl;
status = RpcBindingFree(&hello_IfHandle);
if(status != 0)
cout<<"RpcBindingFree returns: "<<status<<"!"<<endl;
cin.get();
return 0;
}
void doRpcCall(void)
{
char buff[1024];
RpcTryExcept{
while(true){
cout<<"Please input a string param for Rpc call:"<<endl;
cin.getline(buff, 1023);
if(strcmp(buff, "exit") == 0 || strcmp(buff, "quit") == 0){
ShutDown();
}
else{
HelloProc((unsigned char*)buff);
cout<<"call helloproc succeed!"<<endl;
}
}
}
RpcExcept(1){
unsigned long ulCode = RpcExceptionCode();
cout<<"RPC exception occured! code: "<<ulCode<<endl;
}
RpcEndExcept
}
void * __RPC_USER MIDL_user_allocate(size_t len)
{
return (malloc(len));
}
void __RPC_USER MIDL_user_free(void* ptr)
{
free(ptr);
}
7、編譯客戶端程序。
再次利用剛纔的空項目RpcTest。
1)首先將剛剛加入的hello_h.h等文件從項目中全部移除。
2)然後加入hello_h.h, hello_c.c, client.cpp三個文件。
3)爲項目加入rpc庫文件:rpcrt4.lib。
4)編譯生成RpcTest.exe,更名爲client.exe。
8、大功告成。
OK,到現在,已經有了客戶端、服務端應用程序的可執行文件。
1)首先運行server.exe。
2)而後,在client.exe所在的目錄下用命令行"client.exe -ip 192.168.1.146"來啓動客戶端程序並與服務器端相連。
3)在client的窗口內輸入任意字符串,回車後可看到server窗口上有顯示。
4)在client窗口內輸入exit或quit,server窗口關閉。