前言
该篇博文不是讲Windows rpc入门的。是笔者在实际使用Windows RPC时 所遇到的问题,以及解决方法。
笔者有这样的需求,需要从RPC Server获取大量数据,而且该数据是动态分配的。故此RPC Client在调用RPC Server方法是 需要动态获取空间。笔者在中文社区没有找到相关资料,最后只有去看官方文档,下面几个链接是官方文档。
https://docs.microsoft.com/en-us/windows/desktop/Midl/string 字符串的使用
https://docs.microsoft.com/en-us/windows/desktop/Midl/midl-arrays 数组的使用
https://docs.microsoft.com/en-us/windows/desktop/rpc/multiple-levels-of-pointers 多级指针使用
场景如下:
笔者需要从RPC Server获取学生列表信息,个数当然是不确定的,这肯定是动态分配的,学生信息中含有学生姓名和地址,这个是字符串,字符串的长度同样是不确定了,这个需要动态分配。所以这里会在RPC中用到 自定义数据类型、自定义数据类型数组、指针数组。看笔者是怎样处理的。
// 在C语言中笔者想接受的自定义数据类型是
typedef struct _STUDENT
{
char *m_name; // 或者std::string类型
char *m_address; // 或者std::string类型
short m_age;
short m_score;
}STUDENT,*PSTUDENT;
// 按照MIDL数据类型的写法,应该为下面这样,但这样MIDL就无法编译过!
// 是因为 RPC对应可变长的字符串(运行期间进行分配空间) 是采用的柔性数组,从生成的头文件我们也能看出来。
// 柔性数组成员只能放在结构体最后且只能放置一个柔性数组成员!
typedef struct _STUDENT
{
[string]char m_name[*];
[string]char m_address[*];
short m_age;
short m_score;
}STUDENT,*PSTUDENT;
编译错误的图片,错误关键字 error MIDL2055 : field deriving from a conformant array must be the last member of the structure
因为在rpc调用是没有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语言中我们想接受的自定数据类型数据
//typedef struct _STUDENT
//{
// char *m_name; // 或者std::string类型
// char *m_address; // 或者std::string类型
// short m_age;
// short m_score;
//}STUDENT,*PSTUDENT;
// 按照MIDL数据类型的写法,应该为下面这样,但这样MIDL就无法编译过!
// 是因为 RPC对应可变长的字符串(运行期间进行分配空间) 是采用的柔性数组,从生成的头文件我们也能看出来。
// 柔性数组成员只能放在结构体最后且只能放置一个柔性数组成员!
//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;
// 这样我们把基础数据类型和指针数据类型分开,
// 由于基础数据类型的长度已经固定了,所以我们只需要在RPCServer动态开辟一个数组空间即可,并个数返回给RPCClient
// 那么我们的字符串呢?当然字符串也是动态开辟的,RPCServer返回给RPCClient的是一个[str1的地址,str2的地址,str3的地址...]这是一个指针数组,同样也将个数返回
// 这个指针数组的空间 也是有RPCServer开辟的,而且指针数组的成员 也就是最终的字符串的空间也是由RPCServer开辟的,所以我们参数是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)
{
// 具体业务逻辑,根据实际情况申请内存空间
// 这里模拟10个业务数据,具体业务参数的数据,需要通过rpc将数据返回给客户端
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服务器 开辟空间,将数据返回给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个字节,字符串size至少2个字节
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获取的数据进行组装
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);
// 拷贝完一个单元数据,释放一个单元数据
midl_user_free(nameList[i]);
midl_user_free(addressList[i]);
}
// 释放基础数据数组
midl_user_free(baseData);
// 释放指针数组内存
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;
}
运行结果
完整工程
RPCServer、RPCClient、idl文件打包下载地址 这里下载