跨权限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已经表达的很完美了。
3万+

被折叠的 条评论
为什么被折叠?



