电脑技术学习

编写安全的Symbian C++游戏代码

dn001

除了上面说的特殊情况,你可以简单的弹出一个对话框,告诉用户没有足够的内存运行程序,并且安全的关闭程序。比如我的游戏程序就是这样处理的:
void CStageManager::DoGameFrame()
{
;TRAPD(error, DoGameFrameProtectedL());
;if (error == KErrNoMemory)
;{
;;StopGame();
;;m_noMemoryDlg->ExecuteLD(R_KEY_INVALID_DIALOG);
;;Exit();;;// Call CAknAppUi::RunAppShutter( )
;}
;else if (error != KErrNone)
;{
;;User::Panic(_L("Some other error."), error);
;}
}
其中noMemoryDlg是直接或者间接在Container的ConstructL中创建的:
// in header file
CAknQueryDialog* m_noMemoryDlg;
// somewhere in ConstructL
TBuf<128> errMsg;
_LIT(formater, "Not enough memory. Please close some applications.");
errMsg.Copy(formater);
m_noMemoryDlg = new (ELeave) CAknQueryDialog(errMsg, CAknQueryDialog::EErrorTone);
当然,你也可以制作一个精美的图片来报告内存不足,等待用户按任意键再退出。不过载入这个图片也可能会失败,所以至少在这个图片成功载入之前,你还是需要系统对话框来报告的。
值得一提的是,你不一定需要退出程序,或者你可以稍后重试申请内存,幸运的话,没准第二次就能成功。这是因为Symbian系统会在内存不足时自动关闭一些应用程序。我觉得这是Symbian系统一个比较奇怪的设计。通常应用程序在AppUi的HandleCommandL中会响应EEikCmdExit消息,并且调用CAknAppUi::Exit( )函数(如下代码)。这使得应用程序可以在应用程序管理器中用C键结束掉。这也使得Symbian操作系统有机会在内存不足时通过这个渠道自动关闭一些应用程序。
// ----------------------------------------------------
// CFlyAppUi::HandleCommandL(TInt aCommand)
// takes care of command handling
// ----------------------------------------------------
//

void CFlyAppUi::HandleCommandL(TInt aCommand)
{
;switch ( aCommand )
;{
;case EEikCmdExit:
;;Exit();
;;break;
;// TODO: Add Your command handling code here;
;default:
;;break;;;;
;}
}
坦白说我没有尝试过重试申请内存这个办法,不过我想是可行的。

1.3.4.;栈回滚和对象的安全析构
上面说到在遇到某些异常时,你可以选择弹出对话框并且结束程序,其实这会比你想象的要困难一些。因为C++可不像Java那样有托管堆进行垃圾收集。不过好在C++栈会自动回滚,栈上的对象会被销毁。如果你此时调用CAknAppUi::RunAppShutter( )结束程序,那么AppUi,Container的析构函数会依次被调用,引起你自己创建对象的析构函数也依次被调用。那么堆上的对象也要被销毁。可是,请记住,异常随时随处可能发生,使对象处于一种"半构造"的状态。此时析构函数被调用可能会造成对无效指针的访问错误。请看下面这个例子,它犯了两个常见的错误:
class BadExample
{
protected:
;TText8* m_pBuf;
;TText8* m_pBuf2;
public:
;static BadExample* NewL()
;{
;;BadExample* self = new (ELeave) BadExample();
;;self->ConstructL();
;;return self;
;}

void DeleteBuf()
;{
;;delete m_pBuf;
;}

void RebuildBufL()
;{
;;m_pBuf = new (ELeave) TText8[256];
;}

private:
;BadExample();
;~BadExample()
;{
;;delete m_pBuf;
;;delete m_pBuf2;

}
;void ConstructL()
;{
;;m_pBuf = new (ELeave) TText8[256];
;;m_pBuf2 = new (ELeave) TText8[256];
;}
};
假设我们在AppUi的ConstructL中使用BadExample::NewL( )来构造对象,在AppUi的析构函数中delete这个对象。

