第二章Direct 3D程序的Windows程序代码

 

2. Direct 3D程序的Windows程序代码

Microsoft Direct3D的程序可以设计成只在全屏幕模式或窗口模式下执行-或者是在二者下皆可执行。当处于全屏幕模式下时,程序会接管整个屏幕,这也是比较普及的作法(基于许多理由)。它可以让您的程序选择切换屏幕分辨率及色板层次,以便在硬件上有最好的表现。它也可以提供使用者较好的体验,因为它也允许切换绘图页并且不需要更新其它程序的图形,所以通常能提供更高的效率。另一方面,有时在窗口模式下执行Direct3D程序会比较好-通常是当使用者也想在屏幕上看到其它程序的输出画面时。

我认为最好是开发全屏幕和窗口模式二者均可执行的程序代码,并且允许使用者在二种模式间切换。虽然这样做会对您的开发作业增加一些时间和复杂度,却可以让使用程序的人选择他们适用的环境。更重要的是,这样可以让您的应用程序配合更多的硬件加速卡得到最好的执行效果。如果有一个使用者正在一个具有二种加速卡的环境下作业(一种支持窗口应用,而另一种只支持全屏幕模式),您的软件可以辨别不同加速卡的功能,并且提供使用者选择增加速度或增加功能。此外,在窗口模式下较易除错。如果要在全屏幕模式下有效的除错,您必须要另接一台屏幕或是用远程除错(remote debugger)。

在您试着开发一个Direct3D应用程序之前,最好先了解Microsoft Windows程序的开发方式。本章会说明要产生一个叫作RoadRageDirect3D应用程序时的必要的Windows程序代码。RoadRage利用了在第一章提到的Direct3D程序框架,并且同时支持窗口模式和全屏幕模式。您可以在随书光盘上的RoadRage系统档案roadrage.dswChap2子项目中找到关于本章的实际程序代码。这些程序代码是包装在主要的CMyD3DApplication类别及其它的C++类别中。它说明了如何产生一个窗口及附属的菜单来控制Direct3D应用程序中的Direct3D参数(例如烟雾和上色)。本书重心会放在产生RoadRage应用程序上。 第三章 ,您可以学到加入以DirectDraw为主的程序,用来辨识执行环境的2D功能,并且让您的应用程序可在窗口及全屏幕模式下切换(依不同的分辨率)。本章的最后,您会知道如何加入程序来启动菜单中的特性(如烟雾和贴图),并且提供其它的功能,包括动态角色和网络联机游戏。

如何将您的Windows程序代码结构化
 

一个典型应用程序的Windows部分程序代码是由下列程序代码区块所构成的:

  • 设定函式档和定义全域唯一识别码GUID的程序代码。
     
  • WinMain,用来设定主窗口和讯息处理。
     
  • 定义主窗口样式的程序代码(在RoadRage中,这是由CMyD3DApplication CD3DApplication类别里的程序代码来处理)。
     
  • 主要的窗口讯息处理函式(在RoadRage中,这是由d3dapp.cppWndProc负责将讯息传给CMyD3DApplication::MsgProcCD3DApplication::MsgProc)。
     

让我们试着浏览一遍所有Windows相关程序每一部分的实作内容。假如您将随书光盘上的程序代码安装好,并且打算依照本章所讨论的内容,请在Microsoft Visual C++中的 File 菜单中点选 Open Workspace ,点选 roadrage.dsw ,并且按下 Open Roadrage.dsw是本书的系统文件。如果要把 第二章 的内容变成作用中的项目,请在Workspace窗口中选择 FileView ,鼠标右键点选Chap2,并且从关联菜单中选择 Set as Active Project 

指定函式档和定义GUID的程序代码
 

凡使用到DirectX的程序都需要定义特定的GUID(例如IID_IDirectDraw7),这样才能成功的编译。GUID是一种全域变量而非常数,所以您必须再定义储存空间。一种可将需要的GUID并入您程序的作法,是在建立项目时将dxguid.lib含括在您的函式库中。或者您喜欢另一种方式,就是在您括入标头档windows.hdddraw.hd3d.h及使用任何其它的 #define定义前,先用下列方式在某个原始程序模块中定义符号INITGUID

#define INITGUID

如果您忘了定义INITGUID或括入dxguid.lib,您在编译DirectX程序代码时会碰到一大堆错误。


提示

