概述
//*******************************************************************
// 工程:hello
// 文件:hello.cpp
// 内容:创建第一个DirectDraw应用程序,
//*******************************************************************
#include <windows.h>
#include <windowsx.h>
#include <ddraw.h>
LPDIRECTDRAW lpDD; // DirectDraw对象
LPDIRECTDRAWSURFACE lpDDSPrimary; // DirectDraw主页面
char szMsg1[] = "Hello World, I am DirectDraw boy !";
char szMsg2[] = "按 ESC 退出";
//函数声明
LRESULT CALLBACK WinProc( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam );
BOOL InitWindow( HINSTANCE hInstance, int nCmdShow );
BOOL InitDDraw( void );
void FreeDDraw( void );
//*******************************************************************
//函数:WinMain()
//功能:Win32应用程序入口函数。进行初始化工作,处理消息循环
//*******************************************************************
int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MSG msg;
//初始化主窗口
if ( !InitWindow( hInstance, nCmdShow ) )
return FALSE;
//初始化DirectDraw环境,并实现DirectDraw功能
if ( !InitDDraw())
{
MessageBox(GetActiveWindow(), "初始化DirectDraw过程中出错!", "Error", MB_OK );
FreeDDraw();
DestroyWindow(GetActiveWindow());
return FALSE;
}
//进入消息循环
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
//******************************************************************
//函数:InitWindow()
//功能:创建主窗口。
//******************************************************************
static BOOL InitWindow( HINSTANCE hInstance, int nCmdShow )
{
HWND hwnd; //窗口句柄
WNDCLASS wc; //窗口类结构
//填充窗口类结构
wc.style = 0;
wc.lpfnWndProc = WinProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );
wc.hCursor = LoadCursor( NULL, IDC_ARROW );
wc.hbrBackground = GetStockObject(BLACK_BRUSH);
wc.lpszMenuName = NULL;
wc.lpszClassName = "dxHello";
//注册窗口类
RegisterClass( &wc );
//创建主窗口
hwnd = CreateWindowEx(
0,
"dxHello",
"",
WS_POPUP,
0, 0,
GetSystemMetrics( SM_CXSCREEN ),
GetSystemMetrics( SM_CYSCREEN ),
NULL,
NULL,
hInstance,
NULL );
if( !hwnd ) return FALSE;
//显示并更新窗口
ShowWindow( hwnd, nCmdShow );
UpdateWindow( hwnd );
return TRUE;
}
//******************************************************************
//函数:WinProc()
//功能:处理主窗口消息
//******************************************************************
LRESULT CALLBACK WinProc( HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam )
{
switch( message )
{
case WM_KEYDOWN://击键消息
switch( wParam )
{
case VK_ESCAPE:
PostMessage(hWnd, WM_CLOSE, 0, 0);
break;
}
break;
case WM_DESTROY://退出消息
FreeDDraw();
PostQuitMessage( 0 );
break;
}
//调用缺省消息处理过程
return DefWindowProc(hWnd, message, wParam, lParam);
}
//******************************************************************
//函数:InitDDraw()
//功能:初始化DirectDraw环境并实现其功能。包括:创建DirectDraw对象,
// 设置显示模式,创建主页面,输出文字。
//******************************************************************
BOOL InitDDraw(void)
{
DDSURFACEDESC ddsd; //页面描述
HDC hdc; //设备环境句柄
//创建DirectCraw对象
if ( DirectDrawCreate( NULL, &lpDD, NULL ) != DD_OK ) return FALSE;
// 取得独占和全屏模式
if ( lpDD->SetCooperativeLevel( GetActiveWindow(),
DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN ) != DD_OK)
return FALSE;
//设置显示模式
if ( lpDD->SetDisplayMode( 640, 480, 8 ) != DD_OK) return FALSE;
//填充主页面信息
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS;
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
//创建主页面对象
if ( lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL ) != DD_OK)
return FALSE;
//输出文字
if ( lpDDSPrimary->GetDC(&hdc) != DD_OK) return FALSE;
SetBkColor( hdc, RGB( 0, 0, 255 ) );
SetTextColor( hdc, RGB( 255, 255, 0 ) );
TextOut( hdc, 220, 200, szMsg1, lstrlen(szMsg1));
TextOut( hdc, 280, 240, szMsg2, lstrlen(szMsg2));
lpDDSPrimary->ReleaseDC(hdc);
return TRUE;
}
//******************************************************************
//函数:FreeDDraw()
//功能:释放所有的DirectDraw对象。
//******************************************************************
void FreeDDraw( void )
{
if( lpDD != NULL )
{
if( lpDDSPrimary != NULL )
{
lpDDSPrimary->Release();
lpDDSPrimary = NULL;
}
lpDD->Release();
lpDD = NULL;
}
}
1. 为了简化代码,这第一个入门程序没有头文件。在进行编译之前,还得进行最后的设置。选择Project菜单的Settings…,出现工程设置对话框。选择Link栏,在Object/Library modules中添入“Ddraw.lib”。使对话框如图所示。
pichello3.gif
· 请注意:这一步骤是将DirectDraw的静态连接库文件连接到工程中,否则,程序虽然可以正常编译,但是在连接时会产生一个“unresolved external symbol”(没有定义的外部符号)的错误。在以后所有的DirectDraw程序中,都必须将与你所用到的DirectDraw组件相应的静态连接库添入到这个设置中。
至此,一个最基本的DirectDraw应用程序已创建完毕,你现在不必去深究这些代码的含义,在下面及以后的章节中我们会对它们进行详细的分析。这虽不是一个最简单的DirectDraw应用程序,但它确实是一个能够实现最基本的输出功能的DirectDraw程序。
按F7编译成功后,按Ctrl+F5,执行该程序,显示器将切换到640*480*256色模式,黑屏后,屏幕中央会打印出蓝底黄字“Hello World, I am DirectDraw boy !”,除了输出这些字符外,这个程序什么也不做。按ESC可退出程序。程序运行结果如下图
pichello4.GIF
这就是DirectDraw?有的人也许会对DirectDraw感到很失望,因为它并没有为我们表现出神奇的功能啊?但有的人却会对此感到异常兴奋,他们觉得一扇充满诱惑的房间的大门正向他们打开。这就象透过天窗,有些人只会看到黑暗的夜空,有些人却能看见满天的星星一样。第一个例子,为了使程序不至于太长而让那些初学者望而生畏,所以只能一再简化(尽管这样,整个程序还是占用了相当大的篇幅)。在后续章节的例子中,你们会看到程序会一个比一个更精彩。
3、分析代码
回到目录
下面,让我们来逐一分析一下这个程序。
1)程序结构
回到目录
分析程序应该是一个由外而内,逐步求精的过程。首先从大的方面来看,这个程序一共用到了五个函数,如果按照正常顺序,排除程序中出错的可能,它们的调用顺序依次是这样的。
WinMain ----> InitWindow ----> InitDDraw ----> WinProc ----> FreeDDraw
WinMain:所有Win32应用程序的入口函数,它也是应用程序关闭时的出口,一个应用程序的全生命周期就是在它的控制之下。所以,确切的说,其它四个函数是被包括在WinMain之内的。消息循环也是在这个函数中启动。
InitWindow:初始化和创建一个与程序的HINSTANCE(实例句柄)相关联的主窗口,这个窗口的HWND(窗口句柄)在初始化DirectDraw环境时需要用到。
InitDDraw:初始化和创建DirectDraw对象,并执行一定的功能。它里面包括了创建DirectDraw对象,创建页面,设置显示模式,创建主页面,输出文字。
WinProc:是应用程序感知外来动作和产生反应的神经中枢,相当于人的大脑。这是程序中最主要的部件之一,它和在WinMain中所启动的消息循环是一起工作的。
FreeDDraw:释放DirectDraw的各种对象,以使其不再占用内存空间。
上面这段话如果使你感到迷惑,就象是在你小学的时候有人给你讲什么是微积分,那么你仍需要事先预习一下Win32编程。请跳转到本教程的“Win32编程基础知识”一章,复习一下Win32编程的基础知识。
如果你对Win32编程和Windows的消息机制有一定的了解的话,以上概念是比较容易理解的。
2)定义和创建DirectDraw对象
回到目录
分析完程序的总结构,再让我们从最开头看起。
#include <ddraw.h>
这是把DirectDraw的头文件包含到文件里来。这一步在以后所有的例程中都是必不可少的。
LPDIRECTDRAW lpDD; // DirectDraw 对象
LPDIRECTDRAWSURFACE lpDDSPrimary; // DirectDraw 主页面
接着定义了三个全局指针变量,它们都是指向对象的指针。第一个是DirectDraw对象,表示显示硬件,它包括了显示器和显卡还有显存,用它来代表整个显示系统。第二个是DirectDrawSurface对象,表示页面,你可以在大脑中把它想象成一张矩形的白纸,你可以在上面绘制图象。现在你暂且不用去深究它们的含义,看下去就是了,在后面的课程中,我还会更加详细的介绍。
LPDIRECTDRAW和LPDIRECTDRAWSURFACE是在ddraw.h头文件里预定义的指向DirecctDraw和DirectDrawSurface对象的长型指针,所以前面加了LP,代表Long Point。因为这些DirectDraw对象指针变量经常用到,所以给它起名为lpDD和lpDDSPrimary,DD是DirectDraw的缩写。
· 给一个变量命名时,在变量名前加上该变量的类型标志,在Windows编程中是一个默认约定,称为匈牙利表示法,该名称来源于微软的一个匈牙利籍资深程序员,因为他惯用此表示法,后来便成了规范。如lpDD表示一个长型指针变量,dwHeight代表一个DWORD型变量。这样的好处就在于你可以一眼就辨认出某变量的类型,而不用去追溯它的定义。
这里所说的“对象”(Object),并不完全等同于C++中对象的概念,尽管它们使用的是同一个英文单词Object。这里的对象指的是COM,COM是Component Object Model 的缩写,代表“部件对象模型”,它在DirectX中贯穿始终,无处不是它的身影。
· 在DirectX SDK中,大多数APIs(应用程序编程接口)由对象和基于COM的接口组成。COM是致力于可重复利用接口资源的面向对象系统的基础,并且是OLE编程的核心模型。它也是一个接口规范,通过它可以设计出许多接口。它是建立在操作系统层次的对象模型。
COM与C++类也有许多相同之处。对一个C++程序员来说,COM接口就象是一个抽象基础类。这就是说,它定义了一套关键符号(signatures)和语法(semantics),但不是执行语句,并且没有与接口相关联的状态数据。在C++抽象类中,所有的方法被定义成纯虚(pure virtual)函数,它们并没有实际的代码。在这一点上,COM和基础类是一致的。
COM对象与C++对象的另一个相似点是:一个函数的第一个引用是接口或类的名称,在C++中叫做this引用。因为COM对象和C++对象是完全二进制兼容的,编译器把COM接口当作C++抽象类来看,而且采取同样的语法。这样就可以减少代码的复杂程度。例如,this引用在C++中被当作可识元素,并且被暗中的操作,COM中也是如此。
· COM 是DirectX的基础,虽然它和C++中的类不近相同,但你完全可以把它当成C++的类来看待,在实际编程中,它们的语法和接口也是完全一样的。所以,对于C++程序员,进行DirectX编程并不需要你去学习新的编程方法,继续沿用你所熟知的C++,DirectX也能为你所用。
只要是你要用到DirectDraw接口的特性,都必须创建一个DirectDraw对象,它是DirectDraw接口的核心。它是这样被创建的:
if ( DirectDrawCreate( NULL, &lpDD, NULL ) != DD_OK ) return FALSE;
DirectDrawCreate()函数是在ddraw.h中定义的,关于这个函数的详细解释,请参看本教程的“DirectDraw参考手册”一章,它的原型如下:
HRESULT DirectDrawCreate(GUID FAR * lpGUID, LPDIRECTDRAW FAR * lplpDD, IUnknown FAR * pUnkOuter);
第一个参数是lpGUID:指向DirectDraw接口的全局唯一标志符(GUID:Global unique identify)的指针。在这里,我们给它NULL,表示我们将使用当前的DirectDraw接口。
第二个参数是lplpDD:这个参数是用来接受初始化成功的DirectDraw对象的地址。在这里,我们给它&lpdd。
第三个参数是pUnkOuter:千万不要追问这个参数是干嘛使的,如果你不想惹麻烦,就给它NULL吧。Microsoft的说明书上是这么写的“考虑到与将来的COM集合特性保持兼容,当前,不管怎样,如果这个参数不是NULL ,DirectDrawCreate将返回一个错误”。
所有的DirectDraw函数的返回值都是HRESULT类型,它是一个4字节(32位)的值,用来代表某个错误或警告。DirectDraw头文件中已经预定义了所有可能的返回值常量,仅函数返回成功的值是用 “DD_OK”表示,所有的错误值标志开头都为“DDERR”,如:
DDERR_DIRECTDRAWALREADYCREATED
DDERR_GENERIC
DDERR_OUTOFMEMORY
· Windows编程中,有一项让中国软件开发者大挠其头的就是函数名、常量名和数据结构名称中往往有长串的字符。这时,就要考验读者的英文断句水平了。如:DDERR_OVERLAYCOLORKEYONLYONEACTIVE,应该断为:DDERR-OVERLAY-COLORKEY-ONLY-ONE-ACTIVE。这对于老外来说也许并不成什么问题,但对于我们,有时候要看懂一个语句,简直就象在做一道英文题。除非你对一个名称有十足的把握,否则就应该尽量使用CP规则(原是离散数学里的一条规则,现引申为Copy-Paste,即:复制-粘贴)。而且,给读者一点忠告,在起函数、变量或常量名称时,也应该沿用老外的这条原则,以尽量清楚的表达意义为宗旨,不要担心它是否太长(当然,对于那些重复利用率很高的函数、变量或常量,还应该尽量使用缩写),否则,总是以i、n为变量,程序的可读性会变得极差,而在编程的初学者当中,这是很常见的。
3)设置控制程度和显示模式
回到目录
DirectDrawCreate函数调用成功后,lpDD已经指向了一个DirectDraw对象,它是整个DirectDraw接口的最高层领导,以后的步骤都是在它的控制之下。
if ( lpDD->SetCooperativeLevel( GetActiveWindow(),
DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN ) != DD_OK)
return FALSE;
这个语句用来设置应用程序对操作系统的控制程度。它的原型如下:
HRESULT SetCooperativeLevel( HWND hWnd, DWORD dwFlags )
第一个参数是hWnd,我们调用Win32的API函数GetActiveWindow获得应用程序主窗口的句柄,这将使DirectDraw对象与主窗口的消息挂上勾。
第二个参数是dwFlags,控制级的标志符。我们给它DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN,表示我们期望DirectDraw以独占和全屏方式工作。
这个函数有很多用法,而且它必须在创建DirectDraw对象之后,立即调用。你可以用它来设置应用程序是运行于全屏还是窗口模式,是独占还是共享模式。具体用法将在以后逐步介绍。可参阅“DirectDraw参考手册”。
if ( lpDD->SetDisplayMode( 640, 480, 8 ) != DD_OK) return FALSE;
显而易见,这是设置显示器的显示模式,它把显示模式设为640*480,8位色彩模式(即256色)。这是绝大多数显示器所能够支持的显示模式,所以我们不用担心它会出什么问题。在以后的例程中,我们将看到可以调用EnumDisplayModes()来列举出显示器所支持的所有显示模式。绝不要轻易尝试直接设置一个新的显示模式,而应从列举出的显示模式中选择,这也将在后续章节讲到。
· 要注意的是,只有当DirectDraw对象为独占访问的控制程度时才能改变显示器的显示模式,如果DirectDraw对象运行为窗口模式,调用该函数会返回一个错误。
4)创建主页面
回到目录
然后的任务是要创建一个DirectDrawSurface对象。
DirectDrawSurface对象代表了一个页面。页面可以有很多种表现形式,它既可以是可见的(屏幕的一部分或全部),称之为主页面(Primary Surface);也可以是作换页用的不可见页面,称之后台缓存(Back Buffer),在换页后,它成为可见;还有一种始终不可见的,称之为离屏页面(Off-screen Surface),用它来存储图象。其中,最重要的页面是主页面,每个DirectDraw应用程序都必须创建至少一个主页面,用它来代表屏幕上可见的区域,说白了,就是你的显示屏幕。
创建一个页面要分两步走,这里,我们以创建主页面为例,简要介绍一下这两个步骤,其它类型页面的创建也与之类似,在以后的例程中还会着重讲解。
1. 在调用CreateSurface()函数创建一个页面之前,首先需要填充一个DDSURFACEDESC的结构,它是DirectDraw Surface Description的缩写,意思是DirectDraw的页面描述。该结构的详细资料请参看本教程“DirectDraw参考手册”一章。
//填充主页面信息
ddsd.dwSize = sizeof( ddsd ); //结构的大小
ddsd.dwFlags = DDSD_CAPS; //指定DDSURFACEDESC结构的ddsCaps成员为可用
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE; //指定要创建的是主页面
· 这就象是你到银行取款,必须事先填写一张取款单,你要在上面详细描述你的姓名,你的存折序号,以及你要取的钱数等等,然后把它递给银行工作人员。
1. 页面描述填充完毕后,把它传递给CreateSurface()函数即可。
//创建主页面对象
if ( lpDD->CreateSurface( &ddsd, &lpDDSPrimary, NULL ) != DD_OK)
return FALSE;
CreateSurface()函数的第一个参数是被填充了页面信息的DDSURFACEDESC结构的地址,为&ddsd;第二个参数是接收主页面指针的地址,此处为&lpDDSPrimary;第三个参数现在必须为NULL,为该函数所保留。
如果函数调用成功,lpDDSPrimary将代表一个合法的主页面对象。由于在前面已经设置了该程序的工作模式为独占和全屏,所以,此时主页面所代表的实际上是你的整个显示屏幕。在主页面上所绘制的图形将立即反映到你的显示屏幕上。
5)输出文字
回到目录
下面开始在主页面上输出文字。
//输出文字
if ( lpDDSPrimary->GetDC(&hdc) != DD_OK) return FALSE; //获得设备环境句柄
SetBkColor( hdc, RGB( 0, 0, 255 ) ); //设置背景颜色
SetTextColor( hdc, RGB( 255, 255, 0 ) ); //设置文字颜色
TextOut( hdc, 220, 200, szMsg1, lstrlen(szMsg1)); //输出文字
TextOut( hdc, 280, 240, szMsg2, lstrlen(szMsg2));
lpDDSPrimary->ReleaseDC(hdc); //释放资源
如果你十分熟悉Windows的GDI(图形设备接口),你会发现上面这段程序居然和在窗口中输出文字一模一样。这是因为,DirectDraw页面和GDI的设备环境(DC)其实是兼容的。在调用主页面的GetDC()函数获得页面的设备环境句柄(HDC)之后,就可以使用Win32的API绘图函数来进行绘图操作了。同样,最后也必须调用主页面的ReleaseDC()函数来释放设备环境资源。
6)释放对象
回到目录
在程序结束之前,DirectDraw还必须做一项扫尾工作,即:把已经创建的所有DirectDraw对象从内存中清除出去。这就是FreeDDraw()函数的作用。
void FreeDDraw( void )
{
if( lpDD != NULL ) //判断DirectDraw对象是否为空
{
if( lpDDSPrimary != NULL ) //判断主页面对象是否为空
{
lpDDSPrimary->Release(); //释放
lpDDSPrimary = NULL;
}
lpDD->Release(); //释放
lpDD = NULL;
}
}
每一个DirectDraw接口的对象都有Release()函数,以将其所引用的对象释放。这其实相当于C++中的delete方法。及时的将不用的对象释放掉是每一个优秀的程序员都应当养成的良好习惯。
6)主窗口的类型
回到目录
以上关于DirectDraw接口的编程似乎与应用程序的主窗口没有一点联系,而且执行后,也看不到窗口的影子,是不是就可以不用创建程序的主窗口了呢?当然不是。
其实,程序执行后,漆黑的背景就是程序的主窗口。在注册窗口类时,已经给hbrBackground成员指定了黑色。
wc.hbrBackground = GetStockObject(BLACK_BRUSH);
而且,在创建主窗口时,窗口的类型用的是WS_POPUP,表明创建的是一个没有标题栏,没有边框的窗口;而不是常用的WS_OVERLAPPEDWINDOW。
hwnd = CreateWindowEx(
……
WS_POPUP,
……
值得特别注意的是:在设置DirectDraw对象控制级的时候,使用了主窗口的句柄(HWND)。这就是说,DirectDraw将用主窗口来接收各种消息,这样,就可以利用主窗口来实现DirectDraw对用户操作的反馈。如,用户想用光标键来控制游戏图象的运动,光标键按下的消息被发往主窗口,然后在主窗口的消息处理过程中操作DirectDraw,使之对光标键产生反应。所以,在大多数情况下,创建一个主窗口是必不可少的。
if ( lpDD->SetCooperativeLevel( GetActiveWindow() /*主窗口句柄*/ ,
DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN ) != DD_OK)
return FALSE;
4、小结
回到目录
DirectX是一个功能强大而且使用复杂的工具,在这一章里,我们仅学习了一个最简单的利用DirectDraw接口的例子,对于庞大的DirectX来说,它只是冰山一角。要掌握DirectDraw编程是一个复杂的过程,需要从大量的实践中摸索出经验,任何读者想一蹴而就是不可能的。但是,好的开端意味着成功的一半,如果你能理解和掌握本章所学到的内容,继续下去,坚持不泄,你会发现你将一天比一天更加充实。
通过本章的学习,读者应能掌握:
· 在VC++5.0环境中使用Win32应用程序开发环境新建一个DirectDraw工程以及添加源文件。
· 用DirectDrawCreate()函数创建DirectDraw对象,并立即调用SetCooperativeLevel()设置其控制级,注意其第一个参数为应用程序主窗口句柄。
· 只有在独占的控制级下才能调用SetDisplayMode()以改变显示器的显示模式。
· 在调用CreateSurface ()创建DirectDrawSurface对象之前,必须先填充一个DDSURFACEDESC结构以描述页面的信息。
· 页面对象的种类,只有主页面代表了显示屏幕,为可见页面。
· 用GetDC()获得主页面的HDC,然后可以使用Windows API的GDI函数进行绘图或输出文字,最后必须调用ReleaseDC()以释放资源。
· DirectDraw接口的对象必须被及时的用Release()函数释放。
最后
以上就是糊涂灰狼为你收集整理的ddraw入门实例的全部内容,希望文章能够帮你解决ddraw入门实例所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复