博文

GL的简单封装类与游戏编程基础(2007-05-17 23:25:00)

摘要:用MFC习惯了,所以想把GL的一些东西封装成类,放在MFC里一起搭应用程序。 GL当初是被设计成与硬件及平台无关的专业3D绘图API,也就是说用GL的时候,你可以把你正在用计算机这件事忘了,你是在进行艺术创作! 虽然MS不太情愿支持GL,但从WIN NT开始也正式把GL加入了WIN32 API中,在一组dll中实现,所以使用起来也还方便。 WINDOWS下GL实现的基本原理(其它平台的不知道),因为有GDI/GDI+,所以GL在这里显得有些像异类,在GDI下画图是使用的图形设备上下文(描述表)DC,而在GL里是渲染描述表RC,所有的gl*绘画函数都是在一个当前RC上进行的,绘制完之后用WIN API SwapBuffers(HDC)交换缓存到DC,进行图形的显示。 class CGLFrame;可以看成是对RC的封装,所以可以认为它就是GL本身。 它的构造函数不做任何事情,将产生一个对象,未做初始化。初始化要在CreateRC()中完成。 BOOL CGLFrame::CreateRC(CDC *pdc, CRect &rect, BOOL bMWA); pdc : 一个CDC在堆上的对象,可以是CClientDC等,在析构函数中有delete pdc的操作。 rect : 是初始的显示宽度 bMWA : 标识是否在多窗口应用下绘画,默认为FALSE。 从这之后,将由CGLFrame对象托管RC资源,及进行绘图操作(绘图还是用gl*函数,只是放在特定的地方) 举个例子,在基本对话框应用程序下使用CGLFrame 1,包含必要的头文件和库文件(或者直接把glframe.h和glframe.cpp添加到工程) 2,从CGLFrame下继承自己的CMyFrame 3,在CSampleDlg中创建CMyFrame的对象,m_glfrm 4,在CSampleDlg::OnInitDialog()里进行RC的创建:
CClientDC *pdc=new CClientDC(this);
CRect rect;
GetClientRect(&rect);
m_glfrm.CreateRC(pdc,&rect,FALSE); 5,在需要进行渲染的地方调用:m_glfrm.Render();(或者Rend......

阅读全文(5714) | 评论:0

VC实现线程同步(或异步)(2007-04-24 22:07:00)

摘要:操作系统里讲的的进程同步,用的是信号灯,PV操作,P操作看成是申请资源,V操作是看成是交还资源,资源可以有很多解释,比如时间,空间,数据等,而信号量可以看成是资源数目。 在WIN32里多进程用得少,因为进程建立很费劲,分配虚拟内在是其中一个原因,取而代之的是线程,线程可以看成是小进程,是一个进程中活的东西,进程是死的,占有了内存和得到了一些系统资源后就死了,只有启动主线程的时候才活起来,主线程的地位相当重要,主线程一结束进程也就被OS踢出去了。进程间也可以通信,当然要复杂一些,因为地址空间完全不同,用得多的有管道等。 1,互斥对象 一个互斥对象,维护一组数据:当前线程,使用计数,受信状态。PV操作相当于:WaitForSingleObject()和ReleaseMutex(),ReleaseMutex()不一定会将Mutex Release掉,如果计数为1,才会将线程ID改为0,并改受信状态为signed。建立的语句是CreateMutex,释放资源是CloseHandle,进程退出的时候也会自动释放。 2,事件对象 事件对象分两种,一种是人工重置的(ManualReset),影响到WaitForSingleObject()的时候是否由系统设为unsigned,这里有个值得思考的情况: a,When "Non-ManualReset" use
   WaitForSingleObject(event1); b,When "ManualReset" use
  WaitForSingleObject(event1);
  ResetEvent(event1); 他们是不是等价的呢?看起来,如果人工重置的,在WaitForSingleObject()后紧接着就ResetEvent(),那不就是系统自动的了吗? 这是不等价的,原因是,在调用完WaitForSingleObject()后,在进入ResetEvent()之前,是有可能被系统的线程调度程序给设成‘就绪’状态,其它的线程执行,这时候又是signed,所以就出问题了。 3,临界区段 临界区程序就是有线程在处理相同的数据,如果线程都在跑各自不同的数据,那异步是完全没有问题的,如果跑相同的程序,就可能有问题,相当于线程撞在了一起,所以叫临界......

阅读全文(10311) | 评论:0

VC小结(1)(2007-03-16 23:18:00)

摘要:MSDN里面有一些专题值得读一下,至少知道MFC的目的和一些方法或者手段。随便乱写一些,如果以后发现还有价值的话就整理。 1)MFC是对平台SDK的一个大封装。 学了C++,还不知道C++能干嘛,不知道就算能干这些,又有什么用,甚至不知道为什么要设计类,为什么要封装。 WINDOWS里的资源是用句柄表示的,句柄这个词不知道是哪个中国IT菜手翻译的,handle干嘛要这么翻译?它的意义很明显嘛,处理,为什么要处理?有人说,它是微软要隐含内部实现,因为它是商业软件嘛,我觉得不是,这不是根本原因,它原本的目的还是为了代码的向后兼容和可复用性。比如最重要一种资源,文件,它是和具体设备相关的,但文件操作只对一个文件的HANDLE进行处理,就是把它的定义和处理分开,C语言里的FILE也是一样的道理。各种各样的资源统一处理,在系统里统一表示,这样系统就会有条不紊地工作。也算是一个小的封装吧,具体实现和外部代码的分开,封装。于是有了SDK编程。 MFC封装在外面,更大的封装,它是为了让程序员把搭建一个复杂的WIN32程序,从设计那些复杂的实现中解放出来,把开发重点放在软件的数据结构和算法上。由于SDK封装得低,所以可以认为那些handle本身就是资源本身,可以这样认为。再在这之上进行面向类的封装,就是MFC。 有人说懂得了继承,才真正懂得了面向对象,或者C++。C++的目标或许也是这样吧,虽然语言本身没告诉我们它的目的是什么,做为了个成熟的类库,比如MFC,我们不仅仅是简单的利用它的一些功能调用,比如用CCommonDialog,而关键是选择合适的类继承下来,设计自己的子类,来实现整个软件的功能。 在面对各种各样的MFC中的类,要记住一点,类生成的对象,本身并不代表资源本身,比如CEdit并不是EditBox,它们之间可以有联系,但这个联系可以人为的剪断,对象只是一个从操作到资源的一个纽带。这也是为什么它有构造函数,却要用::Create()创建的原因。 2)弄清消息分发的过程。 消息的机制是为了让WINDOWS变迟顿,让它‘慢’下来,只有你‘叫’它做事它才做,由主动工作变为被动工作。有时候发送消息本身也可以看成消息处理本身。MFC对消息的处理封装得可就复杂了,不是非要弄得非常清楚才行,要知道消息被MFC丢来丢去的先后顺序,比如一个按钮的COMMAND响应可以给CW......