要编译一个有用到DirectX的程序,您必须让您的编译器能用到包含最新的DirectX函式及函式库档案的目录。为了确定Visual C++的设定正确,从 Tool 菜单中点选 Options ,并按下 Directories 标签页。确定包含DirectX函式文件的路径(如C:/mssdk/include)要存在目录清单中。再使用 Show Directories For control 指令来切到Library Files设定处,确定包含DirectX函式文件的路径(如C:/mssdk/lib)要存在目录清单中。假如您没这样作,Visual C++会直接引用Visual C++出厂时设定的DirectX标头档和函式档,这通常是不对的。


函式档会在档案roadrage.cpp的开头指定(在其它的c++模块也是如此)。您可以在本章的项目目录中找到标头文件resource.hd3dapp.hroadrage.hppResource.h定义了RoadRage程序所用到的Windows资源。D3dapp.h则是d3dapp.cpp的标头档,其中包含了本章所提到的大部分程序代码,也会包含主事件处理(main-event-handling)程序及后几章所提到关于RoadRage的成像相关程序。

Roadrage.cpp中定义的CMyD3DApplication类别,将涵盖RoadRage中负责处理所有主要动作的程序,如建立3D世界成像及3D世界的使用者控制、声音及多人对战。CMyD3DApplication用到DirectXDirectInputDirectSoundDirectPlay组件,并从CD3DApplication类别中继承了它的基本属性。

CMyD3DApplicationCD3DApplication类别
 

以下的程序代码(摘自roadrage.hpproadrage.cpp)定义了CMyD3DApplication类别,类别的预设建构元(constructor),及WinMain函式(应用程序的主要进入点)。

#ifndef __ROADRAGE_H
#define __ROADRAGE_H
//-------------------------------------------------------------------
//名称:CMyD3Dapplication类别
//叙述:应用程序类别。可提供所有需要功能的基本类别
//所以我们只要提供管道作为和程序中的非C++函式间
//的界面。
//-------------------------------------------------------------------
class CMyD3DApplication :public CD3DApplication
{
public:
        CMyD3DApplication();
        

  
  
   
    
  
  
        void DisplayCredits(HWND hwnd);
        void DisplayRRStats(HWND hwnd);
        void DisplayLegalInfo(HWND hwnd);
        LRESULT MsgProc(HWND hWnd,UINT uMsg,WPARAM wParam,
                                   LPARAM lParam );
                                   
        static HRESULT hr;
        HINSTANCE hInstApp;
};
#endif //__ROADRAGE_H

  
  
   
    
  
  
///------------------------------------------------------------------
//File:RoadRage.cpp
//
//说明:RoadRage的主程序.The CMyD3DApplication类别处理
//了大部分的RoadRage功能。
//
//Copyright (c)1999 William Chin and Peter Kovach.All rights
//reserved.

  
  
   
    
  
  
//-------------------------------------------------------------------
#define STRICT
#define D3D_OVERLOADS
#include <math.h>
#include <time.h>
#include <stdio.h>
#include "D3DApp.h"

  
  
   
    
  
  
//-------------------------------------------------------------------
//名称:WinMain
//说明:Entry point to the program.Initializes everything,and goes
//into a message-processing loop.Idle time is used to render
//the scene.
//-------------------------------------------------------------------
INT WINAPI WinMain(HINSTANCE hInst,HINSTANCE, LPSTR strCmdLine,INT)
{
        CMyD3DApplication d3dApp;
        d3dApp.hInstApp =hInst;
        if(FAILED(d3dApp.Create(hInst,strCmdLine)))                                           
        return 0;
        d3dApp.Run();
        CoUninitialize();                                                                             
        return TRUE;
}

  
  
   
    
  
  
//-------------------------------------------------------------------
//名称:CMyD3DApplication
//说明:Application建构元。设定应用程序的属性。
//
//-------------------------------------------------------------------
CMyD3DApplication::CMyD3DApplication()
{
        m_strWindowTitle =TEXT("Chapter 2"));
        pCMyApp =this;
}

CMyD3Dapplication类别是从CD3DApplication类别中衍生而来的,而CD3Dapplication类别则是Direct3D程序框架的一部分,并且提供了一些我们会用到的相关变量。CD3Dapplication也提供了建立新应用程序的方法和负责处理许多程序会接收到的Windows讯息。在本章中,我们会用到一个CD3DApplication的简化版本,而不直接引用在标准Direct3D程序框架中定义的完整版本。CD3DApplication类别是定义在d3dapp.h中,您可以在本章的项目目录中找到,如下:

//-------------------------------------------------------------------
//File:D3DApp.h
//
//说明:Direct3D范例程序框架链接库的应用程序类别
//
//
//-------------------------------------------------------------------
#ifndef D3DAPP_H
#define D3DAPP_H
#define D3D_OVERLOADS
#include <d3d.h>
//-------------------------------------------------------------------
//名称:CD3Dapplication类别
//说明:
//-------------------------------------------------------------------
class CD3DApplication
{
        //内部变数和成员函式
        BOOL m_bActive;
        BOOL m_bReady;
protected:
        HWND m_hWnd;
        //应用程序可更动的变量
        TCHAR*         m_strWindowTitle;
        //可更动的电源管理(APM)函式
        virtual LRESULT OnQuerySuspend(DWORD dwFlags);
        virtual LRESULT OnResumeSuspend(DWORD dwData);
public:
        //负责建立,执行,暂停,和清除应用程序的函式
        virtual HRESULT Create(HINSTANCE,LPSTR);
        virtual INT Run();
        virtual LRESULT MsgProc(HWND hWnd, 
                                 UINT uMsg, 
                                 WPARAM wParam, 
                                 LPARAM lParam);
        //存取函式
        HWND Get_hWnd(){return m_hWnd;};
        BOOL GetbActive(){return m_bActive;};
        BOOL GetbReady(){return m_bReady;};
        VOID SetbReady(BOOL val){m_bReady =val;};
        VOID SetbActive(BOOL val){m_bActive =val;};
        //类别建构元
        CD3DApplication();
};
#endif //D3DAPP_H

设定主窗口和讯息处理
 

WinMain程序就是Windows-based应用程序的主函式。系统会呼叫这个函式作为应用程序的起始进入点。

我们实作出的WinMain用到CMyD3Dapplication的预设建构元来建立应用程序CMyD3DApplication类别的实例,并且呼叫类别的Create方法来产生应用程序主窗口的实例。CMyD3DApplicationCD3DApplication继承了它的Create方法,并且使用CD3DApplication的建构元。以下是CD3DApplication类别的建构元:

//-------------------------------------------------------------------
//名称:CD3D应用程序
//说明:CD3Dapplication类别的建构元
//-------------------------------------------------------------------
        CD3DApplication::CD3DApplication()
{
        m_hWnd =NULL;
        m_bActive =FALSE;
        m_bReady =FALSE;
        m_strWindowTitle =_T("Direct3D Application");
        g_pD3DApp =this;
}

这个建构元会在我们建立这种类别的实例时,将程序中的相关变量予以初始化。

CD3DApplication::Create方法的定义如下:
//-------------------------------------------------------------------
//名称:Create
//说明:建立程序的主窗口
//-------------------------------------------------------------------
HRESULT CD3DApplication::Create(HINSTANCE hInst,CHAR*strCmdLine)
{
        //登录窗口类别
        WNDCLASS wndClass ={0,WndProc,0,0,hInst, LoadIcon(hInst,                                                     
                              MAKEINTRESOURCE(IDI_MAIN_ICON)),                                                               
                              LoadCursor(NULL,IDC_ARROW),                                                            
                         (HBRUSH)GetStockObject(WHITE_BRUSH),                                                
                         NULL,_T("D3D Window")};
        RegisterClass(&wndClass);
        //建立绘制窗口。
        m_hWnd =CreateWindow(_T("D3D Window"),m_strWindowTitle,                                                              
                               WS_OVERLAPPEDWINDOW|WS_VISIBLE,                                                       
                              CW_USEDEFAULT,CW_USEDEFAULT,640,480,0L,-                                              
                              LoadMenu(hInst,MAKEINTRESOURCE(IDR_MENU)),                                            
                              hInst,0L );
        if (!m_hWnd)
        {
               LPVOID lpMsgBuf;
               FormatMessage(
               FORMAT_MESSAGE_ALLOCATE_BUFFER|
               FORMAT_MESSAGE_FROM_SYSTEM|
               FORMAT_MESSAGE_IGNORE_INSERTS,
               NULL,
               GetLastError(),
        //预设语系
               MAKELANGID(LANG_NEUTRAL,SUBLANG_DEFAULT),                                             
               (LPTSTR)&lpMsgBuf,                                                                           
               0,                                                                                           
               NULL);
        //
        //处理任何在lpMsgBuf的插入指令
        //...
        //显示字符串
        //
        MessageBox(NULL,(LPCTSTR)lpMsgBuf,"Error",                                                                   
                          MB_OK |MB_ICONINFORMATION);
        //Free the buffer.
        
        LocalFree(lpMsgBuf);
 }
 UpdateWindow(m_hWnd);
 //应用程序准备好了。
 m_bReady =TRUE;
 return S_OK;
}

