RPC,即 Remote Procedure Call,远程过程调用。
Microsoft RPC 采用客户机/服务器(C/S)模式,客户机负责发送请求;服务器响应请求,达到通信目的。
客户机和服务器共同维护一个命令列表,发送请求时命令作为第一个参数,服务器通过命令来调用正确的处理程序,返回客户机需要的信息

例子1:入门 hello world

客户端程序通过RPC远程过程调用,传递一个字符串“Hello,World”给服务端,
服务端输出字符串该串“Hello,World”到命令行控制台中。

一、vs 新建空项目 winRPC;

1.1 添加新的空项目,取名 IDL;
1.2. 进入 IDL文件夹,cmd 命令行也进入这个目录,执行 uuidgen /i /oRPCTest.idl 命令后生成文件 RPCTest.idl 【说明】 uuidgen 就是 uuidgen.exe 工具,安装完 vs 基本有这个。 /i /o 是选项
1.3. 右键 IDL项目,添加现有项即刚生成的 RPCTest.idl, 双击添加两个函数HelloProc(), Shutdown():

a. 定义头中包含了自动生成随机的 UUID 和版本号。版本号作兼容之用。客户端、服务端的版本只有兼容了,才可以连接。
b. 函数的参数 pszString有两个属性[in]和[string]:
[in]属性告诉运行库此参数只能从客户端传送到服务端。
[string]属性指定了桩要按C风格的字符串来处理此参数。

// RPCTest.idl
[
	uuid(8119ceb2-4586-4cdf-9fa3-7cff4b36274d),
	version(1.0)
]
interface RPCTest
{
	void HelloXXXXXX([in, string] unsigned char* pszString);
	void Shutdown(void);
}

1.4. 右键 IDL项目,新建文件:RPCTest.acf,
确保RPCTest.idl 和 RPCTest.acf 在同一文件夹中;双击编辑:

// RPCTest.acf    //描述独立与机器的数据结构
[
    implicit_handle (handle_t RPCTest_IfHandle)
] 
interface RPCTest
{
}

[implicit_handle]属性允许客户端程序为它的远程过程调用选择一个服务端句柄;
【注意】此ACF文件的体为空

1.5. 选择平台如 x64 release 编译 IDL项目(MIDL编译器),生成 3 文件:

头文件: RPCTest_h.h
客户端桩文件:RPCTest_c.c
服务端桩文件:RPCTest_s.c


二、服务端 Server 子项目

2.1. 添加新的空项目 Server ,工程属性从UNICODE改成多字节符 2.2 添加上一步产生的文件:RPCTest_h.h 和 RPCTest_s.c
2.3 添加新空文件: Server.cpp
2.4 编译生成 Server.exe

//Server.cpp
#define _CRT_SECURE_NO_DEPRECATE
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include "../IDL/RPCTest_h.h"

#pragma comment(lib, "Rpcrt4.lib")

int main(int argc, char* argv[])
{
    unsigned char* pszSecurity = NULL;
#if 1   //local 本机通信
    unsigned char pszProtocolSequence[] = "ncalrpc";
    // unsigned char pszEndPoint[] = "\\pipe\\RPCTest"; //命名管道,使用 ncacn_np
    unsigned char pszEndPoint[] = "AppXXXName"; //使用 ncalrpc
#else   //tcp-ip 跨机器通信
    unsigned char pszProtocolSequence[] = "ncacn_ip_tcp";
    unsigned char pszEndPoint[] = "13521";
#endif

    RPC_STATUS rpcStats = RpcServerUseProtseqEp(pszProtocolSequence, RPC_C_LISTEN_MAX_CALLS_DEFAULT, pszEndPoint, pszSecurity);
    printf("rpcStats=%d,RpcServerUseProtseqEp\n", rpcStats);
    if (rpcStats) exit(rpcStats);

    rpcStats = RpcServerRegisterIf(RPCTest_v1_0_s_ifspec, NULL, NULL);
    printf("rpcStats=%d,RpcServerRegisterIf\n", rpcStats);
    if (rpcStats) exit(rpcStats);

    unsigned int fDontWait = FALSE;
    rpcStats = RpcServerListen(1, RPC_C_LISTEN_MAX_CALLS_DEFAULT, fDontWait);
    printf("rpcStats=%d,RpcServerListen\n", rpcStats);
    if (rpcStats) exit(rpcStats);

    return 0;
}

