1、腾讯通 RTX 插件开发指南目 录1、开发前准备2、创建插件工程3、界面上的体现4、交互控制5、收发数据处理6、插件打包发布7、后语1、开发前准备需要安装如下文件:RTXC2006Beta02,RTXClient2006 SDKBeta02 ;开发插件的过程一般是要经历一下几个步骤:创建插件工程,RTX 客户端里界面上体现,交互控制,数据传输,打包发布;以下的用例是开发一个程序共享的插件,通过介绍开发程序共享的插件,来讲解插件的一般开发过程。2、创建插件工程创建插件工程,首先把 RTXC SDK 目录中 wizard 目录下的 RTXCModuleAW.awx文件拷至 VC 安装目录 Micr
2、osoft Visual StudioCOMMONMSDev98Template ,然后在 VC 中创建一个插件工程。第一步:创建一个 AppSharePlugin 工程。第二步:设置插件的信息。第三步:设置插件内部绑定的事件。完成以上三个步骤之后,可以自动生成如下函数:这些自动生成的代码,已经定义了 RTXC 的接口函数,一个插件的基本框架已经搭建成功,用户只需要在这些接口函数里添加自己的功能。创建完成一个插件工程之后,就需要考虑在 RTX 的客户端界面能体现出该插件,如菜单、面板、TAB 或者在 RTXC 其他的界面元素上;程序共享主要在 RTXC 的菜单上增加一个新的菜单项来体现,其他的
3、体现方式,可以参看 RTXC SDK 文档。3、界面上的体现程序共享一般需要在 RTX 客户端的菜单中添加“程序共享”的菜单项,操作步骤如下:第一步:设置动态菜单相关的内容。首先在 Stdafx.h 文件中添加如下代码:#import “ClientObjects.tlb“ raw_interfaces_only no_namespace, named_guids#include “RTXCModuleIds.h“在插件中实现动态的添加和删除菜单,就必须包含 ClientOjbect.tlb 和RTXModuleIds.h 这两个文件,这两个文件分别位于 SDK 安装目录下的 TLB 和 IN
4、CLUDE目录下,用户可以根据实际情况,设置文件的相对路径。然后在 AppSharePluginModule.h 文件中增加菜单对象的定义和菜单响应的函数,如下:IRTXCMenuPtr m_pMenu; / 定义 RTXC 菜单对象IRTXCUICommand* m_pUICmd;定义菜单响应函数的接口:BEGIN_DUAL_INTERFACE_PART(MenuSink, IRTXCUICommand)STDMETHOD(OnInvoke)(enum RTXC_UI_TYPE UIType, long Id, VARIANT Parameter)METHOD_PROLOGUE(CAppSh
5、arePluginModule, MenuSink)return pThis-OnInvoke(UIType, Id, Parameter);STDMETHOD(OnQueryState)(enum RTXC_UI_TYPE UIType, long Id, VARIANTParameter, BSTR* Text, enum RTXC_UI_ITEM_STATE* State) METHOD_PROLOGUE(CAppSharePluginModule, MenuSink) return pThis-OnQueryState(UIType, Id, Parameter, Text, Stat
6、e);END_DUAL_INTERFACE_PART(MenuSink)定义菜单响应的接口函数:HRESULT OnInvoke(enum RTXC_UI_TYPE UIType, long Id, VARIANT Parameter);HRESULT OnQueryState(enum RTXC_UI_TYPE UIType, long Id, VARIANT Parameter, BSTR* Text,enum RTXC_UI_ITEM_STATE* State);在 AppSharePluginModule.cpp 文件中,需要添加如下代码:/ 实现标准的 IDispatch方法DELE
7、GATE_DUAL_INTERFACE(CAppSharePluginModule, MenuSink)/ 实现菜单响应的实现函数:HRESULT CAppSharePluginModule:OnQueryState(enum RTXC_UI_TYPE UIType, long Id,VARIANT Parameter, BSTR* Text, enum RTXC_UI_ITEM_STATE* State)return S_OK;HRESULT CAppSharePluginModule:OnInvoke(enum RTXC_UI_TYPE UIType, long Id,VARIANT Pa
8、rameter)return S_OK;第二步:添加动态菜单。在 AppSharePluginModule.cpp 文件中的 OnLoad 函数中,实现添加菜单的功能,代码如下:/ 添加菜单,其中 ID_BENGIN_SCREEN_SHARE为自定义的菜单项的 IDIRTXCModulePtrIClientObjectsModulePtr ClientObjects = module; CComPtr ptrMenu;if(FAILED(ClientObjects-get_Object(_bstr_t(_T(“RTXCMenu“),(IDispatch*)/ 在主窗口的操作菜单中添加项if (
9、FAILED(ptrMenu-AddItem(VARIANT_FALSE, -1, RTXC_UI_TYPE_MAINFRAME_ACTION,ID_BENGIN_SCREEN_SHARE, / 在组织架构里的用户上的右键菜单中添加项if (FAILED(ptrMenu-AddItem(VARIANT_FALSE, -1, RTXC_UI_TYPE_ORG_STRUCT_USER,ID_BENGIN_SCREEN_SHARE, / 在 IM 窗口添加第三方菜单if (FAILED(ptrMenu-AddItem(VARIANT_FALSE, -1, RTXC_UI_TYPE_IM_THIRDP
10、ARTY,ID_BENGIN_SCREEN_SHARE, 至此,已经完成了向 RTX 客户端添加菜单的功能,该插件运行之后,会在 RTX 客户端添加一个新的菜单项“应用程序共享” ,如下图:图1 在主面板菜单中添加新菜单 图2 在用户右键菜单中添加新的菜单图3 在会话窗口里添加第三方菜单完成界面体现之后,接下来应该做的用户之间的交互,当一方提出邀请另一方来进行程序共享的时候,另一方可以选择接受或者拒绝;这些操作都是需要用户自己在程序中控制,并且可以采用一定的方式表现出来。4、交互控制以上已经实现了插件在 RTX 客户端添加菜单的功能,用户在点击菜单的时候,就可以发起一次程序共享的邀请,具体操作
11、的步骤如下:第一步:得到当前选中的用户名。在 OnQueryState 和 OnInvoke 函数中,都可以得到当前选中的用户名;如下代码是在 OnQueryState 函数中得到当前选中的用户:HRESULT CAppSharePluginModule:OnQueryState(enum RTXC_UI_TYPE UIType, long Id, VARIANTParameter, BSTR* Text, enum RTXC_UI_ITEM_STATE* State)if( Id = ID_BENGIN_SCREEN_SHARE)/ 在组织架构里的用户上的右键菜单中得到选中的用户CStrin
12、g strSelectName;if(UIType = RTXC_UI_TYPE_ORG_STRUCT_USER)/ 得到选中的用户名if(Parameter.vt = VT_DISPATCH)IRTXCDataCollectionPtr ptrReceivers = Parameter.pdispVal;int nCount = ptrReceivers-Count; if(nCount 0)IRTXCDataPtr pstrRtxData=ptrReceivers-GetItem(1);long nType=pstrRtxData-GetLong(RDK_TYPE);if(nType=OB
13、JECT_RTX_BUDDY)_bstr_t bstrUserName=pstrRtxData-GetString(RDK_VALUE);strSelectName=OLE2T(bstrUserName);else if(Parameter.vt = VT_BSTR)_bstr_t bstrUserName= Parameter. bstrVal;strSelectName=OLE2T(bstrUserName);/ 在 IM 窗口里的第三方菜单中得到当前会话窗口的用户else if(UIType = RTXC_UI_TYPE_IM_THIRDPARTY)/ 得到选中的用户名vector ve
14、cParticipants;IRTXCSessionPtr ptrSession = Parameter.pdispVal;_bstr_t bstrParticipants = ptrSession-Participant;CString str = (LPCTSTR)bstrParticipants;int iStart = 0;int iIndex = str.Find(_T(“;“);BOOL bBreak = FALSE;while(!bBreak)CString strTmpToken ;if (iIndex != -1)strTmpToken = str.Mid(iStart, i
15、Index - iStart);iStart = iIndex + 1;iIndex = str.Find(_T(“;“), iStart); elsebBreak = TRUE;strTmpToken = str.Mid(iStart, _tcslen(LPCTSTR)str)-iStart);if (strTmpToken.IsEmpty()return S_OK;/ 检查当前用户是否是自己if (strTmpToken.CompareNoCase(LPCTSTR)m_ptrRoot-Account) != 0)vecParticipants.push_back(strTmpToken);
16、strSelectName = vecParticipants.at(0);return S_OK;第二步:设置菜单项的状态当选中的用户离线的时候,菜单会变成 Disable 状态,可以在 OnQueryState 函数中设置菜单的状态,如下代码:HRESULT CAppSharePluginModule:OnQueryState(enum RTXC_UI_TYPE UIType, long Id,VARIANT Parameter, BSTR* Text, enum RTXC_UI_ITEM_STATE* State)if( Id = ID_BENGIN_SCREEN_SHARE)CStri
17、ng strSelectName;/ 得到选中的用户名,如上代码。if ( RTX_PRESENCE_OFFLINE =m_ptrRoot-Presence-RTXPresence_bstr_t(strSelectName) )/ 选中的用户离线,菜单设置为 Disable 状态*State = RTXC_UI_ITEM_STATE_DISABLED;return S_OK;return S_OK;完成以上代码之后,当检测到用户离线的时候,可以让“应用程序共享”菜单项变为灰色,其效果如下图所示:第三步:检测对方是否安装插件在发起邀请之前,需要检测对方是否安装过该插件,在响应程序共享的菜单项的时
18、候,向对方发送一个确认数据包,如果对方没有安装插件,就会立即返回一个发送结果,根据发送的结果,可以得到对方是否安装过插件。如下代码:#define SCREEN_SHARE_TEST _T(“ScrnShareTest“)HRESULT CAppSharePluginModule:OnInvoke(enum RTXC_UI_TYPE UIType, long Id,VARIANT Parameter)if(Id = ID_BENGIN_SCREEN_SHARE)CString strSelectName;/ 得到当前选中的用户名,代码如上。/ 发送一个数据包给对方,测试一下对方是否存在该插件I
19、RTXCDataPtrptrSendData-SetString(SRC_SENDER, _bstr_t(LPCTSTR)m_ptrRoot-Account);ptrSendData-SetString(SRC_KEY, _bstr_t(SCREEN_SHARE_TEST);m_ptrModuleSite-SendData(_bstr_t(LPCTSTR) strSelectName), ptrSendData,RTXC_SEND_DATA_FLAG_FILTERING_IS_FORBIDDEN);return S_OK;在发送数据包之后,会触发 evt_OnSendDataResult 事件
20、,响应 OnSendDataResult函数,如下代码:HRESULT CAppShareModule:OnSendDataResult( RTXC_MODULE_SEND_DATA_RESULT Result,Const VARIANT* Extra)if(Result = RTXC_MODULE_SEND_DATA_RESULT_NOT_EXIST)/对方不存在本插件CString strRecverName;strRecverName = (LPCTSTR)_bstr_t(Extra-bstrVal);/ strRecverName用户没有安装插件!return S_OK;第四步:发送邀
21、请。当点击菜单的时候,会执行 OnInvoke 函数,可以在该函数中发起程序共享的邀请,如下代码:#define SCREEN_SHARE_CMD_INVITE _T(“ScrnShareCmdInvite“)#define SCREEN_SHARE_CMD_ACCEPT _T(“ScrnShareCmdAccept“)HRESULT CAppSharePluginModule:OnInvoke(enum RTXC_UI_TYPE UIType, long Id,VARIANT Parameter)if(Id = ID_BENGIN_SCREEN_SHARE)CString strSelect
22、Name;/ 得到当前选中的用户名,代码如上。/不能给自己发起程序共享的邀请if(strSelectName = (LPCTSTR)m_ptrRoot-Account)return S_FALSE/ 发送一个数据包给对方,来邀请对方接受程序共享IRTXCDataPtrptrSendData-SetString(SRC_SENDER, _bstr_t(LPCTSTR)m_ptrRoot-Account);/ SCREEN_SHARE_CMD_ INVIT 标识发起邀请ptrSendData-SetString(SRC_KEY, _bstr_t(SCREEN_SHARE_CMD_INVIT);m_
23、ptrModuleSite-SendData(_bstr_t(LPCTSTR) strSelectName), ptrSendData,RTXC_SEND_DATA_FLAG_FILTERING_IS_FORBIDDEN);return S_OK;第五步:接受邀请。接受方接收到发送的程序共享邀请之后,会触发 evt_OnDataReceived 事件,响应 OnDataReceived 函数,用户在此函数中,来处理该邀请,如下代码:void CAppSharePluginModule:OnDataReceived(LPCTSTR Key)RTXCDataPtr_bstr_t/ 接收到程序共享的
24、邀请if(command = _bstr_t(SCREEN_SHARE_CMD_INVITE)CString strSenderName;strSenderName = (LPCTSTR)ptrData-GetString(SRC_SENDER);/ 可以弹出一个自定义 TIP的窗口,来提示用户进行选择,如下图。/ 当用户接受邀请,向发送方发送接受邀请的数据包IRTXCDataPtrptrSendData-SetString(SRC_SENDER, _bstr_t(LPCTSTR)m_ptrRoot-Account);/ SCREEN_SHARE_CMD_ACCEPT 标识接收邀请ptrSen
25、dData-SetString(SRC_KEY, _bstr_t(SCREEN_SHARE_CMD_ACCEPT);m_ptrModuleSite-SendData(_bstr_t(LPCTSTR) strSenderName), ptrSendData,RTXC_SEND_DATA_FLAG_FILTERING_IS_FORBIDDEN);下图是一个自定义的对话框 TIP,当用户接收到邀请的时候,会在右下角弹出这个 TIP让用户进行选择接受和拒绝。5、收发数据处理程序共享的网络传输是采用两种传输方式:RTX 传输通道和 P2P 通道;这两种方式同时存在,传输数据量比较小的命令和控制信息,采用
26、 RTX 传输通道来实现,这样可以实现安全和稳定的传输;针对数据量比较大的屏幕压缩数据和鼠标位置信息等,将采用 P2P 传输,这样的传输高效,占用的资源比较少。RTX 传输通道的使用方式,是通过 m_ptrModuleSite-SendData()来实现发送数据,当接收到数据的时候,会响应 OnDataReceived 函数,其具体操作,可以参看“交互控制”部分的描述;在此,主要说明 P2P 通道的使用,其步骤如下:第一步:创建 P2P 通道管理器。在 AppSharePluginModule.h 文件中添加 P2P 通道管理接口指针的定义:IP2PMgrExPtr m_ptrP2PMgrEx
27、;CRTXP2PExEvent* m_P2MgrExEventSink;typedef map MAP_P2PCHANNEL_SEQ;MAP_P2PCHANNEL_SEQ m_mapP2PChannelSeq;/ 定义一个接受者CString m_strRecvName;/ P2P 通道的事件响应函数void OnCreateResult(LONG nSeq,BSTR pAccount,LONG nError);void OnRecv(LONG nSeq,BYTE * pData, LONG DataLen);void OnSend(LONG nSeq, LONG nResult);在 App
28、SharePluginModule.cpp 文件中的 OnLoginResult 函数中实现对该指针的初始化:HRESULT CAppSharePluginModule:OnLoginResult(RTXC_LOGIN_RESULT Result)RTX_TRYif(Result = RTXC_LOGIN_RESULT_OK)/ 登陆成功,初始化 P2P通道m_ptrP2PMgrEx = NULL;m_ptrP2PMgrEx = m_ptrRoot-CreateP2PMgr();if(m_ptrP2PMgrEx != NULL m_ptrP2PMgrEx-SetVerify(_bstr_t(_
29、T(“p2p_appshare“);m_P2PMgrExEventSink = CRTXP2PExEvent:CreateObject();if(m_P2PMgrExEventSink)m_P2PMgrExEventSink-evt_OnRecv.bind(this,m_P2PMgrExEventSink-evt_OnCreateResult.bind(this,m_P2PMgrExEventSink-evt_OnSend.bind(this,if (!m_P2PMgrExEventSink-Advise(m_ptrP2PMgrEx)throw RTX_UNSPECIFIC_ERROR;RTX
30、_CATCH_ALL(return E_FAIL);return S_OK; 在 AppSharePluginModule.cpp 文件中添加事件响应函数:/ 返回创建通道的结果void CAppSharePluginModule:OnCreateResult(LONG nSeq,BSTR pAccount, LONG nError) / 返回发送数据的结果void CAppSharePluginModule:OnSend(LONG nSeq, LONG nResult) / 接收到 P2P通道的数据void CAppSharePluginModule:OnRecv(LONG nSeq, BY
31、TE *pData, LONG DataLen)第二步:创建一个 P2P 通道。在程序共享发起方接收到对方邀请之后,发起方会跟接受方建立一个 P2P 通道,在 OnDataReceived 函数中实现创建 P2P 通道,如下代码:void CAppSharePluginModule:OnDataReceived(LPCTSTR Key)RTXCDataPtr_bstr_t/ 接收到程序共享的邀请if(command = _bstr_t(SCREEN_SHARE_CMD_INVITE)/ 代码如上。/ 对方接受了发起方的邀请else if(command = _bstr_t(SCREEN_SHA
32、RE_CMD_ACCEPT)CString strSenderName;strSenderName = (LPCTSTR)ptrData-GetString(SRC_SENDER);/ 开始建立 P2P通道if(m_ptrP2PMgrEx)m_ptrP2PMgrEx-Create(_bstr_t(LPCTSTR) strSenderName);在创建 P2P 之后,需要去捕获 OnCreateResult 事件,得到当前创建 P2P 通道的结果,可以在如下函数中得到创建的 P2P 通道的序列号,该序列号可以唯一的标识一个 P2P 通道,如下代码:/ 返回创建通道的结果void CAppShar
33、ePluginModule:OnCreateResult(LONG nSeq,BSTR bstrAccount, LONG nError) CString strAccount(bstrAccount);if(nError)/ 当前创建的 P2P通道成功!把该通道的序列号保存在 m_mapP2PChannelSeq中 strAccount.MakeLower();m_mapP2PChannelSeqstrAccount = nSeq;m_strRecvName = strAccount;else/ 创建 P2P通道失败!第三步:使用 P2P 通道。使用 P2P 通道来实现发送数据,如下程序代码
34、:/ 发送程序共享数据包void CAppSharePluginModule:SendScreenDataBuffer(CString strRecvName, BYTE * pBuffer,int nLength)RTX_TRYstrRecvName.MakeLower();MAP_P2PCHANNEL_SEQ:iterator/ 先得到 P2P通道的序列号if (it2 != m_mapP2PChannelSeq.end()int if(m_ptrP2MgrEx != NULL nResult = m_ptrP2MgrEx-Send(nP2PChannelSeq, pBuffer, nLe
35、ngth);else/ P2P通道没有开通RTX_CATCH_ALL(;);当通道接收到数据的时候,会触发 OnRecv 消息,响应 OnRecv 函数,针对接收到的数据处理,如下代码:/ 接收到 P2P通道的数据void CAppSharePluginModule:OnRecv(LONG nSeq, BYTE *pData, LONG DataLen)int nP2PChannelSeq = -1;/ 先找到接收者对应得 P2P通道的MAP_P2PCHANNEL_SEQ:iteratorif (it2 != m_mapP2PChannelSeq.end()nP2PChannelSeq = i
36、t2-second;elsereturn;if(nP2PChannelSeq = nSeq)BYTE * pUins = new BYTEDataLen;if(pUins)memcpy(pUins, pData, DataLen);/ 可以处理该数据包了delete pUins;pUins = NULL; 6、插件打包发布插件开发完成之后,需要采用 RPIPackager.exe 文件进行插件打包,打包完成之后,生成 RPI 格式的文件,该文件就是发布插件的文件。RPI 格式的文件与 RTXC 客户端的RPISetup.exe 自动关联,用户运行 RPI 格式的文件的时候,RPISetup.exe 会读取该插件的信息,并且加载到 RTXC 系统中。7、后语本文档所涉及到的功能点,仅局限于在开发一个应用程序共享所用到 RTXC SDK的一些功能,并不包括 RTXC SDK 的所有功能点,开发者如果需要了解更多的 RTXC SDK 的功能,请参看RTX Client SDK 帮助文档。