C/C++:Windows编程—Windows RPC 传递自定义数据类型、自定义数据类型数组、指针数组

前言

该篇博文不是讲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文件打包下载地址 这里下载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值