/******************************************************/
/*         MIDL allocate and free                     */
/******************************************************/
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);
}

void HelloXXXXXX(unsigned char __RPC_FAR* pszString)
{
    printf("%s\n", pszString);
}

void Shutdown(void)
{
    RPC_STATUS status;
    status = RpcMgmtStopServerListening(NULL);
    if (status)  exit(status);

    status = RpcServerUnregisterIf(NULL, NULL, FALSE);
    if (status)  exit(status);

} //end Shutdown

三、客户端 Client 子项目

3.1. 添加新的空项目 Client,工程属性从UNICODE改成多字节符 3.2 添加上一步产生的文件:RPCTest_h.h 和 RPCTest_c.c
3.3 添加新空文件: Client.cpp
3.4 编译生成 Client.exe

//Client.cpp
#define _CRT_SECURE_NO_DEPRECATE
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include "../IDL/RPCTest_h.h"

#pragma comment(lib, "Rpcrt4.lib")

int main(int argc, char* argv[])
{
    unsigned char* pszUuid = NULL;
    unsigned char* pszOptions = NULL;
    unsigned char* pszStringBinding = NULL;
#if 1  //local 本机通信
    unsigned char pszProtocolSequence[] = "ncalrpc";
    unsigned char* pszNetworkAddress = NULL;
    // unsigned char pszEndpoint[] = "\\pipe\\RPCServer"; //命名管道,使用 ncacn_np
    unsigned char pszEndpoint[] = "AppXXXName"; //使用 ncalrpc
#else  //tpc-ip 跨机器通信
    unsigned char pszProtocolSequence[] = "ncacn_ip_tcp";
    unsigned char pszNetworkAddress[] = "192.168.0.27";
    unsigned char pszEndpoint[] = "15571";
#endif
    RPC_STATUS status = RpcStringBindingCompose(pszUuid, pszProtocolSequence, pszNetworkAddress, pszEndpoint, pszOptions, &pszStringBinding);
    printf("rpcStats=%d,RpcStringBindingCompose\n", status);
    if (status) exit(status);

    status = RpcBindingFromStringBinding(pszStringBinding, &RPCTest_IfHandle);
    printf("status=%d,RpcBindingFromStringBinding\n", status);
    if (status) return status;

    RpcTryExcept
    {
	    char str[50] = { 0 };
        for (size_t i = 0; i < 20; i++)
        {
    		sprintf(str, "[%u] Hello World", i);
            HelloXXXXXX(str); // 调用 Server 中的函数,就像调用本地函数一样使用
        }
        Shutdown(); // 调用 Server 中的函数,使 Server 推出
    }
    RpcExcept(1)
    {
        unsigned long ulCode = RpcExceptionCode();
        printf("Runtime reported exception 0x%lx = %ld\n", ulCode, ulCode);
    }
    RpcEndExcept

    status = RpcStringFree(&pszStringBinding);
    printf("status=%d,RpcStringFree\n", status);
    if (status) return status;

    status = RpcBindingFree(&RPCTest_IfHandle);
    if (status) return status;

    return 0;
}

/******************************************************/
/*         MIDL allocate and free                     */
/******************************************************/
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);
}

例子2:进阶

参考
Windows RPC 传递自定义数据类型、自定义数据类型数组、指针数组

// RPCServer.idl
// oaidl用于OLE自动化; ocidl用于OLE容器(例如ActiveX);
import "oaidl.idl";
import "ocidl.idl";

[
	uuid(551d88b0-b831-4283-a1cd-276559e49f28),
	version(1.0)
]

