深度探索DxFramework


请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com

第2章 DxFramework的架构剖析 1

2.1 DxFramework的全局头文件

在开始着手分析DxFramework之前,我们有必要先弄懂DxFramework所定义的一些在其引擎代码内部中频繁使用的一些数据类型,宏,和全局函数。

众所周知,由于C++语言的灵活性和自由性;在不同的操作系统平台下,不同的编译器对同一关键字所指定的数据类型所占字节大小是不一致的——C++标准委员会对此也并没有做统一的要求。以int类型为例,C++标准委员会只是要求此类型的大小至少不小于16位。例如在Microsoft Win32平台下,Visual C++6编译器所实现的int是32位整形数据。而在DOS系统下,Borland C++ 3.1实现的int则只有16位。因此,为了代码的平台移植,或者是便于版本的数据类型升级和扩充,一般的大型软件系统都有其自定义的数据类型。以Microsoft定义的win32数据类型为例,就有诸如无符号双字类型,32位的DWORD;无符号单字类型,十六位的WORD;无符号的整形数据,UINT等等。DxFramework也不例外,它也有着它自身的预定义数据类型,它们定义在globals.h文件中,现在我们来逐个分析。代码如下:

//globals.h
#define WIN32_LEAN_AND_MEAN
#define DIRECTINPUT_VERSION 0x0800    //指定使用的DInput版本

/*
忽略编号为4786的编译器警告 当变量标识符名字过长(超过255个字符)的时候,调试器无法调试变量标
识符号超过255个字符长度的代码。无法在调试器中查看、计算、更新或监视被截断的符号。调试器在调试过程中
将会把变量标识符“截短”至255个字符。有时在嵌套声明的变量名中,会出现一串长的警告,在使用STL 的时候,
这种警告会经常出现,使用此指令可以关掉此警告
*/

#pragma warning(disable: 4786)

//用来进行内存侦测的起到开关作用的宏

#ifdef _DEBUG
#define _CRTDBG_MAP_ALLOC    //定义此宏,使得CRT内存泄漏侦测函数可用,此宏在crtdbg.h中定义

#define _INC_MALLOC
#define enw DEBUG_NEW
#endif


//定义使用UNICODE的宏

#define UNICODE
//如果宏UNCODE定义了,则把宏_UNICODE也定义

#ifdef UNICODE
#define _UNICODE
#endif

#define USE_DOUBLE_FLOAT_PRECISION

/*
如果定义了USE_DOUBLE_FLOAT_PRECISION宏 XVECTOR2定义为用双精度浮点数类型实例化模板类
类型的DVECTOR2替代为,XFLOAT定义为如果没有定义的话,就把XVECTOR2定义为单精度浮点数
实例化的模板类类型FVECTOR2,XFLOAT定义为floatFVECTOR2,DVECTOR2类型在vector2.h中定义,
稍后详述
*/

#ifdef USE_DOUBLE_FLOAT_PRECISION
#define XVECTOR2 DVECTOR2
#define XFLOAT double
#else
#define XVECTOR2 FVECTOR2
#define XFLOAT float
#endif


//预包含engine所要使用的系统头文件
#include <tchar.h>
#include <stdlib.h>
#include <crtdbg.h>
#include <windows.h>
#include <exception>
#include <d3d8.h>
#include <d3dx8.h>
#include <Dinput.h>
#include <dshow.h>
#include <dsound.h>
#include <ctime>
#include <dplay8.h>
#include <dxerr8.h>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <string>
#include <map>
#include <list>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
using namespace std;

//使用STL的basic_string模板类来定义
//DxFramework的字符串类tstring
//TCHAR是单字节还是双字节,取决于代码编译时,宏UNICODE启动与否

typedef std::basic_string<TCHAR> tstring;

//定义了版本号的字符串常量
const tstring ENGINE_ENGINE_VERSION_TSTRING = _T("0.9.3");
#define ENGINE_ENGINE_VERSION 0x0093

//引擎所需的各种图片,音频,视频资源所存放的目录
const tstring    IMAGE_DIR = _T("./images/"); 
const tstring    SOUND_DIR = _T("./sounds/");
const tstring    VIDEO_DIR = _T("./video/");

从物理学知识可以知道,任何一样颜色的可见光都是由不同强度的红光,绿光,蓝光混合而成。在计算机图形学中,人们通常使用一个四元组来描述一个光的颜色的组成。这四元组是透明度Alpha,红色分量Red,绿色分量Green,蓝色分量Blue组成。一般地,各个分量都可以分成256级。在Direct3D中,通常使用区间[0.0,1.0]内的值表示分量的强度。