Create方法用到WNDCLASS型态来建立一个窗口类别,设定这个窗口类别的属性,使用RegisterClass函式来记录窗口类别,并且利用CreateWindow函式来建立以记录窗口为基础的新窗口。窗口类别的属性包括了窗口的光标、窗口图标及窗口菜单。CD3DApplication::Create会用到下列函式来填入及记录WNDCLASS结构:

  • LoadIcon函式负责加载图示,并命名为IDI_MAIN_ICON,结合在程序中。
     
  • LoadCursor函式负责加载游标。在本项目中,标准的箭头光标就够用了。
     
  • 呼叫GetStockObject函式可以取得任一预先定义的画笔、调色盘、笔刷及字型的掌控权。在这个部分,我们先取得的是白色笔刷。
     

提示

您不需要透过呼叫DeleteObject来删除对象,但是如果您这样做,也不会引发错误。要记住,您不可以去调整笔刷原点,而且NULL_BRUSHHOLLOW_BRUSH对象是一样的。


一旦定义了窗口类别,下一步就是要建立窗口。建立窗口是很容易的,虽然整个建立的指令包含了许多参数。CD3DApplication::Create引用了Windows函式CreateWindow来建立主窗口。CreateWindow函式的定义如下:

HWND CreateWindow(
        LPCTSTR lpClassName,
        LPCTSTR lpWindowName,
        DWORD dwStyle,
        int x,
        int y,
        int nWidth,
        int nHeight,
        HWND hWndParent,
        HMENU hMenu,
        HINSTANCE hInstance,
        LPVOID lpParam
);

参数

说明

lpClassName

包含登记类别名称且以null结尾的字符串指针

lpWindowName

包含窗口名称且以null结尾的字符串指针

dwStyle

窗口型态

x

窗口的水平位置

y

窗口的垂直位置

nWidth

窗口宽度

nHeight

窗口高度

hWndParent

父窗口或自身窗口的控制代码

hMenu

菜单控制或子识别码

hInstance

程序案例的控制代码(Windows 2000中无作用)

lpParaml

建立窗口的数据

设定讯息处理循环
 

在任何的Windows-based应用程序中,您都必须定义讯息处理循环来让应用程序可以处理它所接收到的任何讯息。RoadRageCD3DApplication::Run就是负责这件事的程序,如下所示:

//-------------------------------------------------------------------
//名称:Run
//说明:讯息处理循环。利用闲置时间来绘制场景。
//-------------------------------------------------------------------
INT CD3DApplication::Run()
{
        //加载键盘加速器。
        HACCEL hAccel =LoadAccelerators(NULL,
               MAKEINTRESOURCE(IDR_MAIN_ACCEL));
        //现在要开始接收和处理Windows讯息。
        BOOL bGotMsg;
        MSG msg;
        PeekMessage(&msg,NULL,0U,0U,PM_NOREMOVE);
        while(WM_QUIT !=msg.message)
        {
               //如果程序在作用中时,可以用PeekMessage,这样
               //我们即可利用闲置时间来绘制场景。如果程序没有
               //在作用,就使用GetMessage以避免占用CPU时间。
               if(m_bActive)
                       bGotMsg =PeekMessage(&msg,NULL,0U,0U,PM_REMOVE);
               else
                       bGotMsg =GetMessage(&msg,NULL,0U,0U);
               if(bGotMsg )
               {
                       //翻译和分派讯息。
                       if(0 ==TranslateAccelerator(m_hWnd,hAccel,&msg))
                       {
                               TranslateMessage(&msg);
                               DispatchMessage(&msg);
                       }
               }
        }
        return msg.wParam;
}

这个程序中,在真正进入讯息处理循环前的第一个呼叫函式是LoadAccelerators。以下是LoadAccelerators的函式宣告:

HACCEL LoadAccelerators(
HINSTANCE hInstance ,
LPCTSTR lpTableName
);

参数

说明

hInstance

应用程序实例的控制代码

