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

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值