下面我们分析一下可能遇到的问题:
首先,在函数NewL中,self指针没有被保护,试想如果self->ConstructL( )一句抛出异常。那么这个self指针指向的对象就没有return给外界(也就是AppUi),这个对象就永远"丢失了",造成了内存泄露。正确的做法是使用CleanupStack对它进行保护。CleanupStack至少能保证在程序退出时压入其中的对象都能销毁。
static BadExample* NewL()
{
;BadExample* self = new (ELeave) BadExample();
;CleanupStack::PushL(self);
;self->ConstructL();
;CleanupStack::Pop();
;return self;
}
但是注意,此处还有一个微妙的内存泄露。仔细看看CleanupStack::PushL( )的声明:
IMPORT_C static void PushL(TAny* aPtr);
IMPORT_C static void PushL(CBase* aPtr);
IMPORT_C static void PushL(TCleanupItem anItem);
如果传入的指针是CBase指针,那么CBase的虚析构函数(virtual ~CBase( ))就能保证对象在销毁时正确的调用析构函数。可是本例中BadExample不是从CBase中派生,那么对象只能做很有限的销毁,根本不会调用析构函数。所以,如果ConstructL是由于第二个内存申请m_pBuf2失败,那么m_pBuf申请的内存就永远不会回收。所以正确的做法是,让BadExample从CBase派生。
class BadExample : public Cbase
其次,我们并没有为m_pBuf和m_pBuf2赋初值,在Release版中他们的值是随机的。那么,如果m_pBuf2的申请失败,析构函数还是会执行delete m_pBuf2,试图删除一个无效指针。正确的做法是在构造函数中为m_pBuf和m_pBuf2赋初值NULL。因为标准C++规定,delete一个空指针不做任何操作。不过实际上,如果对象从CBase派生,这一步是没有必要的,因为CBase能保证派生类的成员变量在构造时自动清零。
最后,动态的使用DeleteBuf和RebuildBufL是不安全的。如果你先用DeleteBuf删除了这个对象,那么m_pBuf就是一个坏指针。可是紧接着的RebuildBufL可能会失败。此时如果析构函数被调用,还是会产生delete无效指针的错误。正确的做法是,在DeleteBuf中,把m_pBuf设为NULL。
总结上面说到的几点,完整的安全的代码是:
class BadExample : public CBase
{
protected:
;TText8* m_pBuf;
;TText8* m_pBuf2;
public:
;static BadExample* NewL()
;{
;;BadExample* self = new (ELeave) BadExample();
;;CleanupStack::PushL(self);
;;self->ConstructL();
;;CleanupStack::Pop();
;;return self;
;}

void DeleteBuf()
;{
;;delete m_pBuf;
;;m_pBuf = NULL;
;}

void RebuildBufL()
;{
;;m_pBuf = new (ELeave) TText8[256];
;}

private:
;~BadExample()
;{
;;delete m_pBuf;
;;delete m_pBuf2;
;}
;void ConstructL()
;{
;;m_pBuf = new (ELeave) TText8[256];
;;m_pBuf2 = new (ELeave) TText8[256];
;}
};

1.4.;安全的图像引擎

;;Symbian C++游戏的2D图像显示部分一般由下面几个类组成:
;;图像 - 封装了一个CWsBitmap。是基本的图片资源。支持图像之间的各种贴图和混合操作。
;;双缓冲 - 一个和屏幕分辨率、色深相等的图像。
;;直接写屏支持 - 复合一个CDirectScreenAccess对象,实现MDirectScreenAccess接口。负责直接写屏的安全处理。比如来电、屏保时适时的停止和开启直接写屏与游戏逻辑。
;;绘图类 - 负责在图像中绘图。它不是对Gc的封装,而是通过直接修改图像内存区进行绘图。
;;位图字体类 - 使用预先创建的位图资源写字。如下图就是一个预先创建的位图资源。优点是速度快,缺点是无法支持大字符集合,比如中文。