interface RPCServer
{
	// 官方文档推荐的自定义字符串写法(带长度和大小)
	typedef struct _MYSTRING {
		unsigned short size;
		unsigned short length;
		[ptr, size_is(size), length_is(length)] char string[*];
	} MYSTRING, ** PPMYSTRING, * 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);

	error_status_t GetServerName([out, size_is(len)]char* pszName,
		[in]long len);

	error_status_t Add([in]long num1,
		[in]long num2,
		[out]long* rtn);

	error_status_t ShutDown();
}
//RPCServer.acf
[
    implicit_handle (handle_t RPCServer_IfHandle)
]

interface RPCServer
{
}
// RPCServer.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>
#include "../IDL/RPCServer.h"
#include <list>
#include <string>
using namespace std;

typedef struct _STUDENT{
	string m_name;
    string m_address;
	short m_age;
	short m_score;
}STUDENT,*PSTUDENT;

#pragma comment(lib, "Rpcrt4.lib")

long StartService();

void __RPC_FAR* __RPC_USER midl_user_allocate(size_t len)
{
    //return (malloc(len));
    return(HeapAlloc(GetProcessHeap(), HEAP_GENERATE_EXCEPTIONS | HEAP_ZERO_MEMORY, len));
}
void __RPC_USER midl_user_free(void __RPC_FAR* ptr)
{
    //free(ptr);
    HeapFree(GetProcessHeap(), 0, ptr);
}

// 获取信息列表
void rpc_GetMsgList(
    /*[out]*/ int* pNum,
	/*[out, size_is(, *pNum)]*/ PBASETYPEDATA* pBaseTypeData,
    /*[out, size_is(, *pNum)]*/ PPMYSTRING*    pNameList,
    /*[out, size_is(, *pNum)]*/ PPMYSTRING*    pAddressList )
{
    //模拟10个业务数据,需要通过rpc将数据返回给客户端
    list<STUDENT> students;
    STUDENT s;
    char tmp[50] = { 0 };
    static int k;
    for (int i = 0; i < 10; i++)
    {
        k++;
        memset(tmp, 0, sizeof(tmp) );
        sprintf(tmp, "Lisi-%d", k);
        s.m_name = tmp;

        memset(tmp, 0, sizeof(tmp) );
        sprintf(tmp, "Beijing Street %d", k);
        s.m_address = tmp;

        s.m_age = k * 10;
        s.m_score = k * 10 + 9;
        students.push_back(s);
    }

    // RPC服务器 开辟空间,将数据返回给RPC客户端
    *pNum = (int)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);
    }
}

error_status_t GetServerName(
    /* [size_is][out] */ unsigned char* pszName,
    /* [in] */ long len )
{
    strncpy((char*)pszName, "RPCServer", len);
    pszName[len - 1] = '\0';
    printf("Server Start success\n");
    return 0;
}

error_status_t Add(
    /* [in] */ long num1,
    /* [in] */ long num2,
    /* [out] */ long* rtn )
{
    *rtn = num1 + num2;
    return 0;
}

error_status_t ShutDown(void)
{
    RPC_STATUS rpcStatus;
    rpcStatus = RpcMgmtStopServerListening(NULL);
    rpcStatus = RpcServerUnregisterIf(NULL, NULL, FALSE);

    printf("Server Shut down.\n");
    return 0;
}


int main(int argc, char* argv[])
{
    unsigned char* pszSecurity = NULL;
#if 1   //local
    unsigned char pszProtocolSequence[] = "ncalrpc";
 // unsigned char pszEndPoint[] = "\\pipe\\RPCServer"; //命名管道,使用 ncacn_np
    unsigned char pszEndPoint[] = "AppName"; //使用 ncalrpc
#else   //tcp-ip
    unsigned char pszProtocolSequence[] = "ncacn_ip_tcp";
    unsigned char pszEndPoint[] = "13521";
#endif

    RPC_STATUS rpcStats = RpcServerUseProtseqEp(pszProtocolSequence, RPC_C_LISTEN_MAX_CALLS_DEFAULT, pszEndPoint, pszSecurity);
    printf("rpcStats=%d,RpcServerUseProtseqEp\n", rpcStats);
    if (rpcStats) exit(rpcStats);

    rpcStats = RpcServerRegisterIf(RPCServer_v1_0_s_ifspec, NULL, NULL);
    printf("rpcStats=%d,RpcServerRegisterIf\n", rpcStats);
    if (rpcStats) exit(rpcStats);

    unsigned int fDontWait = FALSE;
    rpcStats = RpcServerListen(1, RPC_C_LISTEN_MAX_CALLS_DEFAULT, fDontWait);
    printf("rpcStats=%d,RpcServerListen\n", rpcStats);
    if (rpcStats) exit(rpcStats);

    return 0;
}
// RPCClient.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include "..\IDL\RPCServer.h"
#include <list>
#include <string>
using namespace std;