lpTableName

表格名称字符串的地址

这个程序是用来加载应用程序的快速键。在Windows环境下,快速键定义了用来存取各种菜单选项的快捷方式。

2-1显示当您在建构一个菜单时, Microsoft Visual C++资源编辑器窗口的状态。


 

2-1 建立应用程序的菜单

要在Visual C++中加入一个新的菜单,从 Insert 菜单中点选 Resource 就会出现如图2-2中的 Insert Resource 对话盒。在Insert Resource对话盒里,从 Resource Type 控件中点选 Menu ,并且按下 New 按钮。您可以自行替这个菜单命名。


 

2-2 在应用程序的菜单中插入一个菜单资源

2-3Visual C++资源编辑器正显示着RoadRage应用程序所定义的快速键。如果您想的话,可以自行加入或修改这些项目。在右边的面板,ID字段显示可以启动的菜单项目名称,Key字段则显示快捷方式的按键序列,而Type字段则记录按键型态(ASCIIVIRTKEY)。


 

2-3 设定菜单的快捷方式

在您建好这些菜单后,您必须去定义如何处理这个菜单和各种窗口对象所产生的讯息。关于作用在每个发生事件上的讯息撷取和分派循环的相关作业,才是程序的重点所在。当系统收到WM_QUIT讯息时,它会去终止这个程序,并让WinMain传回在讯息wParam参数中传递的值。如果这个程序在进入讯息循环前便已终止,它则传回FALSE值。

一旦您定义好了快速键,您就可以进入讯息处理循环,这包含了下列步骤:

  1. 呼叫PeekMessage来检查是否有收到讯息。或者是当应用程序本身不在启动状态下时,呼叫GetMessage来等待讯息发生。
  2. 如果收到了讯息,则利用TranslateAcceleratorTranslateMessage DispatchMessage等函式去读取并加以辨识,并且再予以分派。

让我们分别一一检视这些指令,以确保您了解他们的用法以及他们在过程中需要执行的理由。

PeekMessage
 

PeekMessage函式的宣告内容如下:

BOOL PeekMessage(
LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax,
UINT wRemoveMsg
);

参数

说明

lpMsg

从讯息队列指到讯息结构的指针

hWnd

您需要的窗口讯息的handle

wMsgFilterMin

在您想检查的讯息范围中的第一个讯息值

wMsgFilterMax

在您想检查的讯息范围中的最后一个讯息值

wRemoveMsg

删除旗标:PM_NOREMOVE表示在呼叫PeekMessage后,讯息不 应删除,PM_REMOVE表示应予删除。

PeekMessage是用来从讯息队列中读取一个讯息。唯一需要稍微解释的参数是最后一个wRemoveMsg参数。几乎对所有的应用程序来说,您会用到PM_REMOVE旗标来要求将已处理的讯息删除。这个旗标是用来确定一旦某个讯息已经处理,便会从队列中删除,以便让下一个讯息进来。您很难不去删除已处理的讯息。然而,假想您现在开发了一种程序,就是当程序判断它无法马上处理讯息,但是预期可在短时间内加以处理时,程序不会从队列中删除讯息,而仅是试着在稍候再处理(在数亳秒内)。那么这种程序便不需要用到PM_REMOVE旗标。GetMessage很像是PeekMessage,差别在如果队列中没有任何讯息时,这个函式会等到有讯息来为止。运用这二种函式的理由会在后面的章节中清楚说明。

TranslateAcceleratorTranslateMessage、以及DispatchMessage
 

TranslateAccelerator函式的宣告内容如下:

int TranslateAccelerator(
HWND hWnd,
HACCEL hAccTable,
LPMSG lpMsg
);

参数

说明

hWnd

需要变换讯息的窗口

hAccTable