//一些经常使用的颜色,使用了Direct3D提供的D3DCOLOR数据类型和宏D3DCOLOR_ARGB宏来定义实现

const D3DCOLOR BLACK = D3DCOLOR_ARGB(255,0,0,0);//黑色
const D3DCOLOR RED = D3DCOLOR_ARGB(255,255,0,0);//红色
const D3DCOLOR GREEN = D3DCOLOR_ARGB(255,0,255,0);//绿色
const D3DCOLOR BLUE = D3DCOLOR_ARGB(255,0,0,255);//蓝色
const D3DCOLOR MAGENTA = D3DCOLOR_ARGB(255,255,0,255);//洋红色
const D3DCOLOR CYAN = D3DCOLOR_ARGB(255,0,255,255);    //青色
const D3DCOLOR YELLOW = D3DCOLOR_ARGB(255,255,255,0);//黄色
const D3DCOLOR WHITE = D3DCOLOR_ARGB(255,255,255,255);//白色

//安全地释放对象指针,对象数组,COM对象
#define SAFE_DELETE(p){ if(p) { delete (p); (p)=NULL; } }
#define SAFE_ARRAY_DELETE(p){if(p){delete [](p);(p)=NULL;}}
#define SAFE_RELEASE(p) {if(p){(p)->Release();(p)=NULL; }}


//把一个vector类型的STL容器清空
#define FLUSH_VECTOR(v){while(!v.empty())\
                        v.pop_back();v.clear();}

2.2 DxFramework的异常处理机制

2.2.1 异常的概念和C++的异常处理机制

异常(exception) 是指可能能被检测到的,程序运行时刻不正常的情况。比如用除法运算时除数为0; 数组越界访问;内存耗尽;被要求打开一个并不存在的磁盘文件,等等。异常往往存在于程序的正常代码之外,而且要求程序要立即转入处理。

C++提供了一些语言特性来产生(raise) 和处理异常。这些语言特性将激活了一种运行时(runtime)机制。通过这种机制,C++程序的两个无关的代码模块可以进行异常通信。

当C++程序中出现异常时,侦测到异常的程序段可以通过产生(raise) 或者是抛出(throw) 异常,告知系统有异常发生。 关于在Windows平台上对C++语言的异常机制的实现原理,可参阅Jeffey Ritcher的《Advanced Windows Program》中关于结构化异常的描述的章节。

2.2.2 DxFramework对异常描述的封装

DxFramework使用类Error来描述异常信息,通过抛出一个Error实例对象来被异常处理器截获,从而转到异常处理流程。类Error在engine\error.h文件中声明,在engine\error.cpp文件中定义。

类Error除了使用文本字符串描述异常之外,还能提供给用户发生该异常的代码所在文件名和代码的行号。除此之外,还能返回给用户调用DirectX函数时所得到的HRESULT类型的返回值。类Error的定义如下:

class Error : public exception  // 异常信息描述类
{
public:
    Error(tstring message);
    Error(tstring message, char* file, DWORD line);
    Error(tstring message, char* file,  DWORD line, HRESULT hr);
    void Notify() const;
    void Prompt() const;
private:
    tstring AssembleOutput() const;    
    enum EXCEPTION_TYPE // 异常信息描述类的类型
    {
        EXCEPTION_MESSAGE,    // 仅仅使用文本字符串描述该异常
        EXCEPTION_REGULAR,    // 使用文本字符串,异常所在文件名, 异常代码所在行号描述该异常        
        EXCEPTION_DX        // 使用文本字符串,异常所在文件名,异常代码所在行号,
                            // 以及调用DirectX库函数的HRESULT类型返回值,描述该异常
    }; 
    EXCEPTION_TYPE type;    // 异常描述类的类型,在每个不同的构造函数内指定不同的类型
    tstring title;            // 在表示此异常错误信息时,用到的 message box所使用的标题
    tstring message;  // 描述此异常错误信息的字符串
    tstring file;            // 产生此异常的代码所在的文件的名字
    DWORD line;    // 产生此异常的代码所在的代码行行号
    HRESULT hr;   // Windows/DirectX的HRESULT类型的函数返回值码
};

类Error继承自标准C++库的exception类。

