跨權限Register-Free COM進程外組件

跨權限Register-Free COM進程外組件

啓動COM組件的三種機制
上面的文章說的是在不能使用註冊表的情況下如何繼續使用進程外COM組件。說到的一個必要內容就是需要寫好manifest文件。

作爲COM組件的特性,其中一條就是靈活配置,這時,如果每增加刪除一個組件,所有相關的使用者都要修改manifest文件,恐怕這樣的工作對於維護的人來說是個地獄。

那麼,有沒有一種不需要額外配置的進程外組件的方法?通過嘗試 進程外COM組件實現IRpcChannelBuffer接口 證明這種方法是可行的, 文章下載中的代碼少提交了一些內容,其中registeriid.h文件的內容是

#ifndef registeriid_h__
#define registeriid_h__

struct __declspec(uuid("0A16CA7B-9392-4a99-9E3A-9E31D317B8DC")) RegisterHost;
#endif // registeriid_h__

編譯的時候會出錯, 把FastLib.h中的一部分代碼註釋掉

// #include "_IFastLibEvents_CP.h" // 這個會報錯找不到這個文件,可以註釋掉
class ATL_NO_VTABLE CFastLib :
	public CComObjectRootEx<CComSingleThreadModel>,
	public CComCoClass<CFastLib, &CLSID_FastLib>,
	// public IConnectionPointContainerImpl<CFastLib>, //  註釋掉
	// public CProxy_IFastLibEvents<CFastLib>, // 註釋掉
	// public IDispatchImpl<IFastLib, &IID_IFastLib, &GUID_NULL, /*wMajor =*/ -1, /*wMinor =*/ -1> // 註釋掉
	public IFastLib
BEGIN_COM_MAP(CFastLib)
	COM_INTERFACE_ENTRY(IFastLib)
	// COM_INTERFACE_ENTRY(IDispatch) // 註釋
	// COM_INTERFACE_ENTRY(IConnectionPointContainer) // 註釋
END_COM_MAP()

// BEGIN_CONNECTION_POINT_MAP(CFastLib) // 註釋
//	CONNECTION_POINT_ENTRY(__uuidof(_IFastLibEvents)) // 註釋
// END_CONNECTION_POINT_MAP() // 註釋
	DECLARE_PROTECT_FINAL_CONSTRUCT()

另外全文查找config.json,把找到的地方改成你希望的路徑下的config.json, 這個config.json文件後面會講到

編譯完成後將會生成幾個文件:

  • register.exe 自定註冊表
  • regtool.dll 註冊功能工具
  • TestTwo.dll 功能實現COM組件
  • TestTwoPS.dll 組件代理dll
  • TestTwoConsole.exe 服務程序
  • TestTwoClientConsole.exe 調用的測試程序

使用方法

把上面的程序放在一起, 生成一個名爲config.json的空文件,將這個空文件拖到register.exe上,此時register.exe就在內存中運行了,不要關閉regsiter.exe, 從cmd命令行執行

regsvr32 TestTwo.dll

執行後會出現DLLRegisterServer…成功的提示,實際上這原本是註冊到系統註冊表裏的,現在註冊到我們自己的文件裏面了config.json,這個文件爲後面程序執行提供重要的信息。

執行TestTwoConsole.exe(任意權限),然後再執行TestTwoClientConsole.exe(任意權限)

通用性

上面的方法適用於所有COM組件的C/S模式調用

然而

自定的PIPE服務器如果要具備7*24小時還有高併發的壓力,已經是另一個很大的話題了,所以上面的代碼就實驗性已經可以令人滿意了,但是實用性還有待提高。然後就有以下的課題。

使用RPC代替管道

Windows 系統提供的RPC,從各個方面都可以充分滿足我們的使用要求,可以用它代替自定義的管道服務器。而且RPC可以使用各種協議(TCP/IP, PIPE, LPC, MQ, … ),甚至跨機器, 代替之後,適應面會到達另一個高度。

RPC

加入IDL
在TestTwoConsole工程裏增加新文件 DataTransport.idl

import "oaidl.idl";
import "ocidl.idl";

typedef struct tagRPCOLEMESSAGE_forTrans
    {
    unsigned long dataRepresentation;
    ULONG iMethod;
    ULONG rpcFlags;
    } 	RPCOLEMESSAGEFORTRANS;

[
	uuid("9BC19A8B-01D2-419e-82AC-11B0AFF691A3"),
	version(1.0)
]

interface DataTransPort
{
	int TransmitRPCData(CLSID * pclsid, IID * piid, [in, out] RPCOLEMESSAGEFORTRANS * msg,
		[in] int nInSize,
		[in, size_is(nInSize)] char * pInBuffer,
		[out] int * pOutSize,
		[out, size_is(,*pOutSize)] char ** ppOutBuffer
		);
}

DataTransport.acf文件

[
	implicit_handle(handle_t TransPort_Binding)
]

interface DataTransPort
{
}

在工程編譯DataTransport.idl 生成

  • DataTransport_h.h
  • DataTransport_c.c
  • DataTransport_s.c

服務器

製作CRPCChannelServer, 關鍵代碼:
啓動RPC服務並等待連接,採用LPC連接方式,可以更換成 TCP/IP,PIPE

RpcServerUseProtseqEp(
		(RPC_WSTR)L"ncalrpc"
		, RPC_C_PROTSEQ_MAX_REQS_DEFAULT
		, (RPC_WSTR)L"comsrvRpc"
		, NULL);
// 注意:從Windows XP SP2開始,增強了安全性的要求,如果用RpcServerRegisterIf()註冊接口,客戶端調用時會出現
	// RpcExceptionCode() == 5,即Access Denied的錯誤,因此,必須用RpcServerRegisterIfEx帶RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH標誌
	// 允許客戶端直接調用
