跨權限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已經表達的很完美了。