阅读全文(5698) | 评论:4

要好好学VC(2007-02-04 23:37:00)

摘要:记得原来听有人说VC是做底层的,像做个写字板程序就费劲,用VB简单之云云,所以我一直以为用VC做东西首先要有心理准备,准备好,累。 现在看来,别听那些,全是些半桶水,可能是某些做其它行业的人,用用VC,全都是瞎子摸象,你说去吧! VC写个写字板程序可以不用写一行代码,单文档应用程序,再用AppWizard把CMyView改成从CEditView类继承,就搞定啦。还有像DDX/DDV啊,感觉真的好用嘛,VB可没做这么好。 也有人说它不是纯C++,说还是有过程化的影子,我觉得MFC封装得挺好,做开发也是全在设计类,哪里不是面向对象,偶尔用一用平台SDK,那叫底层~ VC本来就是又强大又好用。。。......

阅读全文(6706) | 评论:4

Windows程序设计[5]-认识一下GDI(2006-03-30 21:22:00)

摘要:其实我写的这几篇质量确实很低,我完全是按自己理解的写的,因为我也是初学,故显得有些乱。

以前看到论坛里讨论操作系统的贴子,主题是关于用哪个操作系统好,争得比较火爆。有人说WINDOWS是垃圾,有人说LINUX是王者,可能还有的人是DOS迷吧,意见不大统一。我觉得WINDOWS最好的东西也就是这个了GDI,图形设备接口,因为它是图形化直观的操作计算机,所以在PC机进入普通人生活的时候,这样简单直观的操作系统也就为很多人接受了。我觉得WINDOWS甚至可以成为操作系统的标准,或者说个人计算机操作系统的标准,就像网络一样ISO/OSI虽然是国际标准,但是事实上的标准却是TCP/IP,在这个时候,市场决定了标准。

不扯远了,说GDI。这个接口有什么用?它可以让我们轻易的使用图形设备。这里需要先确定一个设备环境(Device Context),简称DC(这个名字你很熟?),它就是一个区域,比如一个窗体,它会占有显示器中间的一个矩形框区域,常用的区域都矩形,因为当区域重叠的时候方便系统计算覆盖情况。