Error::Error(tstring message) :
title(_T("Engine Exception")),
type(EXCEPTION_MESSAGE), 
message(message) {}

Error::Error(tstring message, char* file, DWORD line) : 
title(_T("Engine Exception")), type(EXCEPTION_REGULAR), 
message(message), file(toTString(file)), line(line) {}

Error::Error(tstring message, char* file, DWORD line, HRESULT hr) :
title(_T("Engine Exception")), type(EXCEPTION_DX), 
message(message), file(toTString(file)), 
line(line), hr(hr) {}

类Error有三个显式的构造函数,可以根据对异常描述详细程度的不同需要而实例化相对应Error对象出来。一般地,在构造Error对象的时候,通过使用宏FILELINE,便可以得到描述此对象所在的文件的名字,以及此代码所在的行号。在DxFramework中大量地使用了这样的技术。

tstring Error::AssembleOutput() const 
{
    tstring output(message);
    switch (type) 
    {
    case EXCEPTION_DX:
        output += _T("\nDirectX Error String: ")+(tstring)DXGetErrorString8(hr);
    case EXCEPTION_REGULAR:
        output += _T("\n")+ file + _T("\nLine ")+toTString(line);
    case EXCEPTION_MESSAGE:
    default:
        break;
    }
    return output;
}

上述的私有成员函数AssembleOutput()的功能是:根据该异常信息描述类的类型,格式化不同的信息描述字符串,并返回。如果该异常信息描述类的类型是EXCEPTION_DX的话,通过调用D3D库函数:const char WINAPI* DXGetErrorString8 (HRESULT hr)可以根据错误返回码,返回相关的描述错误信息的字符串。接着再添加上产生异常的代码所在的文件的文件名,以及所在行号数。

void Error::Prompt() const 
{
    // 获取到产生异常的原因描述字符串,并且向用户询问是否退出本程序
    tstring output = AssembleOutput() + _T("\nQuit Program?");
    ShowCursor(true);//显示鼠标光标
    int ret = MessageBox(g_WindowHnd, output.c_str(),title.c_str(),MB_YESNO | MB_ICONERROR );
    if (ret == IDYES) //如果用户选择彻底退出
    {
        // 再次提问用户是否真的要退出
        ret = MessageBox(g_WindowHnd, _T("Terminate now (exit(1)) ?"), _T("Eject! Eject!"),
                        MB_YESNO | MB_ICONQUESTION );
        if (ret == IDYES)
        {
            exit(1);
        }
        else
        {
            // 向OS发送退出本执行线程的请求
            PostQuitMessage(0); 
        }
    }    
}

上述的函数通知用户有异常产生,并且提示用户对此异常进行相关处理。调用此函数后,将会有一个Windows消息框弹出,用户可以选择忽略此异常,也可以选择让程序尝试彻底地停止运行并退出。

void Error::Notify() const 
{
    tstring output = AssembleOutput();
    ShowCursor(true);
    // Windows消息只提供了“确定”按钮,用户点击后程序将尝试退出。
    MessageBox(g_WindowHnd, output.c_str(), title.c_str(), MB_OK | MB_ICONERROR );
}

执行上述函数将会弹出一个Windows消息框,通知用户有异常产生。程序在用户点击了Windows消息框的确定按钮之后,将尝试着彻底退出运行。所有在WinMain函数中被捕获到的描述异常的类Error实例对象,都将调用此成员函数。见WinMain函数代码,如下:

// 在main.cpp文件中
int WINAPI WinMain( HINSTANCE instanceHnd, HINSTANCE prevInstanceHnd,char* p_CmdLine, int numCmds)
{
    //...
    try
    {
        //...
    }
    catch(Error e)
    {
        // 截获在try{}代码块中被抛出(thrown)的异常,调用Error::Notify函数处理
        e.Notify();    
    }
    // 截获由C++标准库抛出的异常,告诉用户发生了何种异常,然后退出
    catch(exception e)
    {
        MessageBox(g_WindowHnd, toTString(e.what()).c_str(), 
                    _T("Unhandled Exception, aborting"), MB_OK | MB_ICONERROR );
    }
    SAFE_DELETE(p_Game);// Destroy the game and all objects

    // DO NOT ADD CODE AFTER THIS LINE!
    // (except the possible change of the return value)

    _CrtMemDumpAllObjectsSince(&memstate);    
    // Dump memory leaks, see comment at top of this function.

    return 0; //could return a more useful value here
}

返回首页
上一章
下一章