Window RPC 詳細運用
前言
Thrift是一款由Fackbook開發的可伸縮、跨語言的服務開發框架,該框架已經開源並且加入的Apache專案。Thrift主要功能是:通過自定義的Interface Definition Language(IDL),可以建立基於RPC的客戶端和服務端的服務程式碼,本來是準備研究下Thrift跨語言RPC框架,為了預預熱想先總結下Window RPC的開發經驗,下一期釋出我學習Thrift框架的開發經驗。
Window 系統下要做RPC通訊,需要先編idl檔案,先製作一個基礎型別檔案ms-dtyp.idl,內容如下
cpp_quote("#ifndef _DTYP_IDL") cpp_quote("#define _DTYP_IDL") /* Common data types */ cpp_quote("#ifndef _WINDEF_H") typedef int BOOL, *PBOOL, *LPBOOL; typedef unsigned char BYTE, *PBYTE, *LPBYTE; typedef unsigned long DWORD, *PDWORD, *LPDWORD; cpp_quote("#endif") cpp_quote("#ifndef _BASETSD_H_") typedef unsigned int DWORD32; typedef unsigned __int64 DWORD64; cpp_quote("#endif") //typedef unsigned long error_status_t; cpp_quote("#ifndef _WINDEF_H") typedef int INT, *LPINT; cpp_quote("#endif") cpp_quote("#ifndef _BASETSD_H_") typedef signed char INT8; typedef signed short INT16; typedef signed int INT32; typedef signed __int64 INT64; cpp_quote("#endif") cpp_quote("#ifndef _BASETSD_H_") typedef signed int LONG32; typedef signed __int64 LONG64; cpp_quote("#endif") typedef unsigned __int64 QWORD; cpp_quote("#ifndef _WINNT_") typedef short SHORT; cpp_quote("#endif") typedef __int64 TIME; cpp_quote("#ifndef _WINNT_") typedef char CHAR, *PCHAR; typedef unsigned char UCHAR, *PUCHAR; cpp_quote("#endif") cpp_quote("#ifndef _WINDEF_H") typedef unsigned int UINT; cpp_quote("#endif") cpp_quote("#ifndef _BASETSD_H_") typedef unsigned char UINT8; typedef unsigned short UINT16; typedef unsigned int UINT32; typedef unsigned __int64 UINT64; cpp_quote("#endif") cpp_quote("#ifndef _WINNT_") typedef unsigned long ULONG, *PULONG; cpp_quote("#endif") cpp_quote("#ifndef _BASETSD_H_") typedef unsigned int ULONG32; typedef unsigned __int64 ULONG64; cpp_quote("#endif") cpp_quote("#ifndef _WINNT_") typedef unsigned __int64 ULONGLONG; typedef unsigned short USHORT; cpp_quote("#endif") cpp_quote("#ifndef _WINDEF_H") typedef unsigned short WORD, *PWORD, *LPWORD; cpp_quote("#endif") cpp_quote("#ifndef _WINNT_") typedef long LONG, *PLONG; cpp_quote("#endif") cpp_quote("#ifndef _WINDEF_H") typedef long *LPLONG; cpp_quote("#endif") cpp_quote("#ifndef _WINNT_") typedef signed __int64 LONGLONG; cpp_quote("#endif") cpp_quote("#ifndef _WINDEF_H") typedef float FLOAT; cpp_quote("#endif") cpp_quote("#ifndef __wtypes_h__") typedef double DOUBLE; cpp_quote("#endif") cpp_quote("#ifndef _WINNT_") typedef BYTE BOOLEAN, *PBOOLEAN; cpp_quote("#endif") cpp_quote("#ifndef _BASETSD_H_") #ifdef _WIN64 typedef __int64 LONG_PTR; typedef unsigned __int64 ULONG_PTR; #else typedef LONG LONG_PTR; typedef ULONG ULONG_PTR; #endif typedef ULONG_PTR SIZE_T; typedef ULONG_PTR DWORD_PTR; cpp_quote("#endif") typedef DWORD NET_API_STATUS; cpp_quote("#ifndef _WINNT_") typedef ULONGLONG DWORDLONG, *PDWORDLONG; cpp_quote("#endif") typedef DWORD HCALL; //typedef DWORD HRESULT; cpp_quote("#ifndef _WINNT_") typedef void *HANDLE; typedef void /*VOID,*/ *PVOID; cpp_quote("#endif") cpp_quote("#ifndef __WINE_RPCDCE_H") typedef void *RPC_BINDING_HANDLE; cpp_quote("#endif") typedef [context_handle] void *PCONTEXT_HANDLE; typedef PCONTEXT_HANDLE *PPCONTEXT_HANDLE; cpp_quote("#ifndef _WINNT_") typedef wchar_t WCHAR, *PWCHAR; cpp_quote("#if 0") typedef wchar_t UNICODE; cpp_quote("#endif") typedef const char *LPCSTR; typedef const wchar_t *LPCWSTR; typedef char *PSTR, *LPSTR; typedef wchar_t *LPWSTR, *PWSTR; cpp_quote("#endif") typedef const wchar_t *LMCSTR; typedef WCHAR *LMSTR; cpp_quote("#ifndef __wtypes_h__") typedef WCHAR *BSTR; cpp_quote("#endif") cpp_quote("#if 0") #ifdef Unicode typedef LPCWSTR LPCTSTR; typedef LPWSTR LPTSTR; typedef WCHAR TCHAR; #else typedef LPCSTR LPCTSTR; typedef LPSTR LPTSTR; typedef CHAR TCHAR; #endif cpp_quote("#endif") /* Common data structures */ cpp_quote("#if 0") typedef struct _FILETIME { DWORD dwLowDateTime; DWORD dwHighDateTime; } FILETIME, *PFILETIME, *LPFILETIME; typedef struct _GUID { DWORD Data1; WORD Data2; WORD Data3; BYTE Data4[8]; } GUID, UUID, *PGUID; typedef struct _LARGE_INTEGER { LONGLONG QuadPart; } LARGE_INTEGER, *PLARGE_INTEGER; typedef DWORD LCID; cpp_quote("#endif") typedef struct _RPC_UNICODE_STRING { USHORT Length; USHORT MaximumLength; [size_is(MaximumLength/2), length_is(Length/2)] LPWSTR Buffer; } RPC_UNICODE_STRING, *PRPC_UNICODE_STRING; cpp_quote("#if 0") typedef struct _SYSTEMTIME { WORD wYear; WORD wMonth; WORD wDayOfWeek; WORD wDay; WORD wHour; WORD wMinute; WORD wSecond; WORD wMilliseconds; } SYSTEMTIME, *PSYSTEMTIME; typedef struct _UINT128 { UINT64 lower; UINT64 upper; } UINT128, *PUINT128; typedef struct _ULARGE_INTEGER { ULONGLONG QuadPart; } ULARGE_INTEGER, *PULARGE_INTEGER; typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; [size_is(MaximumLength/2), length_is(Length/2)] LPWSTR Buffer; } UNICODE_STRING, *PUNICODE_STRING; cpp_quote("#endif") /* Constructed security types */ cpp_quote("#if 0") typedef struct _SID_IDENTIFIER_AUTHORITY { BYTE Value[6]; } SID_IDENTIFIER_AUTHORITY; typedef struct _SID { BYTE Revision; BYTE SubAuthorityCount; SID_IDENTIFIER_AUTHORITY IdentifierAuthority; [size_is(SubAuthorityCount)] DWORD SubAuthority[*]; } SID, *PSID; typedef struct _ACCESS_MASK { DWORD ACCESS_MASK; } ACCESS_MASK, *PACCESS_MASK; typedef struct _ACE_HEADER { UCHAR AceType; UCHAR AceFlags; USHORT AceSize; } ACE_HEADER, *PACE_HEADER; typedef struct _ACCESS_ALLOWED_ACE { ACE_HEADER Header; ACCESS_MASK Mask; DWORD SidStart; } ACCESS_ALLOWED_ACE, *PACCESS_ALLOWED_ACE; typedef struct _ACCESS_ALLOWED_OBJECT_ACE { ACE_HEADER Header; ACCESS_MASK Mask; DWORD Flags; GUID ObjectType; GUID InheritedObjectType; DWORD SidStart; } ACCESS_ALLOWED_OBJECT_ACE, *PACCESS_ALLOWED_OBJECT_ACE; typedef struct _ACCESS_DENIED_ACE { ACE_HEADER Header; ACCESS_MASK Mask; DWORD SidStart; } ACCESS_DENIED_ACE, *PACCESS_DENIED_ACE; typedef struct _ACCESS_ALLOWED_CALLBACK_ACE { ACE_HEADER Header; ACCESS_MASK Mask; DWORD SidStart; } ACCESS_ALLOWED_CALLBACK_ACE, *PACCESS_ALLOWED_CALLBACK_ACE; typedef struct _ACCESS_DENIED_CALLBACK_ACE { ACE_HEADER Header; ACCESS_MASK Mask; DWORD SidStart; } ACCESS_DENIED_CALLBACK_ACE, *PACCESS_DENIED_CALLBACK_ACE; typedef struct _ACCESS_ALLOWED_CALLBACK_OBJECT_ACE { ACE_HEADER Header; ACCESS_MASK Mask; DWORD Flags; GUID ObjectType; GUID InheritedObjectType; DWORD SidStart; } ACCESS_ALLOWED_CALLBACK_OBJECT_ACE, *PACCESS_ALLOWED_CALLBACK_OBJECT_ACE; typedef struct _ACCESS_DENIED_CALLBACK_OBJECT_ACE { ACE_HEADER Header; ACCESS_MASK Mask; DWORD Flags; GUID ObjectType; GUID InheritedObjectType; DWORD SidStart; } ACCESS_DENIED_CALLBACK_OBJECT_ACE, *PACCESS_DENIED_CALLBACK_OBJECT_ACE; typedef struct _SYSTEM_AUDIT_ACE { ACE_HEADER Header; ACCESS_MASK Mask; DWORD SidStart; } SYSTEM_AUDIT_ACE, *PSYSTEM_AUDIT_ACE; typedef struct _SYSTEM_AUDIT_CALLBACK_ACE { ACE_HEADER Header; ACCESS_MASK Mask; DWORD SidStart; } SYSTEM_AUDIT_CALLBACK_ACE, *PSYSTEM_AUDIT_CALLBACK_ACE; typedef struct _SYSTEM_MANDATORY_LABEL_ACE { ACE_HEADER Header; ACCESS_MASK Mask; DWORD SidStart; } SYSTEM_MANDATORY_LABEL_ACE, *PSYSTEM_MANDATORY_LABEL_ACE; typedef struct _SYSTEM_AUDIT_CALLBACK_OBJECT_ACE { ACE_HEADER Header; ACCESS_MASK Mask; DWORD Flags; GUID ObjectType; GUID InheritedObjectType; DWORD SidStart; } SYSTEM_AUDIT_CALLBACK_OBJECT_ACE, *PSYSTEM_AUDIT_CALLBACK_OBJECT_ACE; typedef struct _ACL { UCHAR AclRevision; UCHAR Sbz1; USHORT AclSize; USHORT AceCount; USHORT Sbz2; } ACL, *PACL; typedef struct _SECURITY_DESCRIPTOR { UCHAR Revision; UCHAR Sbz1; USHORT Control; ULONG Owner; ULONG Group; ULONG Sacl; ULONG Dacl; } SECURITY_DESCRIPTOR, *PSECURITY_DESCRIPTOR; typedef DWORD SECURITY_INFORMATION, *PSECURITY_INFORMATION; cpp_quote("#endif") typedef struct _RPC_SID { UCHAR Revision; UCHAR SubAuthorityCount; SID_IDENTIFIER_AUTHORITY IdentifierAuthority; [size_is(SubAuthorityCount)] DWORD SubAuthority[]; } RPC_SID, *PRPC_SID; cpp_quote("#endif /* _DTYP_IDL */")
然後結合ms-dtyp.idl裡面的型別,製作需要生成通訊介面的IDL檔案,比如我現在就做個簡單的RPC請求資料介面檔案PostTest.idl:
#include "ms-dtyp.idl" [ uuid(D0FECC64-60E2-4d88-A0FD-39210BB009CA), version(2.0) ] interface PostTest { DWORD RpcGetResult( [unique][string][in] LPWSTR lpParam, [out, string, size_is(*lpcchResult + 1)] LPWSTR lpResult, [out][in] DWORD *lpcchResult); BOOL RpcSafeExit(); }
到這裡,我們的RPC的IDL介面檔案製作完成,如需豐富IDL檔案裡的通訊介面,只需多加幾個類似的RpcGetResult函式即可
下一步,我們開始編譯IDL檔案生成對應的.h,.c檔案,分別用於客戶端服務端專案中,Window RPC的編譯方法有兩種,一種是用MIDL.exe,自己用everything搜素,可以搜素到本地的MIDL.exe路徑,把兩個IDL檔案複製到MIDL.exe同目錄,然後執行MIDL.exe PostTest.idl命令即可生成PostTest_h.h,PostTest_c.c,PostTest_s.c三個檔案,這三個檔案就能直接用到專案中了,第二種編譯方法也非常簡單,我一般都是用第二種方法,就是新建一個隨便命名的空專案,然後將兩個IDL檔案加入到專案,編譯專案即可在專案目錄生成PostTest_h.h,PostTest_c.c,PostTest_s.c三個檔案
最後就是將這三個檔案用到專案中了,我直接上程式碼,至於為啥我程式碼這麼用這麼寫,你們可以自己再多查資料,我這邊只簡單介紹下用法
複製PostTest_h.h,PostTest_s.c檔案到伺服器專案目錄中,新增檔案到專案中,編寫服務端呼叫main函式
WinRPCServer.cpp
// WinRPCServer.cpp : 定義控制檯應用程式的入口點。
//#include "stdafx.h"
#include <Windows.h>
#include <string>
#include <rpc.h>
#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "Rpcrt4.lib")
#include "PostTest_h.h"
using namespace std;
void * __RPC_USER MIDL_user_allocate(size_t len)
{
return (malloc(len));
}
void __RPC_USER MIDL_user_free(void* ptr)
{
free(ptr);
}
DWORD RpcGetResult(handle_t IDL_handle,LPWSTR lpParam,LPWSTR lpResult,DWORD *lpcchResult)
{
wstring strType = lpParam;
wchar_t *szResult = new wchar_t[100];
wmemset(szResult,0,100);
if (strType == L"01")
{
wcscpy(szResult,L"1111111\r\n");
}
else
{
wcscpy(szResult,L"2222222\r\n");
}
if (*lpcchResult <= wcslen(szResult))
{
wcsncpy_s(lpResult,*lpcchResult,szResult,*lpcchResult - 1);
lpResult[*lpcchResult - 1] = '\0';
*lpcchResult--;
}
else
{
wcscpy_s(lpResult,*lpcchResult,szResult);
*lpcchResult = wcslen(lpResult);
}
return 1;
}
BOOL RpcSafeExit(handle_t IDL_handle)
{
//ExitProcess( 0 );
return TRUE;
}
BOOL StartRPC()
{
/* wchar_t *pArgv = (wchar_t*)param;*/
RPC_STATUS status = 0;
unsigned int nMinCalls = 1;
unsigned int nMaxCalls = 20;
// int iNum = 0;
// iNum = _wtoi(pArgv);
char szAppName[256] = {0};
sprintf_s(szAppName,256,"WANG_XIAOMING_RPC_GLOBAL_VER_001");
status = RpcServerUseProtseqEpA((unsigned char *)"ncalrpc", RPC_C_PROTSEQ_MAX_REQS_DEFAULT,
(unsigned char *)szAppName, NULL );
if ( status != 0 )
{
return FALSE;
}
OutputDebugStringA("服務端啟動成功");
status = RpcServerRegisterIfEx(
PostTest_v2_0_s_ifspec,
NULL,
NULL,
RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH,
0,
NULL);
if ( status != 0 )
{
return FALSE;
}
status = RpcServerListen(
nMinCalls,
nMaxCalls,
TRUE );
return TRUE;
}
int main(int argc, char* argv[])
{
if (StartRPC())
{
while (1)
{
Sleep(1000);
}
}
printf("Close");
getchar();
return 0;
}
複製PostTest_h.h,PostTest_c.c檔案到客戶端請求專案目錄中,新增檔案到專案中,編寫客戶端呼叫main函式
WinRPCClient.cpp
// WinRPCClient.cpp : 定義控制檯應用程式的入口點。
//
#include "stdafx.h"
#include <Windows.h>
#include <rpc.h>
#pragma comment(lib, "Ws2_32.lib")
#pragma comment(lib, "Rpcrt4.lib")
#include "PostTest_h.h"
RPC_BINDING_HANDLE g_RpcLink; //RPC連線
void * __RPC_USER MIDL_user_allocate(size_t len)
{
return (malloc(len));
}
void __RPC_USER MIDL_user_free(void* ptr)
{
free(ptr);
}
bool InitRpc()
{
bool bRet = true;
RPC_STATUS status;
unsigned char * pszStringBinding = NULL;
int iSpc = 1;
char szSpc[50] = {0};
sprintf_s(szSpc,"WANG_XIAOMING_RPC_GLOBAL_VER_001");
status = RpcStringBindingComposeA(
NULL,
(unsigned char *)"ncalrpc", //本地的RPC
NULL,
(unsigned char *)szSpc,
NULL,
&pszStringBinding );
if ( status != 0 )
{
return false;
}
status = RpcBindingFromStringBindingA(pszStringBinding, &g_RpcLink );
if ( status != 0 )
{
return false;
}
printf("Server RpcInit Suc\r\n");
return true;
}
bool CheckRpcConnect()
{
if (g_RpcLink == NULL)
{
if (!InitRpc())
{
return false;
}
}
RpcTryExcept
{
RPC_STATUS status;
status = RpcMgmtIsServerListening( g_RpcLink );
if (status == RPC_S_OK)
{
printf("ServerListening Suc\r\n");
return true;
}
}
RpcExcept(1)
{
return false;
}
RpcEndExcept
return false;
}
//初始化RPC連線鏈
bool SendRpcRequest(wchar_t *pParam,wchar_t *pResult,DWORD &iLen)
{
RpcTryExcept
{
if (RpcGetResult(g_RpcLink,pParam,pResult,&iLen) != 0 )
{
return true;
}
return true;;
}
RpcExcept(1)
{
ULONG ulCode = RpcExceptionCode();
return false;
}
RpcEndExcept
return true;
}
int _tmain(int argc, _TCHAR* argv[])
{
if (CheckRpcConnect())
{
wchar_t* pResult = new wchar_t[1024];
wmemset(pResult,0,1024);
DWORD dwLen = 1024;
SendRpcRequest((LPWSTR)L"01",pResult,dwLen);
wprintf(pResult);
}
printf("Close");
getchar();
return 0;
}
然後先執行server程式,後執行client,就可以檢視到請求的資料了,我這邊的例子用的是本地RPC呼叫,如需運用網路RPC呼叫,可自行查詢相關例子,修改對應引數