如下部分程序:
case WM_PAINT:
{
  char szText[]="Hello!Windows!";
  HDC hdc;
  PAINTSTRUCT ps;
  hdc=::BeginPaint(hwnd,&ps);
  //在这里画图
  ::TextOut(hdc,10,10,szText,strlen(szText));
  ::EndPaint(hwnd,&ps);
  return 0;
}
显然是在处理WM_PAINT消息,收到这个消息就说明,客户区需要重画,比如我们在调整窗体大小后。hdc是设备环境的句柄,ps是特定的PaintStruct,::BeginPaint()是获得特定窗口句柄的客户区DC句柄,当然hwnd就是该实例的主窗口句柄了。最后的::EndPaint()关闭hdc,在它......

阅读全文(5488) | 评论:1

Windows程序设计[4]-了解MFC(2006-03-30 20:13:00)

摘要:前面了解到了,用API直接搭建WINDOWS程序的过程。GUI程序的入口函数是WinMain(),在入口函数中用函数::CreateWindowEx()创建一个窗口,再注册并更新一个窗口,然后进入消息循环,当从用户或其它线程获得了一个消息,再调用指定的窗口函数,自定义的窗口函数在处理了特定的消息后,再将处理权交给系统默认的消息处理函数::DefWindowProc()。形式上是非常简单的,但事实上用起来并不是这么轻松的,当开发的系统变大时,用这样的方法搭程序,显然是很不够的,这个时候‘方法’会显得更加重要。我一直的观点是,搞理论的应该更注重‘结果’,而搞技术的应该更注重‘方法’。

MFC(Microsoft Fundation Classes)的设计就是出于这样的目的。它的本质就是对API的一个系统的封装,封装的过程是非常复杂的,但封闭的结果是非常实用的,用这样的方法搭建WINDOWS程序将会变得轻而易举。这就是‘方法’对技术的提高的一种表现吧。

总的看MFC,它由CObject从上往下展开(继承),主要的几个类有(这个时候我正拿着MFC体系图在看呢!),CCmdTarget支,它负责与消息处理有关的对象,包括CWinApp,用来搭建应用程序实例;CWnd,封闭了窗口的对象。这是最重要的两个类了,另外的还有CFile文件类,CDC设备环境类等。

使用的时候,用CWinApp派生CMyApp,重载virtual BOOL InitInstance();虚函数,意思是初始化实例。初始化的时候建立自己的窗口对象,从CWnd继承,如CMyWindow。在CMyApp::InitInstance()中定义一个CMyWindow对象作为成员,CMyWindow的生成函数中将设置窗口的特征,使用CreateEx()注册窗口,InitInstance()返回TRUE则表示进入消息循环。

这里还有个消息映射的问题,我们不愿意在一个消息处理函数中用switch(Message),例一长条的case nMsgID:,这样的程序不好看,也不容易阅读,更不好维护,所以我们希望用一个简单的映射。MFC做的相对简单,完全由宏定义做的,消息是系统固定的,消息宏名大致是WM_开头,意为Windo......

阅读全文(5574) | 评论:1

Windows程序设计[3]-消息与消息循环(2006-03-25 14:48:00)

摘要:今天有朋友提的意见让我感触很深,说写BLOG要多一些‘有趣’多一些funny,回头看看我写的东西,大部分都给人严肃、枯燥的感觉,我在这里免费拿了一个BLOG,而这个BLOG却不完全属于我,他属于千千万万在使用网络的学者!所以,我有责任,而这个责任就是不浪费读者的时间。

不说废话了,同样今天继续写的将是Win32程序设计中的Windows消息机制。

消息就是一个用户对电脑的‘请求’。我想放点音乐,我就对电脑大声喊:“Hey!伙计,放点音乐吧!”,结果显然是我将被冷落。我不能这样发‘请求’,我应该用电脑懂的语言,再用‘友好’的方式请求。我只能选择按鼠标或者敲键盘,于是消息便从这里产生了。

消息的原来形式是你手指的‘动能’,结果导致了某些特定电信号,电信号再到达计算机电路系统,系统再将信号反应到操作系统,操作系统再将信号变成消息形式发到线程,到线程的消息再进入消息队列排队,最终到达消息处理函数,此时电脑就明白你的请求是什么意思了,再通过消息处理函数工作。

与其说消息是发到电脑,还不如说是发到了线程,如果没有线程在那里‘听’,你发再多的消息,也不会有反应。

线程要怎么‘听’,还得从GUI入口函数说起。

