RTX-Client插件开发指南(官方发布)

 1插件开发前准备

需要安装以下文件:

RTXC2006Beta02

RTXClient2006 SDKBeta02

开发RTX插件需要以下几个步骤:

(1)创建插件工程

(2)RTX客户端里界面上体现

(3)交互控制

(4)数据传输

(5)打包发布

下面是开发一个程序共享插件的步骤,通过这个插件开发案例来讲解开发RTX插件过程。

2、创建一个插件工程

创建插件工程,首先把RTXC SDK目录中wizard目录下的RTXCModuleAW.awx文件拷至VC安装目录Microsoft Visual Studio\COMMON\MSDev98\Template\ ,然后在VC中创建一个插件工程。

第一步:创建一个AppSharePlugin 工程 如图所示

rtx插件开发

第二步:设置插件的基本信息,生成RTX插件框架代码。

rtx插件开发2

第三步:设置插件内部绑定的事件,选择需要的特性。

rtx插件开发3

以上三个步骤完成后,会自动生成如下函数:

rtx插件开发4   rtx插件开发5

这些自动生成的代码,已经定义了RTXC的接口函数,一个RTX插件的基本框架就这么轻松的搭建成功了,开发者只需要在这些接口函数里添加自己的功能即可。
创建完成一个插件工程后,需要在RTX的客户端界面能体现出该插件,如菜单、面板、TAB或者在RTXC其他的界面元素上;程序共享插件主要在RTXC的菜单上增加一个新的菜单项来体现

3、界面设计

程序共享一般需要在RTX客户端的菜单中添加“程序共享”的菜单项,操作步骤如下:

第一步:设置动态菜单相关的内容。

首先在Stdafx.h文件中添加如下代码:

#import ”ClientObjects.tlb” raw_interfaces_only no_namespace, named_guids

#include ”RTXCModuleIds.h”

在插件中实现动态的添加和删除菜单,就必须包含ClientOjbect.tlb和RTXModuleIds.h 这两个文件,这两个文件分别位于SDK安装目录下的TLB和INCLUDE目录下,用户可以根据实际情况,设置文件的相对路径。

然后在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(CAppSharePluginModule, MenuSink)

return pThis->OnInvoke(UIType, Id, Parameter);

}

STDMETHOD(OnQueryState)(enum RTXC_UI_TYPE UIType, long Id, VARIANT

Parameter, BSTR* Text, enum RTXC_UI_ITEM_STATE* State)

{

METHOD_PROLOGUE(CAppSharePluginModule, MenuSink)

return pThis->OnQueryState(UIType, Id, Parameter, Text, State);

}

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方法

DELEGATE_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 Parameter)

{

return S_OK;

}

第二步:添加动态菜单。

在 AppSharePluginModule.cpp 文件中的OnLoad函数中,实现添加菜单的功能,

代码如下:

// 添加菜单,其中ID_BENGIN_SCREEN_SHARE为自定义的菜单项的ID

IRTXCModulePtr& module = m_ptrRoot->Module[RTX_MODULE_IDENTIFIER_CLIENT_OBJS];

IClientObjectsModulePtr ClientObjects = module;

CComPtr<IRTXCMenu> ptrMenu;

 

if(FAILED(ClientObjects->get_Object(_bstr_t(_T(“RTXCMenu”)),(IDispatch**)&ptrMenu))

|| ptrMenu == NULL)

{

throw RTX_UNSPECIFIC_ERROR;

}

// 在主窗口的操作菜单中添加项

if (FAILED(ptrMenu->AddItem(VARIANT_FALSE, -1, RTXC_UI_TYPE_MAINFRAME_ACTION,

ID_BENGIN_SCREEN_SHARE, &m_xMenuSink, _T(“应用程序共享”), NULL,

RTXC_MENU_ITEM_SEPARATOR_BELOW, 3, VARIANT_FALSE)))

{

throw RTX_UNSPECIFIC_ERROR;

}

// 在组织架构里的用户上的右键菜单中添加项

if (FAILED(ptrMenu->AddItem(VARIANT_FALSE, -1, RTXC_UI_TYPE_ORG_STRUCT_USER,

ID_BENGIN_SCREEN_SHARE, &m_xMenuSink, _T(“应用程序共享”), NULL,

RTXC_MENU_ITEM_SEPARATOR_BELOW, 3, VARIANT_FALSE)))

{

throw RTX_UNSPECIFIC_ERROR;

}

 

// 在IM 窗口添加第三方菜单

if (FAILED(ptrMenu->AddItem(VARIANT_FALSE, -1, RTXC_UI_TYPE_IM_THIRDPARTY,

ID_BENGIN_SCREEN_SHARE, &m_xMenuSink, _T(“应用程序共享”), NULL,

RTXC_MENU_ITEM_SEPARATOR_BELOW, 3, VARIANT_FALSE)))

{

throw RTX_UNSPECIFIC_ERROR;

}

到这里,已经完成了向RTX客户端添加菜单的功能,该插件运行之后,会在RTX客户端添加一个新的菜单项“应用程序共享”,如下图:

