|
终于闲下来了,准备总结下之前Symbian上的一些经验,也算是告一个段落吧,由于工作原因基本上有半年多没有碰symbian了,已经跟不上Nokia的发展了,新的SDK也没有下下来试试,玩玩什么新的功能什么的,惭愧啊,嘿嘿,废话不说了,开始今天的主题,这也是我觉得在任何一个新平台上开发程序最先要解决的问题――调试环境。
先说下我使用到的symbian输出trace的几种方法,一是把trace直接通过控制台输出到stdout,但是因为屏幕太小看不了几行trace,小程序还能勉强使用,trace多了就很痛苦了,虽然可以加个getchar函数其等待我按键再往下跑,不过这样使用起来还是极其的不方便;二是打到文件里面,也有一个很麻烦的问题,为了获得良好的分析环境,肯定不能在手机上直接看打出来的trace,还是因为屏幕太小,又懒得每次都需要把文件发到电脑上;三是曾经在XX论坛上看到有人使用symbian提供的调试工具好像是GDB(懒的查证了),可以在PC上调试手机上跑的程序,搞了好一阵一直都没有成功过,就放弃了。碰巧当时研究了下蓝牙相关的东西,就想到干脆自己搞个方便点的trace输出功能吧:注册一个蓝牙服务,手机上跑的程序通过这个服务把trace输出到PC上,手机做为服务器,PC做为客户端在程序每次运行时连接一下就可以了,至于PC这边的客户端完全可以用超级终端来代替,这样对我这种写EXE的人来说再好不过了。
再说下蓝牙服务的相关概念,目前支持蓝牙的手机大多会支持几个标准的蓝牙服务,比如OPP(object push profile),FTP(file transfer profile)什么的,都是两个设备之间用来相互传送资料的,也有一些蓝牙耳机、拨号上网服务,蓝牙允许用户自定义服务,以便对端设备来访问,他们都是工作于蓝牙RFCOMM层之上的,RFCOMM是一个串口仿真协议,这样可以把某一个蓝牙服务虚拟为一个串口方便程序的编写。比如在蓝牙配对完成后,PC首先会去查询对方的SDP(Service Discovery Application Profile)服务,这其中有所有对端(这里就是手机)支持的服务的详细信息,PC得到这个信息后就会显示给用户对方有哪些服务,用户可以自由的选择使用哪些服务。这里举例说明我们的DEBUG服务,PC发现了我们在手机上注册的这个DEBUG串口服务(我们注册的当然可以是串口服务,标准中叫他SPP),将其显示给用户,用户在选择连接,在蓝牙链路连接成功后PC会将其虚拟为一个PC上的串口设备,这样我们就可以通过这个串口给手机通讯了。
现在看看我们要实现的这个功能,主要就是两个功能,一是创建一个蓝牙服务,能处理pc过来的连接请求,并建立蓝牙连接,断开后继续监听等待下一次连接,第二是提供一个send函数发数据就可以了。所以我将其分为一个父类CBtSvr来处理第一个问题,再写一个BtDbg子类来处理第二个问题。
先看看CBtSvr需要实现的功能,用一个活动对象来实现
class CBtSvr : public public CActive { public : // 当前服务器的状态 enum CBtBaseSvrStat { // 空闲状态 EWaitingToGetConnection, // 监听状态 EGettingConnection, // 连接状态 EInConnection, } ; CBtSvr(); virtual ~CBtSvr(){} ; // 类似消息处理的主循环 virtual void RunL(); // 启动服务器 virtual int StartL(); // 关闭服务器 virtual void CloseL(); // 取消当前提交的请求 virtual void DoCancel(); // 返回服务器当先状态当前状态 inline CBtBaseSvrStat Status(void) { return iStat; }; protected: // socket 服务器 RSocketServ sockSvr; // 监听socket RSocket listenSock; // 连接 socket RSocket connSock; // send状态标志 TRequestStatus iSendStatus; // 服务的当前状态 CBtBaseSvrStat iStat; TSockXfrLength iLen; TInt channelNum; protected: // 绑定服务,留给子类实现 virtual int BindL(void) = 0; // 监听 virtual int ListenL(void); // 接受连接请求 virtual int AcceptL(void); // 注册蓝牙服务中的Protocol段 virtual void BuildProtocolDescriptionL(CSdpAttrValueDES* aProtocolDescriptor, TInt aPort); // 设置蓝牙服务安全 void SetSecurityOnChannelL(TBool aAuthentication, TBool aEncryption, TBool aAuthorisation, TInt aChannel); // 注册蓝牙服务 int RegieterBlueToothServerL(const TDesC& KServiceName, TInt KSerialClassID); // 绑定蓝牙服务名 int BuildSeriVCe(const TDesC& ServiceName, TInt KSerialClassID); }; 构造函数,初始化状态并加入活动对象调度器 CBtSvr::CBtSvr() : iStat(EWaitingToGetConnection),CActive(0) { CActiveScheduler::Add(this); } 开启蓝牙debug服务 int CBtSvr::StartL() { // 如果当前状态不对或者已经提交了事件 if (iStat != EWaitingToGetConnection || IsActive()) { return -1; } // 建立绑定debug服务 if (BindL() < 0) { return -1; } // 开始监听 if (ListenL() < 0) { listenSock.Close(); sockSvr.Close(); return -1; } // 接受连接 if (AcceptL() < 0) { listenSock.Close(); sockSvr.Close(); return -1; } return 0; } // 监听函数 int CBtSvr::ListenL(void) { assert(iStat == EWaitingToGetConnection); // 只支持一个连接 if(listenSock.Listen(1) != KErrNone) { return -1; } return 0; } // 提交接受连接请求 int CBtSvr::AcceptL(void) { if (connSock.Open(sockSvr) != KErrNone) { return -1; } listenSock.Accept(connSock, iStatus); iStat = EGettingConnection; SetActive(); return 0; } // 实现CActive的doCancel函数供取消事件请求时调用 void CBtSvr::DoCancel() { if (!IsActive()) return; switch (iStat) { case EGettingConnection: listenSock.CancelAll(); break; case EInConnection: connSock.CancelAll(); break; default: break; } } // 服务器关闭函数 void CBtSvr::CloseL() { Cancel(); iStat = EWaitingToGetConnection; listenSock.Close(); sockSvr.Close(); } // 主事件循环 void CBtSvr::RunL() { if (iStatus != KErrNone) { // 出错处理 switch (iStat) { case EGettingConnection: iStat = EWaitingToGetConnection; break; case EInConnection: // 可能是对端断开连接 iStat = EWaitingToGetConnection; // 重新提交接受连接事件 AcceptL(); break; default: break; } } else { switch (iStat) { case EGettingConnection: // 连接建立成功 iStat = EInConnection; break; case EInConnection: // 收到数据不做任何处理 break; default: break; } } }
// 这个函数是设置蓝牙服务安全,没有仔细研究,simple中搬出来 void CBtSvr::SetSecurityOnChannelL(TBool aAuthentication, TBool aEncryption, TBool aAuthorisation, TInt aChannel) { const TUid KUidBTMobTimeObexAppValue = {0x0}; // a connection to the security manager RBTMan secManager; // a security session RBTSecuritySettings secSettingsSession; // define the security on this port User::LeaveIfError(secManager.Connect()); CleanupClosePushL(secManager); User::LeaveIfError(secSettingsSession.Open(secManager)); CleanupClosePushL(secSettingsSession); // the security settings TBTServiceSecurity serviceSecurity(KUidBTMobTimeObexAppValue, KSolBtRFCOMM, 0); //Define security requirements serviceSecurity.SetAuthentication(aAuthentication); serviceSecurity.SetEncryption(aEncryption); serviceSecurity.SetAuthorisation(aAuthorisation); serviceSecurity.SetChannelID(aChannel); TRequestStatus status; secSettingsSession.ReGISterService(serviceSecurity, status); User::WaitForRequest(status); // wait until the security settings are set User::LeaveIfError(status.Int()); CleanupStack::PopAndDestroy(); // secManager CleanupStack::PopAndDestroy(); // secSettingsSession } // 注册蓝牙串口服务 int CBtSvr::RegieterBlueToothServerL(const TDesC& KServiceName, TInt KSerialClassID) { // reg the sdp server database RSdp sdp; RSdpDatabase iSdpDatabase; TSdpServRecordHandle iRecord; // sdp服务器连接 if(sdp.Connect() != KErrNone) { return -CNSE_SYS_ERR; } // 打开数据库 if(iSdpDatabase.Open(sdp) != KErrNone) { return -CNSE_SYS_ERR; } // 创建一个服务 iSdpDatabase.CreateServiceRecordL(KSerialClassID, iRecord); // add a Protocol to the record CSdpAttrValueDES* vProtocolDescriptor = CSdpAttrValueDES::NewDESL(NULL); CleanupStack::PushL(vProtocolDescriptor); // 设置protocl相关信息 BuildProtocolDescriptionL(vProtocolDescriptor, channelNum); iSdpDatabase.UpdateAttributeL(iRecord, KSdpAttrIdProtocolDescriptorList, *vProtocolDescriptor); // Add 0x5 display 设置为可见 CSdpAttrValueDES* browseGroupList = CSdpAttrValueDES::NewDESL(NULL); CleanupStack::PushL(browseGroupList); browseGroupList ->StartListL() // List of protocols required for this method ->BuildUUIDL(TUUID(TUint16(0x1002))) ->EndListL(); iSdpDatabase.UpdateAttributeL(iRecord, KSdpAttrIdBrowseGroupList, *browseGroupList); CleanupStack::PopAndDestroy(2); // Add a name to the record,名字 iSdpDatabase.UpdateAttributeL(iRecord, KSdpAttrIdBasePrimaryLanguage + KSdpAttrIdOffsetServiceName, KServiceName); // Add a description to the record,描述 iSdpDatabase.UpdateAttributeL(iRecord, KSdpAttrIdBasePrimaryLanguage + KSdpAttrIdOffsetServiceDescription, KServiceName); iSdpDatabase.Close(); sdp.Close(); return 0; } // 这里设置蓝牙SDP服务中的协议相关信息 void CBtSvr::BuildProtocolDescriptionL(CSdpAttrValueDES* aProtocolDescriptor, TInt aPort) { TBuf8<1> channel; channel.Append((TChar)aPort); aProtocolDescriptor ->StartListL() ->BuildDESL() ->StartListL() // Details of lowest level protocol // L2CAP层之上 ->BuildUUIDL(KL2CAP) ->EndListL() ->BuildDESL() ->StartListL() ->BuildUUIDL(KRFCOMM) // 这里是绑定的RFCOMM的端口号 ->BuildUintL(channel) ->EndListL() ->EndListL(); } int CBtSvr::BuildSerivce(const TDesC& ServiceName, TInt KSerialClassID) { TBTSockAddr add; int ret; _LIT(KRFCOMM, ”RFCOMM”); // connect to server if (sockSvr.Connect() != KErrNone) { return -1; } // Open a socket if(listenSock.Open(sockSvr, KRFCOMM) != KErrNone) { // ERR_OPEN ret = -1; goto ERR_CONN; } // 得到一个可用的RFCOMM端口号 listenSock.GetOpt(KRFCOMMGetAvailableServerChannel, KSolBtRFCOMM, channelNum); add.SetPort(channelNum); if(listenSock.Bind(add) != KErrNone) { ret = -1; goto ERR_OPEN; } else { // 设置安全信息 TRAPD(err, (SetSecurityOnChannelL(EFalse, EFalse, ETrue, channelNum))); if (err != KErrNone) { ret = -1; goto ERR_OPEN; } // 注册SDP服务 TRAP(err, (RegieterBlueToothServerL(ServiceName, KSerialClassID))); if (err != KErrNone) { ret = -1; goto ERR_OPEN; } return 0; } ERR_OPEN: listenSock.Close(); ERR_CONN: sockSvr.Close(); return ret; }
整个CBtSvr就完成了,其实看着代码比较多,其实就实现了两个功能。蓝牙服务如果有什么不清楚可以多看看SDK自带的那几个示例代码,还有一些nokia提供的相关文档。
现在再派生出一个子类BtDbg,实现send功能
class BtDbg : public CBtSvr { public : ~BtDbg(); static BtDbg* NewLC(); static BtDbg* NewL(); void ConstructL(); int Send(const TDesC8& aDesc); friend int BtDbg_printf(const char* format, ...); private: int BindL(void); }; BtDbg* BtDbg::NewLC() { BtDbg* result = new (ELeave) BtDbg(); CleanupStack::PushL( result ); result->ConstructL(); return result; } BtDbg* BtDbg::NewL() { BtDbg* result = BtDbg::NewLC(); CleanupStack::Pop( result ); return result; } void BtDbg::ConstructL() { } BtDbg::~BtDbg() { } int BtDbg::BindL(void) { _LIT(ServiceName, ”Debug”); return BuildSerivce(ServiceName, 0x1101); } int BtDbg::Send(const TDesC8& aDesc) { if (iStat != EInConnection) { return -1; } // 同步的发送数据 connSock.Write(aDesc, iSendStatus); User::WaitForRequest(iSendStatus); if(iSendStatus != KErrNone) { return -1; } return 0; }
由于比较习惯c的printf来打印调试信息,所以我一般实例化一个BtDbg类,然后再赋给一个全局的指针,写exe程序用全局的东西比较方便。最后封装一个BtDbg_printf来打印trace。
下面代码中的btDbg是事先实例化好的一个BtDbg对象。
static char rxBuf[1024 * 10]; int BtDbg_printf(const char* format, ...) { va_list ap; int len = 0; va_start(ap, format); vsprintf((char*)rxBuf, format, ap); va_end(ap); TPtrC8 PBuf = TPtrC8::TPtrC8((unsigned char*)rxBuf); if (btDbg && btDbg->Status() == CBtSvr::EInConnection) { len = btDbg->Send(pBuf); } return len; }
|