#pragma comment( lib, "Rpcrt4.lib" )

typedef struct _STUDENT {
    string m_name;
    string m_address;
    short m_age;
    short m_score;
}STUDENT, * PSTUDENT;

void CallServerFuntions();

int main(int argc, char* argv[])
{
    unsigned char* pszUuid = NULL;
    unsigned char* pszOptions = NULL;
    unsigned char* pszStringBinding = NULL;
#if 1  //local
    unsigned char pszProtocolSequence[] = "ncalrpc";
    unsigned char* pszNetworkAddress = NULL;
 // unsigned char pszEndpoint[] = "\\pipe\\RPCServer"; //命名管道,使用 ncacn_np
    unsigned char pszEndpoint[] = "AppName"; //使用 ncalrpc
#else  //tpc-ip
    unsigned char pszProtocolSequence[] = "ncacn_ip_tcp";
    unsigned char pszNetworkAddress[] = "10.194.102.228";
    unsigned char pszEndpoint[] = "13521";
#endif
    RPC_STATUS rpcStatus = RpcStringBindingCompose(pszUuid, pszProtocolSequence, pszNetworkAddress, pszEndpoint, pszOptions, &pszStringBinding);
    //printf("rpcStats=%d,RpcStringBindingCompose\n", rpcStatus);
    if (rpcStatus) exit(rpcStatus);

    rpcStatus = RpcBindingFromStringBinding(pszStringBinding, &RPCServer_IfHandle);
    //printf("rpcStats=%d,RpcBindingFromStringBinding\n", rpcStatus);
    if (rpcStatus) exit(rpcStatus);

    RpcTryExcept {
        CallServerFuntions();
    }
    RpcExcept(1) {
        unsigned long ulCode = RpcExceptionCode();
        //printf("thow exception: 0x%lx = %ld.\n", ulCode, ulCode);
    }
    RpcEndExcept

    rpcStatus = RpcStringFree(&pszStringBinding);
    //printf("rpcStats=%d,RpcStringFree\n", rpcStatus);
    if (rpcStatus) exit(rpcStatus);

    rpcStatus = RpcBindingFree(&RPCServer_IfHandle);
    if (rpcStatus) exit(rpcStatus);

    return 0;
}

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);
}

void GetMsgList(list<STUDENT>& result)
{
    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);
}

void CallServerFun()
{
    list<STUDENT> students;
    GetMsgList(students);

    for (list<STUDENT>::iterator it = students.begin(); it != students.end(); it++) {
        printf("name:%s, addr:%s, age:%d, score:%d\n", (*it).m_name.c_str(), (*it).m_address.c_str(), (*it).m_age, (*it).m_score);
    }
}

void CallServerFuntions()
{
	// 1
    unsigned char pszName[64] = { 0 };
    GetServerName(pszName, 64); //获取服务名称
    printf("Server Name is: %s\n", pszName);
	// 2
    long num1 = 2;
    long num2 = 2;
    long nSum = 0;
    Add(num1, num2, &nSum); //加法求值
    printf("%d + %d = %d\n", num1, num2, nSum);
	// 3
    for (size_t i = 0; i < 10; i++)
    {
        CallServerFun(); //获取服务器返回的自定义数据类型
    }
    // 4
    ShutDown(); //远程关闭RPCServer服务
}