|
franksunny的個人技術空間
獲得人生中的成功需要的專注與堅持不懈多過天才與機會。 ——C.W. Wendte |
symbian官方推薦使用活動服務對象(CActive)來代替多線程的使用,我想這個道理是很明了的,在手機這樣的小內存設備里,運行多線程的程序是非常耗資源的,為了節(jié)約資源,symbian提供了一個活動服務對象的框架,允許把程序里并發(fā)執(zhí)行對象(其實不是并發(fā),不過宏觀上看來是)放在一個線程里面執(zhí)行,這些并發(fā)工作的對象就通過活動規(guī)劃器(ActiveScheduler)來進行管理.
關于這兩個東西的介紹,網上有一大堆的文檔,我就不在這里廢話了,如何使用呢?這里我先舉一個簡單的計數(shù)器的例子.我選擇寫一個exe的程序,也就是說程序是以E32Main為入口的.
GLDEF_C TInt E32Main()
{
CTrapCleanup* cleanup=CTrapCleanup::New();
TRAPD(error,callInstanceL());
if (error != KErrNone)
{
printf("get error %d\r\n", error);
}
delete cleanup;
return 0;
}
以上的內容是每一個exe文件都應該做的,CTrapCleanup* cleanup=CTrapCleanup::New()建立一個清除堆棧,以便程序在異常退出的時候把清除堆棧里面的資源都釋放掉.當然你也可以加上堆檢測宏(__UHEAP_MARK,__UHEAP_MARKEND),這里我就不多說了。TRAPD是symbian里面經常使用的宏,功能類似于try,第一個參數(shù)是讓定義一個錯誤返回值變量的名字, 后面就是可能有異常的你寫的函數(shù).當這個函數(shù)異常時,程序不會crash, 你可以得到異常的原因.可以參考nokia論壇上的一些關于這些使用的文檔.
接下來是vcallInstanceL函數(shù),在這個函數(shù)里面我來建立ActiveScheduler.
LOCAL_C void callInstanceL()
{
CActiveScheduler* scheduler = new(ELeave) CActiveScheduler();
CleanupStack::PushL(scheduler);
CActiveScheduler::Install(scheduler);
TRAPD(error,doInstanceL());
if(error)
{
printf("error code=%d\r\n",error);
}
else
{
printf("OK!\r\n[press any key]");
}
CleanupStack::PopAndDestroy(scheduler);
}
這段程序很簡單就是創(chuàng)建一個活動規(guī)劃器,并壓入清除棧,然后安裝活動規(guī)劃器,這樣就可以用了.再執(zhí)行真正的實例函數(shù),最后出棧銷毀。doinstance(該函數(shù)將在最后的代碼中給出,主要的功能就是調用我們自己寫的活動計數(shù)器)我們放到最后來寫,現(xiàn)在來構造我們的活動計數(shù)器對象。
class TimeCount : public CActive
{
public :
static TimeCount* NewLC(); // 構造函數(shù)
~TimeCount();
void StartL(); // 計數(shù)開始
void ConstructL();
void RunL(); // 延時事件到達以后的處理函數(shù)
void DoCancel(); // 取消請求提交
void setDelayTime(int delayTime);
private:
TimeCount();
RTimer iTimer; // 定時器
int iTimeCount; // 計數(shù)器
int mTime; // 計數(shù)間隔時間 單位秒
};
TimeCount::TimeCount():CActive(0) // 這里可以設置活動對象的優(yōu)先級
{
// 把自己加入活動規(guī)劃器
CActiveScheduler::Add(this);
}
TimeCount* TimeCount::NewLC()
{
TimeCount* result = new (ELeave) TimeCount();
CleanupStack::PushL( result );
result->ConstructL();
return result;
}
void TimeCount::DoCancel(void)
{
iTimer.Cancel();
}
void TimeCount::setDelayTime(int mTime)
{
DelayTime = mTime;
}
TimeCount::~TimeCount()
{
Cancel();
iTimer.Close();
}
void TimeCount::StartL()
{
// 設定定時器狀態(tài)為每隔mTime秒鐘狀態(tài)完成一次
iTimer.After(iStatus, 10000 * 100 * mTime);
// 提交異步請求
SetActive();
}
void TimeCount::ConstructL()
{
// 初始化計數(shù)器和定時器
iTimeCount = 0;
User::LeaveIfError(iTimer.CreateLocal());
}
void TimeCount::RunL()
{
// 計數(shù)器+1以后繼續(xù)提交延時請求事件
printf("The Count is ->>%d", iTimeCount++);
StartL();
}
每一個活動服務對象都有一個iStatus來標識當前對象的狀態(tài).在這里我們把iStatus設定為iTimer.After(iStatus, 10000 * 100 * mTime);也就是定時器定時mTime秒鐘以后iStatus發(fā)生改變,這個時候活動規(guī)劃器會收到這個狀態(tài)的改變,從而調用相應活動對象的處理函數(shù),也就是RunL函數(shù).在RunL函數(shù)里面進行計數(shù)和輸出,然后調用startL重新設置定時器和對象狀態(tài),再提交給活動規(guī)劃器。這樣mTime秒鐘以后活動規(guī)劃器會再次調用RunL函數(shù).一直這樣重復,這樣就達到了計數(shù)器的效果。
最后我們來寫doinstanceL函數(shù)
LOCAL_C void doInstanceL()
{
TimeCount* timeCount = TimeCount::NewLC();
// 每隔一秒鐘打印一次
TimeCount->setDelayTime(1);
TimeCount->StartL();
CActiveScheduler::Start();
CleanupStack::PopAndDestroy(1);
}
創(chuàng)建好對象以后,加上CActiveScheduler::Start()程序就開始運行了,這句話告訴活動規(guī)劃器該等待對象的狀態(tài)的改變了(正常情況下,一旦CActiveScheduler::Start()之后,程序直到CActiveScheduler::Stop()才能終止運行),在這里就是timeCount的iStatus的改變.等iStatus改變并調用了RunL以后,繼續(xù)等待iStstus的改變,這樣我們使用活動對象的計數(shù)器就能夠通過消息驅動運行起來了.
這里的CActiveScheduler只管理了一個CActive對象,就是timeCount,可以用類似的方法實現(xiàn)多個CActive,并且都加入CActiveScheduler,CActiveScheduler將會等待所有加入它的CActive的狀態(tài)的改變,其中有一個的狀態(tài)改變就會去執(zhí)行對應的活動對象的處理函數(shù),當狀態(tài)同時發(fā)生的時候,會通過對象的優(yōu)先級來決定先調用誰的RunL函數(shù).CActiveScheduler也是非搶占式的,當一個RunL函數(shù)還沒有執(zhí)行完的時候,如果另一個CActive的狀態(tài)改變,會等待RunL執(zhí)行完以后再執(zhí)行另一個CActive的處理函數(shù)(正因為這一點,所以通常RunL函數(shù)不能設計為長函數(shù),否則會阻塞活動對象)。
Active Object (AO) 框架,是Symbian的基本工作部分。它是為了滿足多個任務同時執(zhí)行的要求。在 Windows/Unix 平臺上,我們可以不加思索的使用多線程來完成多任務。可是在嵌入式平臺上,系統(tǒng)的資源是有限的。比如CPU、內存都比我們平時用的個人計算機要低。這就要求嵌入式系統(tǒng)能夠合理的使用系統(tǒng)資源。不能頻繁的切換線程或者進程。
Symbian為這種特別需求設計了Active Object (AO)框架。AO框架是運行于一個線程內部的調度框架。其基本思想就是把一個單線程分為多個時間片,來運行不同的任務。
這和多線程有很大區(qū)別。多線程之間是可以被搶占的(由操作系統(tǒng)調度),但是AO框架中的各個任務是不可被搶占的,一個任務必須完成,才能開始下一個任務。
下面是多線程和AO框架的簡單比較:
多線程 AO框架
可以被搶占 不可被搶占
上下文切換耗費CPU時間 沒有上下文切換
由操作系統(tǒng)調度 由線程自己的AO框架調度
每個線程都有至少4K Stack. AO沒有單獨的Stack.
操作系統(tǒng)還要分配額外的資源記錄線程 只是一個Active Object.
Symbian系統(tǒng)本身使用了大量的AO框架來實現(xiàn)一些系統(tǒng)服務。這使得Symbian和其他嵌入式系統(tǒng)相比較,對系統(tǒng)資源的使用更加合理。
AO框架包括CActiveScheduler 和CActive (Active Object)。一個線程的所有的Active Object對象都被安裝在該線程的CActiveScheduler對象內.由CActiveScheduler對象監(jiān)控每個Active Object是否完成了當前任務,如果完成了,就調度下一個Active Object來執(zhí)行。CActiveScheduler根據(jù)優(yōu)先級來調度各個Active Object.
關于CActiveScheduler的創(chuàng)建和安裝,CActive的創(chuàng)建和安裝,和CActive的任務處理,可以參看 SDK 文檔。理解起來不難。下面要說一個比較容易忽略的地方。這對理解AO框架非常重要。
創(chuàng)建調度器:
CActiveScheduler * scheduler = new (ELeave) CActiveScheduler;
CleanupStack::PushL(scheduler);
CActiveScheduler::Install(scheduler);
運行調度器:
CActiveScheduler::Start();
停止調度器:
CActiveScheduler::Stop();
以上代碼都是運行在一個線程中的,一般來講,一個EXE只有一個主線程.
可是如果真的有2個線程呢?
為什么在當前線程下調用CActiveScheduler::Start(),CActiveScheduler::Stop()運行/停止的就是當前線程的調度器而不是另一個線程的調度器?
每個線程都有自己的CActiveScheduler,那么這個CActiveScheduler類是怎么調用CActiveScheduler::Start(),CActiveScheduler::Stop()來運行/停止當前的調度器的呢?
我們看到Start/Stop并沒有參數(shù).
打開CActiveScheduler的類定義:
class CActiveScheduler : public CBase
{
public:
IMPORT_C CActiveScheduler();
IMPORT_C ~CActiveScheduler();
IMPORT_C static void Install(CActiveScheduler* aScheduler);
IMPORT_C static CActiveScheduler* Current();
IMPORT_C static void Add(CActive* anActive);
IMPORT_C static void Start();
IMPORT_C static void Stop();
IMPORT_C static TBool RunIfReady(TInt& aError, TInt aMinimumPriority);
IMPORT_C static CActiveScheduler* Replace(CActiveScheduler* aNewActiveScheduler);
IMPORT_C virtual void WaitForAnyRequest();
IMPORT_C virtual void Error(TInt anError) const;
private:
void DoStart();
void OwnedStartLoop(TInt& aRunning);
IMPORT_C virtual void OnStarting();
IMPORT_C virtual void OnStopping();
IMPORT_C virtual void Reserved_1();
IMPORT_C virtual void Reserved_2();
private:
// private interface used through by CActiveSchedulerWait objects
friend class CActiveSchedulerWait;
static void OwnedStart(CActiveSchedulerWait& aOwner);
protected:
inline TInt Level() const;
private:
TInt iLevel;
TPriQue<CActive> iActiveQ;
};
我們并沒有看到靜態(tài)的成員來表示線程,但是卻有一個static函數(shù)CActiveScheduler* Current();返回當前線程的調度器.
現(xiàn)在猜想奧秘就在這個函數(shù)是怎么實現(xiàn)的。這個靜態(tài)的函數(shù)怎么就能得到當前這個運行線程的調度器,而不是別的線程的調度器。我們可以猜想,肯定是Current()內部實現(xiàn)能取到當前線程的標識信息.用這個標識,靜態(tài)函數(shù)能取到這個線程的CActiveScheduler.這個具體如何實現(xiàn)呢?
答案就是:當前線程的線程ID可以這樣得到:
創(chuàng)建一個缺省的線程對象:
RThread thread;
取得當前線程的ID:
TThreadId threadId = thread.Id();
能得到當前線程的threadId,當然可以得到和當前線程關聯(lián)的CActiveScheduler。因此以上兩個問題也就迎刃而解了,在一個線程內調用CActiveScheduler::Start(),CActiveScheduler::Stop()開啟的就是當前線程。
既然回復了上面的問題,那么我們自然就能明確,在一個線程內是不能通過Start和Stop函數(shù)來開啟和停止另一個線程內的活動對象規(guī)劃器CActiveScheduler。
補充點其他東西:
在Symbian操作系統(tǒng)中,每個進程都有一個或多個線程。線程是執(zhí)行的基本單位。一個進程的主線程是在進程啟動時生成的。Symbian屬于搶占式多任務操作系統(tǒng),這意味著每個線程都有自己的執(zhí)行時間,直到系統(tǒng)將CPU使用權給予其他線程。當系統(tǒng)調度時,具有最高優(yōu)先權的線程將首先獲得執(zhí)行。
進程邊界是受內存保護的。所有的用戶進程都有自己的內存地址空間,同一進程中的所有線程共享這一空間,用戶進程不能直接訪問其他進程的地址空間。
每個線程都有它自己的stack和heap,這里heap可以是私有的,也可以被其他線程共享。應用程序框架生成并安裝了一個active scheduler,并且為主線程準備了清除棧。如果沒有使用框架(如編寫exe程序)那就要手動生成這些了。
Symbian操作系統(tǒng)專為單線程應用優(yōu)化,因此強烈推薦使用“活動對象”代替多線程。
如何在C++中調用C的代碼
以前曾經總結過一篇(http://m.shnenglu.com/franksunny/archive/2007/11/29/37510.html),關于在C中如何調用C++的代碼,當時并未做完全的展開,只是簡單的做了下調試,最近看到一個題目要求實現(xiàn)C和C++中代碼的互相調用,其結果雖然都是通過extern “C”來實現(xiàn),但是具體還是有些差別的。
先對C中調用C++代碼作個簡單回顧:
1、 對于C++中非類的成員函數(shù),可以簡單的在函數(shù)聲明前面加extern “C”,通常函數(shù)聲明位于頭文件中,當然也可以將聲明和函數(shù)定義一起放在cpp中,在沒有聲明的情況下,直接在定義前添加extern “C”也可
2、 對于C++類的成員函數(shù),則需要另外做一個cpp文件,將需要調用的函數(shù)進行包裝。
以上兩項的實例參看前面C中如何調用C++代碼的文章。
要實現(xiàn)C++中調用C的代碼,具體操作:
對于C中的函數(shù)代碼,要么將C代碼的頭文件進行修改,在其被含入C++代碼時在聲明中加入extern “C”或者在C++代碼中重新聲明一下C函數(shù),重新聲明時添加上extern “C”頭。
通過以上的說明,我明白一點,那就是加extern “C”頭一定是加在C++的代碼文件中才能起作用的。
下面分析一下這個現(xiàn)象的實質原因:
C編譯器編譯函數(shù)時不帶函數(shù)的類型信息,只包含函數(shù)符號名字,如C編譯器把函數(shù)int a(float x)編譯成類似_a這樣的符號,C連接器只要找到了調用函數(shù)的符號,就可以連接成功,它假設參數(shù)類型信息是正確的,這是C編譯連接器的缺點。而C++編譯器為了實現(xiàn)函數(shù)重載,編譯時會帶上函數(shù)的類型信息,如他把上面的a函數(shù)可能編譯成_a_float這樣的符號為了實現(xiàn)重載,注意它還是沒有帶返回值得信息,這也是為什么C++不支持采用函數(shù)返回值來區(qū)別函數(shù)重載的原因之一,當然,函數(shù)的使用者對函數(shù)返回值的處理方式(如忽略)也是重要原因。
基于以上,C調用C++,首先需要用封裝函數(shù)把對C++的類等的調用封裝成C函數(shù)以便C調用,于是extern "C"的作用是:讓編譯器知道這件事,然后以C語言的方式編譯和連接封裝函數(shù)(通常是把封裝函數(shù)用C++編譯器按C++方式編譯,用了extern "C" 后,編譯器便依C的方式編譯封裝接口,當然接口函數(shù)里面的C++語法還是按C++方式編譯;對于C語言部分--調用者,還是按C語言編譯;分別對C++接口部分和C部分編譯后,再連接就可以實現(xiàn)C調用C++了)。相反,C++調用C函數(shù),extern "C" 的作用是:讓C++連接器找調用函數(shù)的符號時采用C的方式,即使用_a而不是_a_float來找調用函數(shù)。
具體示例請見http://m.shnenglu.com/Files/franksunny/CCallCpp.rar
注:如果你用VC6.0編譯附件時遇到類似“fatal error C1010: unexpected end of file while looking for precompiled header directive”報錯的話,請將bb.c文件做如下處理右鍵點擊項目工程中的該文件,選擇setting,在c/c++欄,選擇PreCompiled headers,然后設置第一選項,選擇不使用預編譯頭。
這種在Symbian C/S架構中,服務器程序與客戶UI進程主動通信中用的比較多。
對于在往UI框架應用程序發(fā)送消息,可以通過Symbian OS的Application Architecture Services可以進行應用程序間的通信,主要用到的類包括:TApaTaskList和TApaTask。
TApaTaskList:用于訪問設備中正在運行的任務(假如有些任務隱藏了的話,那么通過這種方法也無法進行訪問)。
TApaTask:表示設備中某個運行的任務,通過與程序關聯(lián)的窗口組(window group)標識。
具體的解決方案:
發(fā)送消息端:使用TApaTaskList找到等待接收消息的任務,TApaTaskList::FindApp()提供了兩個重載版本,可以使用程序的標題,也可以使用程序的UID進行查找。獲得需要發(fā)消息的任務后就可以通過TApaTask:: SendMessage()發(fā)送消息了,它有兩個參數(shù),第一個參數(shù)用于標識消息,第二個參數(shù)是一個描述符的引用,可以用來提供不同消息時附加的具體信息。
TUid uid( TUid::Uid( 0x
TApaTaskList taskList( iCoeEnv->WsSession() );
TApaTask task = taskList.FindApp(uid );
if( task.Exists() ) //判斷任務是否正在運行
{
LIT8( KTestMsg, "CustomMessage" );
TUid msgUid( TUid::Uid( 1 ) );
task.SendMessage( uid, KTestMsg );
}
接收消息端可以使用如下兩種方案:
第一種方案:由于MCoeMessageObserver是處理來自窗口服務器消息的接口類,而CEikAppUi已經繼承自MCoeMessageObserver,所以我們只需要在自己的UI類中重現(xiàn)實現(xiàn)MCoeMessageObserver的唯一成員函數(shù)HandleMessageL()用來處理接收到的消息即可,代碼如下:
MCoeMessageObserver::TMessageResponse CXXXAppUi::HandleMessageL(TUint32 aClientHandleOfTargetWindowGroup, TUid aMessageUid, const TDesC8& aMessageParameters)
{
_LIT( KFormatStr, "%x" );
TBuf<32> bufUid;
TBuf<32> bufPara;
bufUid.AppendFormat( KFormatStr, aMessageUid.iUid );
bufPara.Copy( aMessageParameters );
iEikonEnv->InfoWinL( bufUid, bufPara );
return MCoeMessageObserver::EMessageHandled;
}
第二種方案:由于TApaTask::SendMessage()發(fā)送的消息可以被CEikAppUI的成員函數(shù)ProcessMessageL()攔截并處理,不過必須在沒有重載HandleMessageL()函數(shù)的前提下,而且函數(shù)ProcessMessageL()只負責攔截消息標識為KUidApaMessageSwitchOpenFileValue和KUidApaMessageSwitchCreateFileValue的這兩個消息,其它標識值的消息不會被傳到ProcessMessageL()中,所以這種方案個人覺得很受限制,不自由,還是采用第一種方案好,具體代碼代碼如下:
//發(fā)送:
TUid uid( TUid::Uid( 0x
TApaTaskList taskList( iCoeEnv->WsSession() );
TApaTask task = taskList.FindApp(uid );
if( task.Exists() ) //判斷任務是否正在運行
{
LIT8( KTestMsg, "CustomMessage" );
//這里的Uid不能使用自定義的,而且只有系統(tǒng)提供的兩個
TUid msgUid( TUid::Uid(KUidApaMessageSwitchCreateFileValue) );
task.SendMessage( uid, KTestMsg );
}
//接收:
void CXXXAppUi::ProcessMessageL(TUid aUid,const TDesC8& aParams)
{
RFileLogger iLog;
iLog.Connect();
iLog.CreateLog(_L("tb"), _L("UpdateListener2.txt"), EFileLoggingModeOverwrite);
iLog.Write(_L("smms appui"));
if (aUid.iUid == KUidApaMessageSwitchCreateFileValue)
{
TBuf<256> buf;
buf.Copy(aParams);
iLog.Write(aParams);
BringMeToFront();
ShowCreateFile(buf,CFileMonitorEngine::EImageType);
}
else
{
CAknViewAppUi::ProcessMessageL(aUid,aParams);
}
iLog.Close();
}
今天接到電話面試,被問到幾個問題,汗顏之余,小結一下
1、 多態(tài)是如何實現(xiàn)綁定的
多態(tài)的綁定可以分為運行是多態(tài)和編譯時多態(tài)
● 編譯時的多態(tài)性
編譯時的多態(tài)性是通過重載來實現(xiàn)的。對于非虛的成員來說,系統(tǒng)在編譯時,根據(jù)傳遞的參數(shù)、返回的類型等信息決定實現(xiàn)何種操作。
● 運行時的多態(tài)性
運行時的多態(tài)性就是指直到系統(tǒng)運行時,才根據(jù)實際情況決定實現(xiàn)何種操作。C#中,運行時的多態(tài)性通過虛成員實現(xiàn)。
編譯時的多態(tài)性為我們提供了運行速度快的特點,而運行時的多態(tài)性則帶來了高度靈活和抽象的特點。
今天才正式弄清楚原來虛函數(shù)是可以實現(xiàn)運行時多態(tài)的,以前只知道虛函數(shù)可以使得基類對象的的方法調用派生類的方法。
2、 析構函數(shù)是虛函數(shù)的優(yōu)點是什么
用C++開發(fā)的時候,用來做基類的類的析構函數(shù)一般都是虛函數(shù)。可是,為什么要這樣做呢?下面用一個小例子來說明:
有下面的兩個類:
class ClxBase
{
public:
ClxBase() {};
virtual ~ClxBase() {};
virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};
class ClxDerived : public ClxBase
{
public:
ClxDerived() {};
~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };
void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };
};
代碼
ClxBase *pTest = new ClxDerived;
pTest->DoSomething();
delete pTest;
輸出結果是:
Do something in class ClxDerived!
Output from the destructor of class ClxDerived!
這個很簡單,非常好理解。
但是,如果把類ClxBase析構函數(shù)前的virtual去掉,那輸出結果就是下面的樣子了:
Do something in class ClxDerived!
也就是說,類ClxDerived的析構函數(shù)根本沒有被調用!一般情況下類的析構函數(shù)里面都是釋放內存資源,而析構函數(shù)不被調用的話就會造成內存泄漏。我想所有的C++程序員都知道這樣的危險性。當然,如果在析構函數(shù)中做了其他工作的話,那你的所有努力也都是白費力氣。
所以,文章開頭的那個問題的答案就是--這樣做是為了當用一個基類的指針刪除一個派生類的對象時,派生類的析構函數(shù)會被調用。
當然,并不是要把所有類的析構函數(shù)都寫成虛函數(shù)。因為當類里面有虛函數(shù)的時候,編譯器會給類添加一個虛函數(shù)表,里面來存放虛函數(shù)指針,這樣就會增加類的存儲空間。所以,只有當一個類被用來作為基類的時候,才把析構函數(shù)寫成虛函數(shù)。
說實話,這個也是今天才深刻認識到的。
當然還問到很多數(shù)據(jù)結構和算法方面(空間復雜度和時間復雜度之類的東東,說真的也是基礎性的)的問題,至于那些東西,自己說實話拋開沒用他們已經很長時間了,真可以說忘的差不多了,考這種真的很怕,也怪平時沒怎么用到。不知道大家用的多不?
好久沒有正式參加過面試了,今天突然來一次覺得自己基礎還是不夠扎實。