RpcServerRegisterIfEx(DataTransPort_v1_0_s_ifspec, NULL, NULL, RPC_IF_ALLOW_CALLBACKS_WITH_NO_AUTH, 0, NULL);
	// block
	RPC_STATUS result = RpcServerListen(1, 20, FALSE);

轉移數據到類實例中繼續執行並返回

int TransmitRPCData( 
					CLSID *pclsid,
					IID *piid,
					/* [out][in] */ RPCOLEMESSAGEFORTRANS *msg,
					/* [in] */ int nInSize,
					/* [size_is][in] */ unsigned char *pInBuffer,
					/* [out] */ int *pOutSize,
					/* [size_is][size_is][out] */ unsigned char **ppOutBuffer){

	RPCOLEMESSAGE rpcmsg = {0};

	rpcmsg.Buffer = midl_user_allocate(nInSize);
	memcpy(rpcmsg.Buffer, pInBuffer, nInSize);

	rpcmsg.cbBuffer = nInSize;
	rpcmsg.dataRepresentation = msg->dataRepresentation;
	rpcmsg.iMethod = msg->iMethod;
	rpcmsg.rpcFlags = msg->rpcFlags;

	CRPCChannelServer::Instance().TransmitRPCData(pclsid, piid, &rpcmsg);

	msg->dataRepresentation = rpcmsg.dataRepresentation;
	msg->iMethod = rpcmsg.iMethod;
	msg->rpcFlags = rpcmsg.rpcFlags;
	
	*pOutSize = rpcmsg.cbBuffer;
	*ppOutBuffer = (unsigned char*)midl_user_allocate(rpcmsg.cbBuffer);	
	memcpy(*ppOutBuffer, rpcmsg.Buffer, rpcmsg.cbBuffer);
	
	rpcmsg.Buffer = NULL;
	
	return 0;
}

類中的關鍵代碼

HRESULT TransDataArrival(CLSID * pclsid, IID * piid, RPCOLEMESSAGE * pMessage){

			RPCOLEMESSAGE * pMsg = (RPCOLEMESSAGE*)pMessage;
			CComQIPtr<IRpcStubBuffer> spStub;
			HRESULT hr = S_OK;
			CComQIPtr<IPSFactoryBuffer> spPsFactoryBuffer;
			if(FAILED(hr = CoGetClassObject(*piid, CLSCTX_ALL, NULL, IID_IPSFactoryBuffer, (void**)&spPsFactoryBuffer))) return hr;
			CComPtr<IUnknown> spUnk;
			if(FAILED(hr = spUnk.CoCreateInstance(*pclsid))) return hr;
			if(FAILED(hr = spPsFactoryBuffer->CreateStub(*piid, spUnk, &spStub))) return hr;
			if(FAILED(hr = spStub->Connect(spUnk))) return hr;	
 			CComQIPtr<IRpcChannelBuffer> spChnn;

			CComObject<CEmptyChannelImpl> * p;
			CComObject<CEmptyChannelImpl>::CreateInstance(&p);

			spChnn = p;
			if(FAILED(hr = spStub->Invoke(pMsg, spChnn))) return hr;

			return S_OK;
		}

客戶端關鍵代碼

綁定接口


#include "DataTransport_h.h"
#include "DataTransport_c.c"

class CRpcContext{
public:
	RPC_WSTR pszStringBinding;
	RPC_STATUS status;
	CRpcContext():status(0){
		RpcStringBindingCompose(
			NULL
			, (RPC_WSTR)L"ncalrpc"
			, (RPC_WSTR)NULL
			, (RPC_WSTR)L"comsrvRpc"
			, NULL,
			&pszStringBinding
			);

		// 綁定接口,這裏要和 test.acf 的配置一致,那麼就是test_Binding
		status = RpcBindingFromStringBinding(pszStringBinding, &TransPort_Binding);
	}
	
	
	~CRpcContext(){
		// 釋放資源
		RpcStringFree(&pszStringBinding);
		RpcBindingFree(&TransPort_Binding);
	}
};

static CRpcContext rpcConext;

IRpcChannelBuffer 發送請求

//////
STDMETHODIMP CRPCChannelForRPCImpl::SendReceive( RPCOLEMESSAGE *pMessage,ULONG *pStatus )
RpcTryExcept
	{
		unsigned char * pBuffer = NULL;
		int retsize = 0;
		
		int nrSize = TransmitRPCData(&data.clsid, &data.iid, &sendmsg, pMessage->cbBuffer, pSendBuffer, &retsize, &pBuffer);

		FreeBuffer(pMessage);
		pMessage->dataRepresentation = sendmsg.dataRepresentation;
		pMessage->rpcFlags = sendmsg.rpcFlags;
		pMessage->cbBuffer = retsize;
		GetBuffer(pMessage, IID_NULL);
		memcpy(pMessage->Buffer, pBuffer, retsize);
		midl_user_free(pBuffer);
		if(pStatus){
			*pStatus = 0;
		}
	}
	RpcExcept(1)
	{
		printf("RPC Exception %d\n", RpcExceptionCode());
	}
	RpcEndExcept

完成之後,現在的這種結構除了不能服務器調用客戶端的接口之外,客戶端可以跨任何權限去調用服務器了

缺點

COM本身需要依賴很多系統後臺提供的內容,調用也是有一定代價的(在執行速度要求高的地方可能會比較喫力, 但是OPC Server 也是工業用的COM形式的服務器,可能速度沒那麼快吧),在開源框架漫天飛的當下,似乎顯得有些落伍了,而且不容易入門。但是就擴展性和低耦合而言,無疑是足夠誘人了,從避免重複寫代碼的角度,COM已經表達的很完美了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章