相信绝大部分人做Symbian程序都是从app开始的,app的例子非常多,很容易上手。但是有些需求在用app实现中出现了一些问题,假设我们要做一个来电检测程序,把所有来电号码都记录在一个文件中。如果用app做当然可以实现,但是问题是这个app是有窗口界面的,但这个窗口对使用者来说毫无价值,白白浪费了一大块资源,但是又不能把这个窗口关掉,一旦关掉,app就终止运行了,来电检测也就无法实现了。类似的程序的最佳解决方案就是做成exe形式。
通常exe程序是用来做后台服务的,对使用者来说他是不可见的,通常没有界面,这样既节省了资源,有不会因为使用者不小心关闭了程序而导致功能无法实现。
2.exe程序的框架
exe的例子也有一些,大家可以参考那些例子来建立mmp文件以及程序基础框架,这里就不多说了。
exe总是从E32Main函数开始执行的,我是用如下的E32Main代码的:
{
CTrapCleanup* cleanup = CTrapCleanup::New();
RUNMAIN(); // 宏
_LIT(KMsgPanicEpoc32ex,"EPOC32EX");
__ASSERT_ALWAYS(!error,User::Panic(KMsgPanicEpoc32ex,error));
delete cleanup;
return 0;
}
#define RUNMAIN() TRAPD(error, MainL());
实际上就是去掉用MainL。为什么这么做,后面会提到。
MainL的代码如下,构建了CActiveScheduler,然后就是具体的处理了
{
CActiveScheduler* scheduler = new(ELeave) CActiveScheduler();
CleanupStack::PushL(scheduler);
CActiveScheduler::Install(scheduler);
// 具体的处理
// ......
CleanupStack::PopAndDestroy(scheduler);
}
3.如何调试exe
又了上面的代码,这个exe已经可以编译和运行了,虽然他什么实质的事情都没做,但是他的的确确已经是一个合格的exe了。如果你把编译好的程序放到手机上运行(用文件管理器打开),你会发现什么都没有发生,你无法判断它是否执行了,执行到哪里了。这就带来一个调试的问题。
首先说说如何在VC6环境中调试,这个比较简单,调试app的时候,我们是指定vc6运行那个模拟器程序的,而调试exe你只要指定vc6调试时运行你生成的那个exe就可以了,当然这个exe是wins编码的,不能是手机上运行的armi编码。调试运行后也会显示手机模拟器的界面,不过没有9宫格主菜单了。
比较麻烦的是手机上的执行调试,如前面提到的那样,我们可能什么都看不到,那么如何让exe显示一些信息呢?这里就需要用到控制台Console。Console就如Windows上的dos窗口,是纯文本的,对付信息显示是绰绰有余。
Console的用法也非常简单,先构造CConsoleBase,然后就可以用它的Printf函数在控制台上输出数据了。
我把Console单独放在一组cpp/h文件中,如下:
#ifndef __CONSOLE_H__
#define __CONSOLE_H__
#include <e32base.h>
#include <e32cons.h>
#include <e32std.h>
#define _DEBUG_CONSOLE_
#ifdef _DEBUG_CONSOLE_
extern CConsoleBase* gConsole;
extern void ConsoleMainL();
#define RUNMAIN() TRAPD(error, ConsoleMainL());
// 显示字符串
#define CONSOLEPRINTINFO(infostr) gConsole->Printf(infostr);gConsole->Printf(_L("/n"));
// 先是数字
#define CONSOLEPRINTNUM(num) gConsole->Printf(_L("%d/n"), num);
#else // _DEBUG_CONSOLE_
#define RUNMAIN() TRAPD(error, MainL());
#define CONSOLEPRINTINFO(infostr)
#define CONSOLEPRINTNUM(num)
#endif // _DEBUG_CONSOLE_
#endif //__CONSOLE_H__
// ================= End of console.h =========================
// ================= Start of console.cpp =======================
#include "console.h"
#ifdef _DEBUG_CONSOLE_
extern void MainL();
CConsoleBase* gConsole;
void ConsoleMainL()
{
gConsole = Console::NewL(_L("MyExe"), TSize(KConsFullScreen, KConsFullScreen));
MainL();
delete gConsole;
}
#endif
// ================= End of console.cpp =========================
这样一来我只要将"#define _DEBUG_CONSOLE_"这一行去掉就可以编译生成不含Console的最终代码了,而加上这一行就可以显示调试信息。在主代码中只要调用 CONSOLEPRINTINFO 和 CONSOLEPRINTNUM 两个宏来分别显示字符串和数字,而不用再考虑是否define了_DEBUG_CONSOLE_。
看到这里的 RUNMAIN 宏了吗,他的作用就是在调试的时候去执行ConsoleMainL,而不是MainL,两者的区别就是ConsoleMainL先建立了一个Console,最后再将其释放。
放到手机上运行一下吧,运行后会出现一个全屏的白色窗口,其实你可能看不清这个窗口,因为它是一闪而过的。怎么会这样,呵呵,因为我们的MainL()函数里面什么都没做,exe程序当然就立即结束了。你可以尝试在MainL()函数“具体的处理”这部分加上两句话:
CONSOLEPRINTINFO(_L("Hello World!"));
CActiveScheduler::Start();
第一句是在控制台上显示Hello World;第二句开始检测CActive事件,这里用这个只是为了能让程序保持住,而不会立即结束。在手机上运行后,你会发现一个白色窗口,上面显示Hello World。新的问题又来了,这个程序现在总也结束不了了,这时候需要一个线程管理工具来终止这个exe,这样的工具有AppMan和TaskSpy。
注意你的线程名称,当你是用了Console时,线程名称就是控制台名称“MyExe”,当不用Console时,线程名称就是那个exe的名字,这点对下一段很有用。为了保持一致,建议大家将控制台名称设定为exe程序的名称。
另外你可以根据你的需要定义你的显示宏,而不一定是我这里的 CONSOLEPRINTINFO 和 CONSOLEPRINTNUM。
新的调试方法
在手机上调试exe程序还是一个比较麻烦的事情,前面介绍了console,但是有时候不方便用console,这时候就要换一种方法来记录信息,比较简单的就是用文件记录,这个方法的缺点是不能实时察看,另外一个缺点就是要消耗较多的时间,别看这点时间,有时候就会掩盖一些问题。我就遇到过,不过可以通过其他的办法来解决。
这里给出一段我用的代码供大家参考。因为是调试代码,所以写的并不是很完善,要求字符串不能含中文:
{
RFs aSession;
aSession.Connect();
TFileName *fname = new (ELeave) TFileName;
CleanupStack::PushL(fname);
fname->Copy(_L("c://testinfo.txt"));
RFile aTestFile;
TInt err = aTestFile.Open(aSession, *fname, EFileWrite);
if (err == KErrNotFound) // 没有此文件
{
err = aTestFile.Create(aSession, *fname, EFileWrite);
}
CleanupStack::PopAndDestroy();
if (err == KErrNone)
{
TInt pos = 0;
aTestFile.Seek(ESeekEnd, pos); // 在最后添加
TBuf8<50> aText;
for (TInt i=0; i<infostr.Length(); i++)
{
aText.Append(infostr[i] & 0xFF); // 16bit简单转8bit
}
aTestFile.Write(aText, aText.Size());
aTestFile.Write(_L8("/r/n"), 2); // 添加一个换行
}
aTestFile.Close();
aSession.Close();
}
前面提到的时间占用问题如何解决呢?
也很简单,在你对时间有要求的地方定义一片缓存,把信息先放到缓存里,等到过了这个地方,再将缓存里的数据写入文件。用这个方法还可以写入数字等内容。
代码类似于:
......
tempstr.Append(myinfo);
tempstr.Append(_L("/r/n")); // 换行
......
tempstr.AppendFormat(_L("a=%d, b=%d/r/n"), a, b);
......
......
WriteTestInfoL(tempstr);
和app不同的是,exe可以运行多个实例,在某些情况下,这是有用的。但是如果我们不需要这个特性,那么如何才能阻止exe运行多个实例以减少资源占用呢?这就需要用TFindProcess。
将MainL写成:
void MainL()
{
CActiveScheduler* scheduler = new(ELeave) CActiveScheduler();
CleanupStack::PushL(scheduler);
CActiveScheduler::Install(scheduler);
// 寻找符合条件的线程
TInt pcount = 0;
TFullName processName;
TFindProcess findProcess(KPROCESSNAME);
while (ETrue)
{
findProcess.Next(processName);
if (processName != KNullDesC)
{
pcount ++;
CONSOLEPRINTINFO(processName);
}
else
break;
}
if (pcount <= 1) // 只有本线程运行
{
// 具体的处理
// ......
}
CONSOLEPRINTINFO(_L("Exe End"));
CleanupStack::PopAndDestroy(scheduler);
}
这里判断线程的数量用了 if (pcount <= 1),而不是<1,因为当前在做判断的线程也算一个。
当发现有其他相同的线程在运行时,本线程就跳过具体的处理,直接结束了。这样就达到了我们的目的。
exe是后台的程序,通常是没有窗口界面的,但是我们有时候需要让使用者获得一些信息。比如一个闹钟提醒程序,平时在后台运行,到时间后除了要播放闹铃,可能还需要在屏幕上显示一些用户预先设置的提示信息,如"XXX生日"之类的。这时候就需要来构造一个窗口。
//
#if !defined(__MY_WINDOW_H__)
#define __MY_WINDOW_H__
class CWindow;
/////////////////////////////////////////////////////////////////////////
////////////////////// Declaration of CWsClient /////////////////////////
/////////////////////////////////////////////////////////////////////////
// Base class for all windows
class CWsClient : public CActive
{
protected:
//construct
CWsClient(const TRect& aRect);
public:
static CWsClient* NewL(const TRect& aRect);
void ConstructL();
// destruct
~CWsClient();
public:
// terminate cleanly
void Exit();
// active object protocol
void IssueRequest(); // request an event
void DoCancel(); // cancel the request
virtual void RunL(); // handle completed request
private:
CWsScreenDevice* iScreen;
CWindowGc* iGc;
CWindow *iWindow;
RWsSession iWs;
RWindowGroup iGroup;
const TRect& iRect;
friend class CWindow; // needs to get at session
};
//////////////////////////////////////////////////////////////////////////////
///////////////////////// CWindow declaration ////////////////////////////////
//////////////////////////////////////////////////////////////////////////////
class CWindow : public CBase
{
public:
CWindow(CWsClient* aClient);
void ConstructL (const TRect& aRect);
~CWindow();
public:
// access
RWindow& Window(); // our own window
// drawing
void Draw(const TRect& aRect);
private:
CWindowGc* SystemGc(); // system graphics context
private:
RWindow iWindow; // window server window
TRect iRect; // rectangle re owning window
private:
CWsClient* iClient; // client including session and group
};
#endif // __MY_WINDOW_H__
// ================= End of Window.h =======================
// ================= Start of Window.cpp =======================
// Window.cpp
//
#include <w32std.h>
#include <coedef.h>
#include "Window.h"
///////////////////////////////////////////////////////////////////////////////
////////////////////////// CWindow implementation /////////////////////////////
///////////////////////////////////////////////////////////////////////////////
CWindow::CWindow(CWsClient* aClient)
: iClient(aClient)
{
}
void CWindow::ConstructL (const TRect& aRect)
{
// Use the window group for parent window
RWindowTreeNode* parent= &(iClient->iGroup);
iWindow=RWindow(iClient->iWs); // use app's session to window server
User::LeaveIfError(iWindow.Construct(*parent,(TUint32)this));
iRect = aRect;
iWindow.SetExtent(iRect.iTl, iRect.Size()); // set extent relative to group coords
iWindow.Activate(); // window is now active
}
CWindow::~CWindow()
{
iWindow.Close(); // close our window
}
RWindow& CWindow::Window()
{
return iWindow;
}
CWindowGc* CWindow::SystemGc()
{
return iClient->iGc;
}
/****************************************************************************/
| Function: CWindow::Draw
| Purpose: Redraws the contents of CSmallWindow within a given
| rectangle. CSmallWindow displays a square border around
| the edges of the window, and two diagonal lines between the
| corners.
| Input: aRect Rectangle that needs redrawing
| Output: None
/****************************************************************************/
void CWindow::Draw(const TRect& aRect)
{
// Drawing to a window is done using functions supplied by
// the graphics context (CWindowGC), not the window.
CWindowGc* gc = SystemGc(); // get a gc
gc->SetClippingRect(aRect); // clip outside this rect
gc->Clear(aRect); // clear
TSize size=iWindow.Size();
TInt width=size.iWidth;
TInt height=size.iHeight;
// Draw a square border
gc->DrawLine(TPoint(0,0),TPoint(0,height-1));
gc->DrawLine (TPoint (0, height-1), TPoint (width-1, height-1));
gc->DrawLine(TPoint(width-1,height-1),TPoint(width-1,0));
gc->DrawLine (TPoint (width-1, 0), TPoint (0, 0));
// Draw a line between the corners of the window
gc->DrawLine(TPoint(0,0),TPoint(width, height));
gc->DrawLine (TPoint (0, height), TPoint (width, 0));
}
/////////////////////////////////////////////////////////////////////////////////////
/////////////////////////// CWsClient implementation ////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
CWsClient* CWsClient::NewL(const TRect& aRect)
{
// make new client
CWsClient* client=new (ELeave) CWsClient(aRect);
CleanupStack::PushL(client); // push, just in case
client->ConstructL(); // construct and run
CleanupStack::Pop();
return client;
}
CWsClient::CWsClient(const TRect& aRect)
: CActive(CActive::EPriorityHigh),
iRect(aRect)
{
}
void CWsClient::ConstructL()
{
// add ourselves to active scheduler
CActiveScheduler::Add(this);
// get a session going
User::LeaveIfError(iWs.Connect());
// construct our one and only window group
iGroup=RWindowGroup(iWs);
User::LeaveIfError(iGroup.Construct(2,ETrue)); // meaningless handle; enable focus
// construct screen device and graphics context
iScreen=new (ELeave) CWsScreenDevice(iWs); // make device for this session
User::LeaveIfError(iScreen->Construct()); // and complete its construction
User::LeaveIfError(iScreen->CreateContext(iGc));// create graphics context
iWindow = new (ELeave) CWindow (this);
iWindow->ConstructL(iRect);
// 窗口始终在最上层
iGroup.SetOrdinalPosition(0, ECoeWinPriorityAlwaysAtFront);
// 禁止接受焦点
iGroup.EnableReceiptOfFocus(EFalse);
// Set the window is non-fading
iGroup.SetNonFading(ETrue);
// 将窗口提到前面
TApaTask task(iWs);
task.SetWgId(iGroup.Identifier());
task.BringToForeground();
// request first event and start scheduler
IssueRequest();
}
CWsClient::~CWsClient()
{
// neutralize us as an active object
Deque(); // cancels and removes from scheduler
// get rid of everything we allocated
delete iGc;
delete iScreen;
delete iWindow;
// destroy window group
iGroup.Close();
// finish with window server
iWs.Close();
}
void CWsClient::IssueRequest()
{
iWs.RedrawReady(&iStatus); // request redraw
SetActive(); // so we're now active
}
void CWsClient::DoCancel()
{
iWs.RedrawReadyCancel(); // cancel redraw request
}
/****************************************************************************/
| Function: CWsClient::RunL()
| Called by active scheduler when an even occurs
| Purpose: do Redraw
/****************************************************************************/
void CWsClient::RunL()
{
// find out what needs to be done
TWsRedrawEvent redrawEvent;
iWs.GetRedraw(redrawEvent); // get event
CWindow* window=(CWindow*)(redrawEvent.Handle()); // get window
if (window)
{
TRect rect=redrawEvent.Rect(); // and rectangle that needs redrawing
// now do drawing
iGc->Activate(window->Window());
window->Window().BeginRedraw(rect);
window->Draw(rect);
window->Window().EndRedraw();
iGc->Deactivate();
}
// maintain outstanding request
IssueRequest(); // maintain outstanding request
}
// ================= End of Window.cpp =======================
先要建立一个CWsScreenDevice:
TFontSpec myFontSpec(FONT_CH16, 200);
iScreen->GetNearestFontInTwips(iFont, myFontSpec);
可以有几种方法,一种是通过文件管理器直接执行这个exe,这种方法通常在开发阶段使用,因为要让用户这么操作,用户会觉得很不方便;第二种是通过app来调用exe;第三种是利用mdl在开机阶段就调用exe。后两种的方法是类似的,都是通过CApaCommandLine来实现,具体的代码可以参考:
下面的代码是用来在手机启动的时候自动运行一个app或exe。
这里有一个问题要特别注意, 就是 WaitForPhoneApplicationL函数,这是用来检查手机上电话程序是否已经启动的函数,也就是一定要在电话程序启动后再执行后面你自己的操作,比如运行一个app。如果等了一段时间后电话程序还是没有启动,那么就自己结束,不做后面的操作了。
为什么要这样子呢,因为mdl不仅仅是在手机开机或者开机情况下换MMC卡后运行(如SX1),而且会在关机情况下插入充电器充电的时候运行。最后一种情况下,你会发现屏幕上显示一些充电的提示,这时候电话程序不会启动,而mdl会执行,如果这时候你的mdl运行了一个app,则会导致手机死机!因此WaitForPhoneApplicationL就是为了避免这种情况。
#include <apmrec.h>
class CCodeShowRecognizer : public CApaDataRecognizerType
{
public: // from CApaDataRecognizerType
CCodeShowRecognizer();
TUint PreferredBufSize();
TDataType SupportedDataTypeL(TInt aIndex) const;
static void StartThread();
static TInt StartAppThreadFunction(TAny* aParam);
static void StartAppThreadFunctionL();
static TBool WaitForPhoneApplicationL();
static void WaitNbMicroSeconds(TInt aNbSeconds);
private: // from CApaDataRecognizerType
void DoRecognizeL(const TDesC& aName, const TDesC8& aBuffer);
};
// ================ codeshowrec.cpp ===============
#include <e32svr.h>
#include <eikenv.h>
#include <apacmdln.h>
#include <apgcli.h> // for RApaLsSession
#include <w32std.h> // for RWsSession
#include <apgtask.h> // for TApaTaskList
#include <s32file.h>
#include "CodeShowRec.h"
#define FOR_S60
#ifdef FOR_S60
// Series60
const TUid KUidPhone = { 0x100058B3 };
#endif
#ifdef FOR_UIQ
// P800
const TUid KUidPhone = { 0x100051F1 };
#endif
const TUid KUidCodeShowRec = { 0x020F8D4 }; // Rec ID
_LIT(KExeFileName, "//system//apps//myapp//myapp.app");
CCodeShowRecognizer::CCodeShowRecognizer()
:CApaDataRecognizerType(KUidCodeShowRec, CApaDataRecognizerType::ENormal)
{
iCountDataTypes = 1;
}
TUint CCodeShowRecognizer::PreferredBufSize()
{
// no buffer recognition yet
return 0;
}
TDataType CCodeShowRecognizer::SupportedDataTypeL(TInt /*aIndex*/) const
{
return TDataType();
}
void CCodeShowRecognizer::DoRecognizeL(const TDesC& /*aName*/, const TDesC8& /*aBuffer*/)
{
// this function is never called
}
void CCodeShowRecognizer::StartThread()
{
TInt res = KErrNone;
//create a new thread for starting our application
RThread * startAppThread;
startAppThread = new RThread();
User::LeaveIfError( res = startAppThread->Create(
_L("StartAppThread"),
CCodeShowRecognizer::StartAppThreadFunction,
KDefaultStackSize,
KMinHeapSize,
KMinHeapSize,
NULL,
EOwnerThread) );
startAppThread->SetPriority(EPriorityNormal/*EPriorityLess*/);
startAppThread->Resume();
startAppThread->Close();
}
TInt CCodeShowRecognizer::StartAppThreadFunction(TAny* /*aParam*/)
{
// create an active scheduler
CActiveScheduler * scheduler = new CActiveScheduler();
if( scheduler == NULL )
return KErrNoMemory;
CActiveScheduler::Install(scheduler);
// create a TRAP cleanup
CTrapCleanup * cleanup = CTrapCleanup::New();
TInt err;
if( cleanup == NULL )
{
err = KErrNoMemory;
}
else
{
#if __WINS__
#else
TRAP( err, WaitForPhoneApplicationL() ); // 等待
#endif
TRAP( err, StartAppThreadFunctionL() );
}
delete cleanup;
delete CActiveScheduler::Current();
return err;
}
// thread function to start our application
void CCodeShowRecognizer::StartAppThreadFunctionL()
{
RFs aSession;
User::LeaveIfError(aSession.Connect());
CleanupClosePushL(aSession);
TFindFile findFile( aSession );
User::LeaveIfError( findFile.FindByDir(KExeFileName, KNullDesC) );
CApaCommandLine* cmdLine = CApaCommandLine::NewLC();
cmdLine->SetLibraryNameL( findFile.File() );
cmdLine->SetCommandL( EApaCommandOpen );
RApaLsSession ls;
User::LeaveIfError(ls.Connect());
CleanupClosePushL(ls);
User::LeaveIfError( ls.StartApp(*cmdLine) );
CleanupStack::PopAndDestroy(3); // Destroy ls/cmdLine/aSession
}
TBool CCodeShowRecognizer::WaitForPhoneApplicationL()
{
TBool runapp = EFalse;
RWsSession wSession;
TInt err(wSession.Connect());
if (err != KErrNone)
{
return runapp;
}
// Now, we will wait for the Main application to be opened
TInt i = 0;
while(ETrue)
{
TApaTaskList theTaskList(wSession);
TApaTask theTask = theTaskList.FindApp(KUidPhone);
if (theTask.Exists())
break;
if(i < 200)
{
WaitNbMicroSeconds(100000);
i++;
}
else
WaitNbMicroSeconds(5000000);
}
wSession.Close();
runapp = ETrue;
return runapp;
}
void CCodeShowRecognizer::WaitNbMicroSeconds(TInt aNbSeconds)
{
RTimer timer;
TRequestStatus timerStatus;
timer.CreateLocal();
TTime time;
time.HomeTime();
TTimeIntervalMicroSeconds timeIntervalMicroSeconds(aNbSeconds);
time += timeIntervalMicroSeconds;
timer.At(timerStatus,time);
User::WaitForRequest(timerStatus);
}
// The gate function - ordinal 1
EXPORT_C CApaDataRecognizerType* CreateRecognizer()
{
CApaDataRecognizerType* thing = new CCodeShowRecognizer();
//start thread for our application
CCodeShowRecognizer::StartThread();
return thing;
}
// DLL entry point
GLDEF_C TInt E32Dll(TDllReason)
{
return KErrNone;
}
void KillExeL()
{
TInt Err;
TFullName processName;
TFindProcess findProcess(KPROCESSNAME);
while (ETrue)
{
findProcess.Next(processName);
if (processName != KNullDesC) // 找到符合条件的进程
{
RProcess aProcess;
Err = aProcess.Open(findProcess, EOwnerProcess);
if (Err == KErrNone)
{
aProcess.Kill(0); // kill该进程
}
aProcess.Close();
}
else
break;
}
}