rtx插件开发6                rtx插件开发7

在主面板菜单中添加新菜单         图在用户右键菜单中添加新的菜单

rtx插件开发8

在会话窗口里添加第三方菜单

完成界面体现之后,接下来应该做的用户之间的交互,当一方提出邀请另一方来进行程序共享的时候,另一方可以选择接受或者拒绝;这些操作都是需要用户自己在程序中控制,并且可以采用一定的方式表现出来。

4、交互控制

上面已经实现了插件在RTX客户端添加菜单的功能,用户在点击菜单的时候,就可以发起一次程序共享的邀请,具体操作的步骤如下:

第一步:得到当前选中的用户名。

在OnQueryState和 OnInvoke函数中,都可以得到当前选中的用户名;如下代码是在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)

{

// 在组织架构里的用户上的右键菜单中得到选中的用户

CString 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==OBJECT_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<CString> vecParticipants;

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, iIndex - iStart);

iStart = iIndex + 1;

iIndex = str.Find(_T(“;”), iStart);

}

else

{

bBreak = 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);

}

}

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)

{

CString 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;

}

完成以上代码之后,当检测到用户离线的时候,可以让“应用程序共享”菜单项变为灰色,其效果如下图所示:

rtx插件开发9

第三步:检测对方是否安装插件

在发起邀请之前,需要检测对方是否安装过该插件,在响应程序共享的菜单项的时候,向对方发送一个确认数据包,如果对方没有安装插件,就会立即返回一个发送结果,根据发送的结果,可以得到对方是否安装过插件。如下代码:

#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;

// 得到当前选中的用户名,代码如上

……

// 发送一个数据包给对方,测试一下对方是否存在该插件

IRTXCDataPtr& ptrSendData = m_ptrRoot->CreateRTXCData();

ptrSendData->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事件,响应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;

}

第四步:发送邀请

当点击菜单的时候,会执行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 strSelectName;

// 得到当前选中的用户名,代码如上

……

//不能给自己发起程序共享的邀请

if(strSelectName == (LPCTSTR)m_ptrRoot->Account)

{

return S_FALSE

}

// 发送一个数据包给对方,来邀请对方接受程序共享

IRTXCDataPtr& ptrSendData = m_ptrRoot->CreateRTXCData();

ptrSendData->SetString(SRC_SENDER, _bstr_t((LPCTSTR)m_ptrRoot->Account));

 

// SCREEN_SHARE_CMD_ INVIT   标识发起邀请

ptrSendData->SetString(SRC_KEY, _bstr_t(SCREEN_SHARE_CMD_INVIT));

 

m_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& ptrData = m_ptrModuleSite->GetData(Key, VARIANT_TRUE);

_bstr_t& command = ptrData->GetString(SRC_KEY);

 

// 接收到程序共享的邀请

if(command == _bstr_t(SCREEN_SHARE_CMD_INVITE))

{

CString strSenderName;

strSenderName = (LPCTSTR)ptrData->GetString(SRC_SENDER);

// 可以弹出一个自定义TIP的窗口,来提,如下图

rtx插件开发10

 

(这是一个自定义的对话框TIP,当用户接收到邀请的时候,会在右下角弹出这个TIP让用户进行选择接受和拒绝。)

// 当用户接受邀请,向发送方发送接受邀请的数据包

IRTXCDataPtr& ptrSendData = m_ptrRoot->CreateRTXCData();

ptrSendData->SetString(SRC_SENDER, _bstr_t((LPCTSTR)m_ptrRoot->Account));

 

// SCREEN_SHARE_CMD_ACCEPT   标识接收邀请

ptrSendData->SetString(SRC_KEY, _bstr_t(SCREEN_SHARE_CMD_ACCEPT));

 

m_ptrModuleSite->SendData(_bstr_t((LPCTSTR) strSenderName), ptrSendData,

RTXC_SEND_DATA_FLAG_FILTERING_IS_FORBIDDEN);

}

}

5、收发数据的处理

程序共享的网络传输是采用两种传输方式:RTX传输通道和P2P通道;这两种方式同时存在,传输数据量比较小的命令和控制信息,采用RTX传输通道来实现,这样可以实现安全和稳定的传输;针对数据量比较大的屏幕压缩数据和鼠标位置信息等,将采用P2P传输,这样的传输高效,占用的资源比较少。

RTX传输通道的使用方式,是通过m_ptrModuleSite->SendData(…)来实现发送数据,当接收到数据的时候,会响应 OnDataReceived 函数,其具体操作,可以参看“交互控制”部分的描述;在此,主要说明P2P通道的使用,其步骤如下:

第一步:创建P2P通道管理器。

在 AppSharePluginModule.h 文件中添加P2P通道管理接口指针的定义:

IP2PMgrExPtr m_ptrP2PMgrEx;

CRTXP2PExEvent* m_P2MgrExEventSink;

 

typedef map<CString, int> 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);

在 AppSharePluginModule.cpp 文件中的OnLoginResult函数中实现对该指针的初始化:

HRESULT CAppSharePluginModule::OnLoginResult(RTXC_LOGIN_RESULT Result)

{

RTX_TRY

{

if(Result == RTXC_LOGIN_RESULT_OK)

{

// 登陆成功,初始化P2P通道

m_ptrP2PMgrEx = NULL;

m_ptrP2PMgrEx = m_ptrRoot->CreateP2PMgr();

 

if(m_ptrP2PMgrEx != NULL && m_ptrRoot != NULL)

{

m_ptrP2PMgrEx->Init(m_ptrRoot);

m_ptrP2PMgrEx->SetVerify(_bstr_t(_T(“p2p_appshare”)));

m_P2PMgrExEventSink = CRTXP2PExEvent::CreateObject();

if(m_P2PMgrExEventSink)

{

m_P2PMgrExEventSink->evt_OnRecv.bind(this,&CAppSharePluginModule::OnRecv);

m_P2PMgrExEventSink->evt_OnCreateResult.bind(this,&CAppSharePluginModule::OnCreateResult);

m_P2PMgrExEventSink->evt_OnSend.bind(this,&CAppSharePluginModule::OnSend);

if (!m_P2PMgrExEventSink->Advise(m_ptrP2PMgrEx))

{

throw RTX_UNSPECIFIC_ERROR;

}

}

}

}

RTX_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, BYTE *pData, LONG DataLen)

{

}

第二步:创建一个P2P通道

在程序共享发起方接收到对方接受邀请的命名之后,发起方会跟接受方建立一个P2P通道,在 OnDataReceived 函数中实现创建P2P通道,如下代码:

void CAppSharePluginModule::OnDataReceived(LPCTSTR Key)

{

RTXCDataPtr& ptrData = m_ptrModuleSite->GetData(Key, VARIANT_TRUE);

_bstr_t& command = ptrData->GetString(SRC_KEY);

 

// 接收到程序共享的邀请

if(command == _bstr_t(SCREEN_SHARE_CMD_INVITE))

{

// 代码如上

……..

}

// 对方接受了发起方的邀请

else if(command == _bstr_t(SCREEN_SHARE_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 CAppSharePluginModule::OnCreateResult(LONG nSeq,BSTR bstrAccount, LONG nError)

{

CString strAccount(bstrAccount);

if(nError)

{

// 当前创建的P2P通道成功!把该通道的序列号保存在m_mapP2PChannelSeq中

strAccount.MakeLower();

m_mapP2PChannelSeq[strAccount] = nSeq;

m_strRecvName = strAccount;

}

else

{

// 创建P2P通道失败

}

}

 

第三步:使用P2P通道

使用P2P通道来实现发送数据,如下程序代码:

// 发送程序共享数据包

void CAppSharePluginModule::SendScreenDataBuffer(CString strRecvName, BYTE * pBuffer,

int nLength)

{

RTX_TRY

{

strRecvName.MakeLower();

MAP_P2PCHANNEL_SEQ::iterator& it2 = m_mapP2PChannelSeq.find(strRecvName);

 

// 先得到P2P通道的序列号

if (it2 != m_mapP2PChannelSeq.end())

{

int & nP2PChannelSeq = it2->second;

if(m_ptrP2MgrEx != NULL && nP2PChannelSeq != -1)

{

// P2P通道已经打通

long nResult = -1;

nResult = m_ptrP2MgrEx->Send(nP2PChannelSeq, pBuffer, nLength);

}

else

{

// P2P通道没有开通

}

}

}

RTX_CATCH_ALL(;);

}

 

当通道接收到数据的时候,会触发 OnRecv 消息,响应 OnRecv 函数,针对接收到的数据处理,如下代码:

// 接收到P2P通道的数据

void CAppSharePluginModule::OnRecv(LONG nSeq, BYTE *pData, LONG DataLen)

{

int nP2PChannelSeq = -1;

// 先找到接收者对应得P2P通道的

MAP_P2PCHANNEL_SEQ::iterator& it2 = m_mapP2PChannelSeq.find(m_strRecvName);

if (it2 != m_mapP2PChannelSeq.end())

{

nP2PChannelSeq = it2->second;

}

else

{

return;

}

 

if(nP2PChannelSeq == nSeq)

{

BYTE * pUins = new BYTE[DataLen];

if(pUins)

{

memcpy(pUins, pData, DataLen);

// 可以处理该数据包了

delete[] pUins;

pUins = NULL;

}

}

}

6、插件打包发布

RTX插件开发结束之后,需要用 RPIPackager.exe进行插件打包工作,打包完成后,会生成一个RPI格式的文件,这个文件就是发布插件的文件。RPI格式的文件与RTXC客户端文件RPISetup.exe自动关联,用户双击运行RPI文时,RPISetup.exe会自动读取该插件的信息,并且加载到RTXC中。

7、后语

本文涉及到的功能点,仅限于开发“应用程序共享插件”所用到的部分RTXC SDK功能,并不包括RTXC SDK所有功能,开发者如果需要了解更多的RTXC SDK的功能,请参考《RTX Client SDK 帮助》。

随机推荐

发表评论