LoadAccelerators加载的快速键的处理(handle

lpMsg

指到MSG结构的指针,这个结构储存了利用PeekMessage(或 GetMessage)从呼叫执行绪(calling thread)的讯息队列中取得的 讯息。

这个函式会去处理菜单指令的快速键。假如有一个加速键对应到某个应发生的动作时,TranslateAccelerator会将WM_KEYDOWNWM_SYSKEYDOWN的讯息转成一个WM_COMMANDWM_SYSCOMMAND讯息。TranslateAccelerator会把转换的讯息传递给适当的窗口程序。

TranslateMessage函式的宣告内容如下:

BOOL TranslateMessage(
CONST MSG * lpMsg
);

这个函式是将虚拟键讯息转成字符讯息。这些字符讯息便可以传给呼叫执行绪(thread)的讯息列,当这些执行绪下次呼叫GetMessagePeekMessage时便可以读取。如果讯息经过转换(所以字符讯息就被放在执行绪的讯息队列中),函式的传回值就不会是0;如果未经转换,传回值就是0。如果讯息是WM_KEYDOWNWM_KEYUPWM_SYSKEYDOWN或者WM_SYSKEYUP,则不论已转换与否传回值都会是0。当TranslateAccelerator传回非零值时,表示透过lpMsg参数传给它的讯息已经处理,您的程序便不需要用TranslateMessage再处理一次。

DispatchMessage函式只有一个单一参数- lpMsg,这是一个指到MSG结构的指针,负责记住从呼叫执行绪的讯息队列中取得的讯息。

LONG DispatchMessage(
CONST MSG *lpMsg
);

这个函式会把转换后的讯息分派给窗口子程序。这个函式的传回值就是窗口子程序的传回值。

建立应用程序的窗口程序
 

在建立WNDCLASS结构时,您会在结构中的lpfnWndProc成员里指定一个指到窗口程序的指针。这个程序在我们的应用程序中称为WndProc Microsoft文件通称为WindowProc),是一个应用程序定义的回传函式,负责处理送给窗口的讯息。WindowProc函式宣告内容如下:

LRESULT CALLBACK WindowProc(
HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam
);

参数

说明

hWnd

窗口的处理

uMsg

讯息识别子

wParam

第一个讯息参数

lParam

第二个讯息参数

D3dapp.cpp 中有一个简短的WndProc函式。它会呼叫g_pD3DAPP->MsgProc来处理讯息:

//-------------------------------------------------------------------
//名称:WndProc
//说明:将讯息传递给程序类别的静态讯息处理程序
//
//-------------------------------------------------------------------
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
        if(g_pD3DApp)
               return g_pD3DApp->MsgProc(hWnd,uMsg,wParam,lParam);
               return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

因为g_pD3DAppCD3DApplication的型态,您可能会认为这段程序代码会去呼叫CD3DApplication::MsgProc。但CMyD3DApplication已经覆写了MsgProc虚拟函式,所以被呼叫的其实是CMyD3DApplication::MsgProc (在roadrage.cpp中)。CMyD3DApplication::MsgProc会先处理一些对RoadRage来说比较特别的讯息,并将其它的讯息传给CD3DApplication::MsgProc,让它来处理那些对有用到Direct3D程序框架的程序来说比较平常的讯息。以下是CMyD3DApplication::MsgProc的内容:

LRESULT CMyD3DApplication::MsgProc(HWND hWnd,UINT uMsg, WPARAM wParam,LPARAM lParam)
{
        HMENU hMenu;
        m_hWnd =hWnd;
        hMenu =GetMenu(hWnd);
        switch(uMsg)
{
               case WM_COMMAND:
                         switch(LOWORD(wParam))
               {
                       case MENU_ABOUT:
                               DialogBox(hInstApp,MAKEINTRESOURCE(IDD_ABOUT),                                                   
                               hWnd,(DLGPROC)AppAbout);
                               break;
                       case IDM_EXIT:
                               SendMessage(hWnd,WM_CLOSE,0,0);
                               DestroyWindow(hWnd);
                               PostQuitMessage(0);
                               exit(0);
                       default:
                               return CD3DApplication::MsgProc(hWnd,uMsg,                                                                                          
                               wParam,lParam);
               }
               break;
        case WM_GETMINMAXINFO:
               ((MINMAXINFO*)lParam)->ptMinTrackSize.x =100;
               ((MINMAXINFO*)lParam)->ptMinTrackSize.y =100;
               break;
        case WM_CLOSE:
               DestroyWindow(hWnd);
               PostQuitMessage(0);
               return 0;
        default:
                return CD3DApplication::MsgProc(hWnd,uMsg,wParam,lParam);
        }
        return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

CD3DApplication::MsgProc方法的定义如下:

//-------------------------------------------------------------------
//名称:MsgProc
//说明:讯息处理函式
//-------------------------------------------------------------------
LRESULT CD3DApplication::MsgProc(HWND hWnd,UINT uMsg,WPARAM wParam, LPARAM lParam)
{
        HRESULT hr;
        switch(uMsg)
        {
               case WM_PAINT:
               //当程序尚未就绪时,负责处理画图讯息
               //
               break;
        case WM_MOVE:
               //如果是在窗口模式下,移动Direct3D程序框架的
               //窗口
               break;
        case WM_SIZE:
               //检查有没有失去窗口
               if(SIZE_MAXHIDE==wParam ||SIZE_MINIMIZED==wParam)
                       m_bActive =FALSE;
               else
                       m_bActive =TRUE;
               //新的窗口尺寸需要占用一个新的后缓冲区大小
               //因此您必须适当地改变3D结构
               break;
        case WM_SETCURSOR:
               //避免在全屏幕模式下出现光标
               break;
        case WM_ENTERMENULOOP:
               //菜单显示时要将程序暂停
               Pause(TRUE);
               break;
        case WM_EXITMENULOOP:
               Pause(FALSE);
               break;
        case WM_ENTERSIZEMOVE:
               //当应用程序正在调整大小或移动时,停止画面移动。
               //
               if(m_bFrameMoving)
                       m_dwStopTime =timeGetTime();
               break;
        case WM_EXITSIZEMOVE:
               if(m_bFrameMoving)
               m_dwBaseTime +=timeGetTime()-m_dwStopTime;
               break;
        case WM_CONTEXTMENU:
               //处理程序的关联性菜单 (透过按一下鼠标右键的方式)
               //
               TrackPopupMenuEx(
               GetSubMenu(
                               LoadMenu(0,MAKEINTRESOURCE(IDR_POPUP)),
                               0 ),
                               TPM_VERTICAL,LOWORD(lParam),HIWORD(lParam),
                               hWnd,NULL);
               break;
        case WM_NCHITTEST:
               //避免使用者在全屏幕模式下选择
               //菜单
        break;
case WM_POWERBROADCAST:
         switch(wParam)
        {
        case PBT_APMQUERYSUSPEND:
               //在这个地方,程序应该储存所有数据
               //以开启网络连接,档案等,并且
               //准备进入暂停模式。
               return OnQuerySuspend((DWORD)lParam);
        case PBT_APMRESUMESUSPEND:
               //在这个地方,程序应该回复所有数据
               //,网络连接,档案等,并且
               //从程序暂停点恢复执行。
               //
               return OnResumeSuspend((DWORD)lParam);
        }
        break;
case WM_SYSCOMMAND:
        //避免全屏幕模式下移动或调整大小
        //及电源丧失
        switch(wParam )
        {
               case SC_MOVE:
               case SC_SIZE:
               case SC_MAXIMIZE:
case SC_MONITORPOWER:
        //如果不在窗口模式下时,传回1
        break;
        }
        break;
case WM_COMMAND:
        switch(LOWORD(wParam))
        {
               case IDM_TOGGLESTART:
                       //切换画面移动
                       break;
               case IDM_SINGLESTEP:
                       //单步骤画面移动
                       break;
               case IDM_CHANGEDEVICE:
                       //显示选择装置的对话盒
                       return 0;
               case IDM_TOGGLEFULLSCREEN:
                       //在全屏幕和窗口模式下切换
                       return 0;
               case IDM_ABOUT:
                       //显示「关于」对话盒
                       Pause(TRUE);
                       DialogBox((HINSTANCE)GetWindowLong(hWnd,GWL_HINSTANCE),
                                      MAKEINTRESOURCE(IDD_ABOUT),hWnd,AboutProc);
                       Pause(FALSE);
                       return 0;
        case IDM_EXIT:
               //接收按键/菜单指令,以终止程序。
               SendMessage(hWnd,WM_CLOSE,0,0);
                       return 0;
               }
               break;
        case WM_GETMINMAXINFO:
               ((MINMAXINFO*)lParam)->ptMinTrackSize.x =100;
               ((MINMAXINFO*)lParam)->ptMinTrackSize.y =100;                                       break;

  
  
   
    
  
  
        //关闭窗口。
        case WM_CLOSE:
               DestroyWindow(hWnd);
               return 0;
        //当窗口从屏幕上删除之后,会将WM_DESTROY讯息
        //送到被结束的窗口的窗口子程序
        //
        //这个讯息会先送到被结束的窗口去,再
        //送到(如果有)子窗口去,将其视为已终止。
        //因此您可以假设当讯息还在处理过程中时,
        //所有的子窗口仍然存在。
        case WM_DESTROY:
               //在此清除3D环境数据PostQuitMessage(0);
               return 0;
}
//
//DefWindowProc函式会呼叫预设的窗口子程序,为
//程序不处理的任何窗口讯息提供预设的处理方式。
//这个函式是为了确保所有讯息都已被处理。
//呼叫DefWindowProc的参数和窗口子程序接收到
//的是相同的参数。
return DefWindowProc(hWnd,uMsg,wParam,lParam);
}

CD3DApplication::MsgProc的相关参数描述了窗口(hWnd),接收的讯息(uMsg),以及和讯息有关的其它参数(wParamlParam)。以下是一些MsgProc方法会去处理的讯息:

  •  WM_SIZE 一旦窗口大小改变时会送出。
     
  •  WM_MOVE 窗口移动位置时会送出。
     
  •  WM_SETCURSOR 当鼠标造成光标在窗口中里移动且无法捕捉到鼠标输入时会送出。
     
  •  WM_PAINT Windows或其它的程序请求重新绘制窗口的部分时会送出。当UpdateWindowRedrawWindow函式被呼叫时也会送出。
     
  •  WM_CLOSE 当使用者关闭应用程序的窗口时会送出。WndProc会去呼叫DestroyWindow函式来处理本讯息。
     
  •  WM_ENTERMENULOOPWM_EXITMENULOOP 当进入或离开菜单架构循环时会发生(当使用者将指针移出或移入菜单时)。
     
  •  WM_ENTERSIZEMOVEWM_EXITSIZEMOVE 当应用程序正在改变大小或移动,使用者需要暂停3D世界对象的运动时会发生。
     
  •  WM_COMMAND 当使用者选了一项菜单,或是当某个控件送给它的父窗口一个讯息,或者当使用者的快速键被转换时都会发生。会用到巢状switch叙述来检查每一个功能项目。目前来说,只能处理「离开」及「关于」的菜单选项。当使用者点选「关于」功能项目时,就会看到如图2-4的画面。当使用者点选「离开」选项时,就会传送给窗口一个WM_CLOSE讯息来予以关闭。
     

CD3DApplicationMsgProc方法中的最后一个步骤是对程序未处理的任何讯息执行预设的处理动作。MsgProc会呼叫DefWindowProc(依据MsgProc收到的相同参数)来作这个预设动作。


 

2-4  About对话盒

DestroyWindow
 

最后一个步骤是在程序完成后终止窗口,这通常发生在使用者从菜单点选「跳离」时。要终止窗口时,会去呼叫DestroyWindow指令。这个指令只有一个参数- hWnd,指向将要终止的窗口。DestroyWindow命令的定义如下:

BOOL DestroyWindow(
HWND hWnd
);

DestroyWindow的作用就如同它的名称一样:终止您指定的窗口。它也会送出一个WM_DESTROY讯息到窗口子程序中,让您的应用程序可以同时终止与该窗口相关的资源。

到目前为止的程序代码
 

我们已经用具备了DirectDrawDirect3D功能的窗口为例,让使用者知道了应该知道的原始程序。如果您已安装了范例程序而且想要测试到目前为止的程序,请开启RoadRage系统空间并开启Chap2项目为作用中的项目。执行程序,即可看到如图2-5的窗口画面。


 

2-5 本章程式的画面

因为我们还没定义任何内容,这个屏幕是一片空白。在后面的章节里,当我们更深入DirectDrawDirect3D的程序后,就可以用有趣的3D内容来填满它!

结论
 

在本章中,您学到了要建立一个DirectX应用程序时的基本Windows程序代码。这个程序代码要能够处理任何您需要关注的讯息。如果您愿意,您可以挑一本专门探讨这个主题的书来深入了解Windows程序开发,例如Charles Petzold所写的《Programming Windows》(5th ed.Microsoft Press1999)和Jeffrey Richter所写的《Programming Applications for Microsoft Windows》(4th ed.Microsoft Press1999)。不过您现在应该只需要把焦点放在3D应用程序的程序上即可。在第三章,我们会去说明当要建立一个Direct3D的应用程序时您必须要了解DirectX的第一个重点:DirectDraw 第一章 曾说过,DirectDraw是用来控制一些基础作业,例如处理主要和辅助显示、切换页等等。 在第三章 ,我们会浏览所有在您的程序中要用到DirectDraw时的必要程序代码。如此您将可以开始建立属于您自己的3D景物,并检视和设定动作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值