一个GUI用户程序的入口函数将不是main(),而是WinMain(),原形是:
int APIENTRY WinMain(
   HINSTANCE hInstance,
   HINSTANCE hPrevInstance,
   LPSTR lpCmdLine,
   int nCmdShow)
{
}
APIENTRY是__stdcall的宏,意思是用Windows标准调用方式,hInstance是本身的实例句柄,lpCmdLine是命令行参数。

光一个WinMain()是不会等待消息的(‘听’),我可以加入一个::MessageBox();简单的弹一个窗口出来,然后默默的结束。

问题是我们需要先建一个窗体出来,建主窗体的过程比较复杂......

阅读全文(6146) | 评论:5

Windows程序设计[2]-线程与线程通信(2006-03-20 09:40:00)

摘要:首先要说明一点,我在这里写的关于Win32编程的Windows程序设计系列文章并不是我深思熟虑的小论文,实际上我在写的时候还不了解Win32编程,这是我自己在学习。如果您是初学者禁止观看,如果您是高手欢迎指导!除此之外的其它版的有些文章我是非常认真写的。

Windows程序启动的过程
在Win32环境下有两种模式,GUI和CUI,前者是图形用户接口,后者是控制台用户接口(类似DOS),GUI后面会要深入讨论,如果Windows没这个东西,估计就没人用它了。学过C语言的都知道任何一个程序都要有一个main(){}入口函数,但OS(操作系统)并不是直接调用main();,而是先调用C/C++运行期启动函数,此函数会初始化C/C++运行期库。我先不去理解这里的两个概念,我觉得这里的C/C++运行期使用的目的是为了实现C++的一种机制,比如malloc()管理自己的存储空间,分配对象,回收对象等。OK先不管它如何做的,总之它做好了运行一个Win32程序的一切准备工作,然后再建立一个进程,该进程有一个主线程,主线程的入口函数为main();,主线程的处于激活状态,主线程参与了CPU的竞争,程序正常运行了。

进程的建立
函数CreateProcess,原型见P15页。调用的时候需给系统指定一个STARTUPINFO变量,该变量保存一些进程初运行的信息,包括图形窗口的信息等。同时返回一个进程标志信息(PROCESS_INFORMATION),如ID、句柄信息等。
例:
STARTUPINFO si={sizeof(si)};
PROCESS_INFORMATION pi;
char* szCommandLine="notepad";
::CreateProcess(NULL,szCommandLine,NULL,NULL,FALSE,NULL,NULL,NULL,&si,&pi);
将打开记事本程序进程,si为运行初信息,pi为返回进程信息。

多线程
以前编程的思想都是单一的,一个程序总是从开始一行跑到最后一行,不管是分支还是循环,总之是从头运行到脚,跟汇编的思想一样,有一个PC指明指令的顺序,这个是容易理解的,因为我们......

阅读全文(6762) | 评论:5

Windows程序设计[1]-程序运行原理(2006-03-16 23:13:00)

摘要:今天又分了个新类出来,‘Win32编程’,其实Windows编程对我不算是刚接触,以前会一点VB,但VB对API封装得太深了,学C语言还是大一开始,决定更系统的学习一下。手上的书是《Windows程序设计》王艳平 著,感觉很不错。

废话少说,一起开始。

第1章是Windows编程基础,有对VC++的使用的以及Win32API介绍,略了。这里还提到代码风格,我觉得很重要。
记得大一的时候刚学完C,然后找本VC的书来看,硬是看不懂,像dword,bool,根本就没见过。书中推荐了一种写法:

1、原则,一个变量或者常量的名字应该越长越好,这里说的长是指所带的信息要足够,这和以前写的小程序不同,小程序里大可以用i,j,k,n之类的变量名,在大程序里就不能这样,在更大的工程里甚至还会用到命名空间,可见命名是很重要;那名字中要体现哪些信息呢?书中P6写到,‘本书规定的变量命名规则为:[限制范围的前缀]+[数据类型前缀]+[有意义的单词]’,限制范围是指变量的作用域,比如全局变量用g_(global),m_表示成员变量,默认为局部变量等;数据类型也缩为一个字,如长整型n,布尔型b,举个完事的例子就是,int m_nErrorCode;(4字节长的成员变量,长整型,表示错误编号)。
2、现在VC中也用了很多的类型别名,用typedef...;定义的宏,如下:

typedef unsigned long DWORD;
typedef int BOOL;
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef float FLOAT;
typedef void far *LPVOID;
typedef int INT;
typedef unsigned int UINT
...

从中可以看出来,新的名字里隐含更多的信息,如P表示指针等,从现在开始要熟悉这些新名......

阅读全文(6832) | 评论:7