C/C++:Windows编程—Windows RPC 传递自定义数据类型、自定义数据类型数组、指针数组
前言
該篇博文不是講Windows rpc入門的。是筆者在實際使用Windows RPC時 所遇到的問題,以及解決方法。
筆者有這樣的需求,需要從RPC Server獲取大量數(shù)據(jù),而且該數(shù)據(jù)是動態(tài)分配的。故此RPC Client在調(diào)用RPC Server方法是 需要動態(tài)獲取空間。筆者在中文社區(qū)沒有找到相關(guān)資料,最后只有去看官方文檔,下面幾個鏈接是官方文檔。
https://docs.microsoft.com/en-us/windows/desktop/Midl/string 字符串的使用
https://docs.microsoft.com/en-us/windows/desktop/Midl/midl-arrays 數(shù)組的使用
https://docs.microsoft.com/en-us/windows/desktop/rpc/multiple-levels-of-pointers 多級指針使用
場景如下:
筆者需要從RPC Server獲取學(xué)生列表信息,個數(shù)當(dāng)然是不確定的,這肯定是動態(tài)分配的,學(xué)生信息中含有學(xué)生姓名和地址,這個是字符串,字符串的長度同樣是不確定了,這個需要動態(tài)分配。所以這里會在RPC中用到 自定義數(shù)據(jù)類型、自定義數(shù)據(jù)類型數(shù)組、指針數(shù)組。看筆者是怎樣處理的。
// 在C語言中筆者想接受的自定義數(shù)據(jù)類型是 typedef struct _STUDENT {char *m_name; // 或者std::string類型char *m_address; // 或者std::string類型short m_age;short m_score; }STUDENT,*PSTUDENT;// 按照MIDL數(shù)據(jù)類型的寫法,應(yīng)該為下面這樣,但這樣MIDL就無法編譯過! // 是因為 RPC對應(yīng)可變長的字符串(運行期間進(jìn)行分配空間) 是采用的柔性數(shù)組,從生成的頭文件我們也能看出來。 // 柔性數(shù)組成員只能放在結(jié)構(gòu)體最后且只能放置一個柔性數(shù)組成員! typedef struct _STUDENT {[string]char m_name[*];[string]char m_address[*];short m_age;short m_score; }STUDENT,*PSTUDENT;編譯錯誤的圖片,錯誤關(guān)鍵字 error MIDL2055 : field deriving from a conformant array must be the last member of the structure
因為在rpc調(diào)用是沒有std::string類型的,需要使用官方推薦的字符串類型寫法或者使用自定義的字符串
// 官方文檔推薦的自定義字符串寫法(帶長度和大小) typedef struct _MYSTRING { unsigned short size; unsigned short length; [ptr,size_is(size), length_is(length)] char string[*]; } MYSTRING; typedef [ptr] MYSTRING** PPMYSTRING; typedef [ptr] MYSTRING* PMYSTRING;最終編譯器生成的字符串類型如下:
typedef struct _MYSTRING{unsigned short size;unsigned short length;unsigned char string[ 1 ];} MYSTRING;idl文件
說了這么多,下面看下筆者的idl文件。
[ uuid(55ae06b7-1011-4654-a4eb-f3347325203f), version(1.0), pointer_default(unique) ] interface RPCTest {// 在C語言中我們想接受的自定數(shù)據(jù)類型數(shù)據(jù)//typedef struct _STUDENT//{// char *m_name; // 或者std::string類型// char *m_address; // 或者std::string類型// short m_age;// short m_score;//}STUDENT,*PSTUDENT;// 按照MIDL數(shù)據(jù)類型的寫法,應(yīng)該為下面這樣,但這樣MIDL就無法編譯過!// 是因為 RPC對應(yīng)可變長的字符串(運行期間進(jìn)行分配空間) 是采用的柔性數(shù)組,從生成的頭文件我們也能看出來。// 柔性數(shù)組成員只能放在結(jié)構(gòu)體最后且只能放置一個柔性數(shù)組成員!//typedef struct _STUDENT//{// [string]char m_name[*];// [string]char m_address[*];// short m_age;// short m_score;//}STUDENT,*PSTUDENT;// 官方文檔推薦的自定義字符串寫法(帶長度和大小)typedef struct _MYSTRING{ unsigned short size; unsigned short length; [ptr,size_is(size), length_is(length)] char string[*]; } MYSTRING;typedef [ptr] MYSTRING** PPMYSTRING;typedef [ptr] MYSTRING* PMYSTRING;typedef struct _BASETYPEDATA{short m_age;short m_score;}BASETYPEDATA,*PBASETYPEDATA;// 這樣我們把基礎(chǔ)數(shù)據(jù)類型和指針數(shù)據(jù)類型分開,// 由于基礎(chǔ)數(shù)據(jù)類型的長度已經(jīng)固定了,所以我們只需要在RPCServer動態(tài)開辟一個數(shù)組空間即可,并個數(shù)返回給RPCClient// 那么我們的字符串呢?當(dāng)然字符串也是動態(tài)開辟的,RPCServer返回給RPCClient的是一個[str1的地址,str2的地址,str3的地址...]這是一個指針數(shù)組,同樣也將個數(shù)返回// 這個指針數(shù)組的空間 也是有RPCServer開辟的,而且指針數(shù)組的成員 也就是最終的字符串的空間也是由RPCServer開辟的,所以我們參數(shù)是3級指針// 獲取信息列表void rpc_GetMsgList([out]int* pNum,[out,size_is(, *pNum)]PBASETYPEDATA *pBaseTypeData,[out,size_is(, *pNum)]PPMYSTRING *pNameList,[out,size_is(, *pNum)]PPMYSTRING *pAddressList); }這里順帶貼一下 筆者的編譯命令,使用Visual Studio命令提示工具編譯
midl E:\Learn_Note\Code\RPCTest\MIDL\RPCTest.idl /acf E:\Learn_Note\Code\RPCTest\MIDL\RPCTest.ACF /out E:\Learn_Note\Code\RPCTest\MIDL
代碼
RPC Server 代碼
main.cpp
#include <iostream> #include <Windows.h> #include "RPCTest.h" #include "student.h" #include <list>#define ENDPOINT_ADDRESS_NAME TEXT("\\pipe\\RPCTest") using namespace std;void * __RPC_USER MIDL_user_allocate(size_t size) {return(HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS | HEAP_ZERO_MEMORY, size)); }void __RPC_USER MIDL_user_free( void *pointer) {HeapFree(GetProcessHeap(), 0, pointer); }void rpc_GetMsgList( /* [out] */ int *pNum,/* [size_is][size_is][out] */ PBASETYPEDATA *pBaseTypeData,/* [size_is][size_is][out] */ PPMYSTRING *pNameList,/* [size_is][size_is][out] */ PPMYSTRING *pAddressList) {// 具體業(yè)務(wù)邏輯,根據(jù)實際情況申請內(nèi)存空間// 這里模擬10個業(yè)務(wù)數(shù)據(jù),具體業(yè)務(wù)參數(shù)的數(shù)據(jù),需要通過rpc將數(shù)據(jù)返回給客戶端list<STUDENT> students;STUDENT s;char tmp[50] = {0};for(int i = 0; i < 10 ; i++){memset(tmp,0,50);sprintf(tmp,"李四-%d",i);s.m_name = tmp;memset(tmp,0,50);sprintf(tmp,"北京街%d號",i);s.m_address = tmp;s.m_age = i * 10;s.m_score = i*10 + 9;students.push_back(s);}// RPC服務(wù)器 開辟空間,將數(shù)據(jù)返回給RPC客戶端*pNum = students.size();size_t baseLen = *pNum * sizeof(BASETYPEDATA);size_t strLen = *pNum * sizeof(PMYSTRING);*pBaseTypeData = (PBASETYPEDATA)MIDL_user_allocate( baseLen );*pNameList = (PPMYSTRING)MIDL_user_allocate( strLen );*pAddressList = (PPMYSTRING)MIDL_user_allocate( strLen );list<STUDENT>::iterator it = students.begin();for(int i = 0; it != students.end() ; it++,i++){(*pBaseTypeData)[i].m_age = (*it).m_age;(*pBaseTypeData)[i].m_score = (*it).m_score;const char* str = (*it).m_name.c_str();int len = (*it).m_name.length();int allocateLen = ( len>=2 ? len+1 : 2 ) + 4; // sizeof(PMYSTRING) 6個字節(jié),字符串size至少2個字節(jié)PMYSTRING ptr = NULL;(*pNameList)[i] = (PMYSTRING)MIDL_user_allocate(allocateLen);ptr = (*pNameList)[i];ptr->length = len;ptr->size = len >=2 ? len+1 :2;strncpy( (char*)ptr->string, str , len );str = (*it).m_address.c_str();len = (*it).m_address.length();allocateLen = ( len>=2 ? len+1 : 2 ) + 4;(*pAddressList)[i] = (PMYSTRING)MIDL_user_allocate(allocateLen);ptr = (*pAddressList)[i];ptr->length = len;ptr->size = len >=2 ? len+1 :2;strncpy( (char*)ptr->string, str , len );} }int main() {RPC_STATUS status;unsigned char* pszSecurity = NULL;unsigned int cMinCalls = 120;unsigned int cMaxCalls = 120;status = RpcServerUseProtseqEp(reinterpret_cast<unsigned short*>(TEXT("ncacn_np")),RPC_C_PROTSEQ_MAX_REQS_DEFAULT,reinterpret_cast<unsigned short*>(ENDPOINT_ADDRESS_NAME),pszSecurity);if (status != RPC_S_OK){printf("RpcServerUseProtseqEp failed!\n");}// Register the services interface(s).status = RpcServerRegisterIf(RPCTest_v1_0_s_ifspec, // Interface to register.NULL, // Use the MIDL generated entry-point vector.NULL); // Use the MIDL generated entry-point vector.if (status != RPC_S_OK){cout << "RpcServerRegisterIf failed!" << endl;return 0;}cout << "RpcServerRegisterIf suc!" << endl;status = RpcServerListen(cMinCalls,cMaxCalls,TRUE);if (status != RPC_S_OK){cout << "RpcServerListen failed!" << endl;return 0;}cout << "RpcServerListen suc!" << endl;status = RpcMgmtWaitServerListen();cout << "RpcMgmtWaitServerListen over!"<< endl;return 0; }RPC Client 代碼
main.cpp
#include <iostream> #include <Windows.h> #include "RPCTest.h" #include "student.h" #include <list>#define ENDPOINT_ADDRESS_NAME TEXT("\\pipe\\RPCTest") using namespace std;void __RPC_FAR * __RPC_USER midl_user_allocate(size_t len) {void* ret = malloc(len);memset(ret,0,len);return ret; }void __RPC_USER midl_user_free(void __RPC_FAR * ptr) {free(ptr); }RPC_WSTR g_pszStringBinding;bool ClearClient() {if(RPCTest_IFHandle){RpcBindingFree(&RPCTest_IFHandle);RPCTest_IFHandle = NULL;}if(g_pszStringBinding){RpcStringFree(&g_pszStringBinding);g_pszStringBinding = NULL;}return true; }bool PrepareClient() {RPC_STATUS status;RPC_WSTR pszNetworkAddress = NULL;// Creates a string binding handle.// This function is nothing more than a printf.// Connection is not done here.status = RpcStringBindingCompose(NULL, // UUID to bind to.reinterpret_cast<unsigned short*>(TEXT("ncacn_np")),pszNetworkAddress,reinterpret_cast<unsigned short*>(ENDPOINT_ADDRESS_NAME),NULL,&g_pszStringBinding);if (status != RPC_S_OK){return false;}/* Set the binding handle that will be used to bind to the server. */status = RpcBindingFromStringBinding(g_pszStringBinding,&RPCTest_IFHandle);if (status != RPC_S_OK) {if(g_pszStringBinding){RpcStringFree(&g_pszStringBinding);g_pszStringBinding = NULL;}return false;}return true; }void GetMsgList(list<STUDENT>& result) {if(!PrepareClient()){return ;}int num = 0;PBASETYPEDATA baseData = NULL;PPMYSTRING nameList = NULL;PPMYSTRING addressList = NULL;rpc_GetMsgList(&num,&baseData,&nameList,&addressList);// 將從RPCServer獲取的數(shù)據(jù)進(jìn)行組裝for(int i = 0 ; i < num; i++){STUDENT s;s.m_age = baseData[i].m_age;s.m_score = baseData[i].m_score;s.m_name = (char*)nameList[i]->string;s.m_address = (char*)addressList[i]->string;result.push_back(s);// 拷貝完一個單元數(shù)據(jù),釋放一個單元數(shù)據(jù)midl_user_free(nameList[i]);midl_user_free(addressList[i]);}// 釋放基礎(chǔ)數(shù)據(jù)數(shù)組midl_user_free(baseData);// 釋放指針數(shù)組內(nèi)存midl_user_free(nameList);midl_user_free(addressList);ClearClient();return ; }int main() {list<STUDENT> students;GetMsgList(students);list<STUDENT>::iterator it = students.begin();for(; it != students.end() ; it++ ){cout << (*it).m_name << " "<< (*it).m_address << " " << (*it).m_age << " "<< (*it).m_score << endl;}system("pause");return 0; }運行結(jié)果
完整工程
RPCServer、RPCClient、idl文件打包下載地址 這里下載
總結(jié)
以上是生活随笔為你收集整理的C/C++:Windows编程—Windows RPC 传递自定义数据类型、自定义数据类型数组、指针数组的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux系统编程:pipe匿名管道的使
- 下一篇: Redis为什么默认16个数据库,干什么