孙鑫C++教程(全20讲)PPT讲义.ppt
1.Windows程序内部运行原理 主讲人:孙鑫http:/www.sunxin.org 应用程序 操作系统 输入输出设备 消息队列 http:/www.sunxin.org向下的箭头表示应用程序可以通知操作系统执行某个具体的动作,如操作系统能够控制声卡发出声音,但它并不知道应该何时发出何种声音,需要应用程序告诉操作系统该发出什么样的声音。这个关系好比有个机器人能够完成行走的功能,但是,如果人们不告诉它往哪个方向上走,机器人是不会主动行走的。这里的机器人就是操作系统,人们就是应用程序。 http:/www.sunxin.org那么,应用程序是如何通知操作系统执行某个功能的呢?有过编程经验的读者都应该知道,在应用程序中要完成某个功能,都是以函数调用的形式实现的,同样,应用程序也是以函数调用的方式来通知操作系统执行相应的功能的。操作系统所能够完成的每一个特殊功能通常都有一个函数与其对应,也就是说,操作系统把它所能够完成的功能以函数的形式提供给应用程序使用,应用程序对这些函数的调用就叫做系统调用,这些函数的集合就是Windows操作系统提供给应用程序编程的接口(Application Programming Interface),简称Windows API。如CreateWindow就是一个API函数,应用程序中调用这个函数,操作系统就会按照该函数提供的参数信息产生一个相应的窗口。 http:/www.sunxin.org向上的箭头表示操作系统能够将输入设备的变化上传给应用程序。如用户在某个程序活动时按了一下键盘,操作系统马上能够感知到这一事件,并且能够知道用户按下的是哪一个键,操作系统并不决定对这一事件如何作出反应,而是将这一事件转交给应用程序,由应用程序决定如何对这一事件作出反应。好比有个蚊子叮了我们一口,我们的神经末梢(相当于操作系统)马上感知到这一事件,并传递给了我们的大脑(相当于应用程序),我们的大脑最终决定如何对这一事件作出反应,如将蚊子赶走,或是将蚊子拍死。对事件作出反应的过程就是消息响应。 http:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orgtypedef struct _WNDCLASS UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HANDLE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; WNDCLASS; http:/www.sunxin.org在我们的程序中经常要用到一类变量,这个变量里的每一位(bit)都对应某一种特性。当该变量的某位为1时,表示有该位对应的那种特性,当该位为0时,即没有该位所对应的特性。当变量中的某几位同时为1时,就表示同时具有几种特性的组合。一个变量中的哪一位代表哪种意义,不容易记忆,所以我们经常根据特征的英文拼写的大写去定义一些宏,该宏所对应的数值中仅有与该特征相对应的那一位(bit)为1,其余的bit都为0。我们使用goto definition就能发现CS_VREDRAW=0 x0001,CS_HREDRAW=0 x0002,CS_DBLCLKS =0 x0008,CS_NOCLOSE=0 x0200。他们的共同点就是只有一位为1,其余位都为0。如果我们希望某一变量的数值既有CS_VREDRAW特性,又有CS_HREDRAW特性,我们只需使用二进制OR(|)操作符将他们进行或运算相组合,如style=CS_VREDRAW | CS_HREDRAW | CS_NOCLOSE。如果我们希望在某一变量原有的几个特征上去掉其中一个特征,用取反()之后再进行与(&)运算,就能够实现,如在刚才的style的基础上去掉CS_NOCLOSE特征,可以用style & CS_NOCLOSE实现。 http:/www.sunxin.org第二个成员变量lpfnWndProc指定了这一类型窗口的过程函数,也称回调函数。回调函数的原理是这样的,当应用程序收到给某一窗口的消息时(还记得前面讲过的消息通常与窗口相关的吗?),就应该调用某一函数来处理这条消息。这一调用过程不用应用程序自己来实施,而由操作系统来完成,但是,回调函数本身的代码必须由应用程序自己完成。对于一条消息,操作系统到底调用应用程序中的哪个函数(回调函数)来处理呢?操作系统调用的就是接受消息的窗口所属的类型中的lpfnWndProc成员指定的函数。每一种不同类型的窗口都有自己专用的回调函数,该函数就是通过lpfnWndProc成员指定的。 http:/www.sunxin.org举例:汽车厂家生产汽车好比应用程序创建窗口,用户使用汽车好比操作系统管理窗口,某种汽车在销售前就指定好了修理站(类似回调函数),当用户的汽车出现故障后(类似窗口收到消息),汽车用户(类似操作系统)自己直接找到修理站去修理,不用厂家(类似应用程序)亲自将车送到修理站去修理,但修理站还得由厂家事先建造好。 http:/www.sunxin.orghttp:/www.sunxin.orgC+中提供了一套输入输出流类的对象,它们是cin 、cout和cerr,对应c语言中的三个文件指针stdin、stdout、stderr,分别指向终端输入、终端输出和标准出错输出(也从终端输出)。cin与一起完成输入操作,cout、cerr与i;注意箭头的方向。在输出中我们还使用endl(end of line),表示换行,注意最后一个是字符l,而不是数字1,endl相当于C语言的n,表示输出一个换行。 http:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.org小技巧:小技巧:在以后的MFC编程中,如果在成员函数中想调用同类中的某个成员,可以使用VC+提供的自动列出成员函数功能,使用this-,VC+将列出该类中的所有成员,我们可以从列表中选择我们想调用的成员。自动列出成员函数功能,可以提高编写速度,减少拼写错误。我们经常不能完全记住某个函数的完整拼写,但却能够从列表中辨别出该函数,自动列出成员函数的功能在这时就显得更加有用了。事实上,在各种IDE编程环境中,我们通常都不可能记住也没有必要记住所有的函数,只要将常用的函数记住,其他不常用的函数只要记住其大概的写法和功能,在调用该函数时可以从自动列出成员函数中选取,这样可以大大节省我们的学习时间。我们不用花费大量的时间去死记硬背许多函数,利用自动列出成员函数功能和帮助系统,就能够在编程时顺利地使用这些函数,等用的次数多了,也就在不知不觉中完全掌握了这些函数。 http:/www.sunxin.orghttp:/www.sunxin.org基类的访问特性基类的访问特性类的继承特性类的继承特性子类的访问特性子类的访问特性PublicProtectedPrivatePublicPublicProtectedNo accessPublicProtectedPrivateProtectedProtectedProtectedNo accessPublicProtectedPrivatePrivatePrivatePrivateNo accesshttp:/www.sunxin.orghttp:/www.sunxin.orgchar ch;int i;1Bytech=(char)i;i=(int)ch;1Byte1Byte1Byte1Bytehttp:/www.sunxin.orgAnimal对象内存Fish继承部分this指针Fish对象的内存图 Fish对象内存布局http:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orgAnimal.cppFish.cppmain.cppAnimal.hFish.h翻译单元1翻译单元2翻译单元3Animal.objFish.objmain.obj.libC+的标准库函数标准类库.exe可执行文件编译(Compile)预处理链接(Link)#include#includehttp:/www.sunxin.orghttp:/www.sunxin.orgghtmAscenttmDescentbase linehttp:/www.sunxin.orghttp:/www.sunxin.org除WM_COMMAND之外,所有以WM_开头的消息。 从CWnd派生的类,都可以接收到这类消息。 来自菜单、加速键或工具栏按钮的消息。这类消息都以WM_COMMAND呈现。在MFC中,通过菜单项的标识(ID)来区分不同的命令消息;在SDK中,通过消息的wParam参数识别。从CCmdTarget派生的类,都可以接收到这类消息。 由控件产生的消息,例如,按钮的单击,列表框的选择等均产生此类消息,为的是向其父窗口(通常是对话框)通知事件的发生。这类消息也是以WM_COMMAND形式呈现。从CCmdTarget派生的类,都可以接收到这类消息。http:/www.sunxin.orgAfxWndProcAfxCallWndProcWindowProcOnWndMsgOnCommandOnNotifyOnCmdMsghttp:/www.sunxin.org012301234楼层房间http:/www.sunxin.org 菜单项状态的维护是依赖于CN_UPDATE_COMMAND_UI消息,谁捕获CN_UPDATE_COMMAND_UI消息,MFC就在其中创建一个CCmdUI对象。我们可以通过手工或利用ClassWizard在消息映射中添加ON_UPDATE_COMMAND_UI宏来捕获CN_UPDATE_COMMAND_UI消息。 在后台所做的工作是:操作系统发出WM_INITMENUPOPUP消息,然后由MFC的基类如CFrameWnd接管。它创建一个CCmdUI对象,并与第一个菜单项相关联,调用对象的一个成员函数DoUpdate()。这个函数发出CN_UPDATE_COMMAND_UI消息,这条消息带有指向CCmdUI对象的指针。同一个CCmdUI对象就设置为与第二个菜单项相关联,这样顺序进行,直到完成所有菜单项。 更新命令UI处理程序仅应用于弹出式菜单项上的项目,不能应用于永久显示的顶级菜单项目。http:/www.sunxin.orghttp:/www.sunxin.org 窗口的Z次序表明了重叠窗口堆中窗口的位置,这个窗口堆是按一个假想的轴定位的,这个轴就是从屏幕向外伸展的Z轴。Z次序最上面的窗口覆盖所有其它的窗口,Z次序最底层的窗口被所有其它的窗口覆盖。应用程序设置窗口在Z次序中的位置是通过把它放在一个给定窗口的后面,或是放在窗口堆的顶部或底部。 Windows系统管理三个独立的Z次序一个用于顶层窗口、一个用于兄弟窗口,还有一个是用于最顶层窗口。最顶层窗口覆盖所有其它非最顶层窗口,而不管它是不是活动窗口或是前台窗口。应用程序通过设置WS_EX_TOPMOST风格创建最顶层窗口。 一般情况下,Windows系统把刚刚创建的窗口放在Z次序的顶部,用户可通过激活另外一个窗口来改变Z次序;Windows系统总是把活动的窗口放在Z次序的顶部,应用程序可用函数BringWindowToTop把一个窗口放置到Z次序的顶部。函数SetWindowPos和DeferWindowPos用来重排Z次序。http:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orgpWeiXinBtn=0088:4400pWeiXinBtn=0088:46600088:44000088:4660m_btn1m_btn2CWeiXinBtn m_btn1;CWeiXinBtn m_btn2;http:/www.sunxin.orghttp:/www.sunxin.org1、创建位图CBitmap bitmap;bitmap.LoadBitmap(IDB_BITMAP1);2、创建兼容DCCDC dcCompatible;dcCompatible.CreateCompatibleDC(pDC);3、将位图选到兼容DC中dcCompatible.SelectObject(&bitmap);4、将兼容DC中的位图贴到当前DC中。pDC-BitBlt(rect.left,rect.top,rect.Width(),rect.Height(),&dcCompatible,0,0,SRCCOPY);位图兼容DC当前DChttp:/www.sunxin.orghttp:/www.sunxin.orgOnLButtonUp函数中0088:4400CGraph graph();CGraph的对象在栈中的内存0088:4400CPtrArray m_ptrArray;m_ptrArray.Add(&graph)http:/www.sunxin.orgCGraph的对象发生析构,内存被回收。如何解决这个问题呢?http:/www.sunxin.orgOnLButtonUp函数中1244:EE00CGraph的对象在堆中的内存CPtrArray m_ptrArray;CGraph *pGraph;0088:46601244:EE001244:EE00pGraph的内存m_ptrArray.Add(pGraph);pGraph=new CGraph();http:/www.sunxin.org仍然能索引到CGraph的对象pGraph的内存被回收(0,0)XmaxXminYmaxYminhttp:/www.sunxin.org,http:/www.sunxin.orghttp:/www.sunxin.orgYmaxYmin世界坐标系空间 下图是运用下图是运用SetWorldTransform函数而进函数而进行的一个典型转换。行的一个典型转换。YmaxYmin页面空间YminYmax设备空间物理设备http:/www.sunxin.orghttp:/www.sunxin.org窗口原点视口原点窗口视口页面空间设备空间http:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orgxViewExtxWinExtyViewExtyWinExtxWinExtxViewExtyWinExtyViewExthttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orgchar ch5=“lisi”;const char * pStr=ch;表示指向的对象是常量lisi0pStr=0088:44000088:4400指向内容不可改变*pStr=w;/errorpStr=“wangwu”; /okch指针值可以修改http:/www.sunxin.orgchar ch5=“lisi”;char * const pStr=ch;表示指针本身是常量lisi0pStr=0088:44000088:4400指针值不可修改pStr=“zhangsan”; /error*pStr=W;/okch指向的内容可以修改http:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.org1、操作系统用来管理进程的内核对象。内核对象也是系统用来存放关于进程的统计信息的地方。2、地址空间。它包含所有可执行模块或DLL模块的代码和数据。它还包含动态内存分配的空间。如线程堆栈和堆分配空间。http:/www.sunxin.orgn进程是不活泼的。进程从来不执行任何东西,它只是线程的容器。若要使进程完成某项操作,它必须拥有一个在它的环境中运行的线程,此线程负责执行包含在进程的地址空间中的代码。n单个进程可能包含若干个线程,这些线程都“同时” 执行进程地址空间中的代码。n每个进程至少拥有一个线程,来执行进程的地址空间中的代码。当创建一个进程时,操作系统会自动创建这个进程的第一个线程,称为主线程。此后,该线程可以创建其他的线程。http:/www.sunxin.orgn系统赋予每个进程独立的虚拟地址空间。对于32位进程来说,这个地址空间是4GB。n每个进程有它自己的私有地址空间。进程A可能有一个存放在它的地址空间中的数据结构,地址是0 x12345678,而进程B则有一个完全不同的数据结构存放在它的地址空间中,地址是0 x12345678。当进程A中运行的线程访问地址为0 x12345678的内存时,这些线程访问的是进程A的数据结构。当进程B中运行的线程访问地址为0 x12345678的内存时,这些线程访问的是进程B的数据结构。进程A中运行的线程不能访问进程B的地址空间中的数据结构,反之亦然。n4GB是虚拟的地址空间,只是内存地址的一个范围。在你能成功地访问数据而不会出现非法访问之前,必须赋予物理存储器,或者将物理存储器映射到各个部分的地址空间。n4GB虚拟地址空间中,2GB是内核方式分区,供内核代码、设备驱动程序、设备I/O高速缓冲、非页面内存池的分配和进程页面表等使用,而用户方式分区使用的地址空间约为2GB,这个分区是进程的私有地址空间所在的地方。一个进程不能读取、写入、或者以任何方式访问驻留在该分区中的另一个进程的数据。对于所有应用程序来说,该分区是维护进程的大部分数据的地方。http:/www.sunxin.orgn线程由两个部分组成:1、线程的内核对象,操作系统用它来对线程实施管理。内核对象也是系统用来存放线程统计信息的地方。2、线程堆栈,它用于维护线程在执行代码时需要的所有参数和局部变量。n当创建线程时,系统创建一个线程内核对象。该线程内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。可以将线程内核对象视为由关于线程的统计信息组成的一个小型数据结构。n线程总是在某个进程环境中创建。系统从进程的地址空间中分配内存,供线程的堆栈使用。新线程运行的进程环境与创建线程的环境相同。因此,新线程可以访问进程的内核对象的所有句柄、进程中的所有内存和在这个相同的进程中的所有其他线程的堆栈。这使得单个进程中的多个线程确实能够非常容易地互相通信。n线程只有一个内核对象和一个堆栈,保留的记录很少,因此所需要的内存也很少。n因为线程需要的开销比进程少,因此在编程中经常采用多线程来解决编程问题,而尽量避免创建新的进程。http:/www.sunxin.orgn操作系统为每一个运行线程安排一定的CPU时间 时间片。系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,因时间片相当短,因此,给用户的感觉,就好像线程是同时运行的一样。n如果计算机拥有多个CPU,线程就能真正意义上同时运行了。http:/www.sunxin.org单线程程序多线程程序一个线程两个线程http:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.org Windows核心编程机械工业出版社http:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.orgnStock:为每个控件提供的标准属性,如字体或颜色。nAmbient:围绕控件的环境属性已被置入容器的属性。这些属性不能被更改,但控件可以使用它们调整自己的属性。nExtended:这些是由容器处理的属性,一般包括大小和在屏幕上的位置。nCustom:由控件开发者添加的属性。http:/www.sunxin.orghttp:/www.sunxin.orgn自从微软推出第一个版本的Windows操作系统以来,动态链接库(DLL)一直是Windows操作系统的基础。n动态链接库通常都不能直接运行,也不能接收消息。它们是一些独立的文件,其中包含能被可执行程序或其它DLL调用来完成某项工作的函数。只有在其它模块调用动态链接库中的函数时,它才发挥作用。nWindows API中的所有函数都包含在DLL中。其中有3个最重要的DLL,Kernel32.dll,它包含用于管理内存、进程和线程的各个函数;User32.dll,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数;GDI32.dll,它包含用于画图和显示文本的各个函数。http:/www.sunxin.orghttp:/www.sunxin.orghttp:/www.sunxin.org代码页面2代码页面1数据页面2代码页面3数据页面1DLL的虚拟内存的虚拟内存代码页面2代码页面1数据页面2代码页面3数据页面1代码页面2代码页面1代码页面2代码页面3数据页面1数据页面2代码页面2代码页面2代码页面3数据页面1数据页面2代码页面1第一个进程的第一个进程的地址空间地址空间第二个进程的第二个进程的地址空间地址空间http:/www.sunxin.orghttp:/www.sunxin.org应用程序操作系统消息队列窗口过程http:/www.sunxin.org代码页面2代码页面1数据页面2代码页面3数据页面1DLL的虚拟内存的虚拟内存代码页面2代码页面1数据页面2代码页面3数据页面1代码页面2代码页面1代码页面2代码页面3数据页面1数据页面2代码页面2代码页面2代码页面3数据页面1数据页面2代码页面1第一个进程的第一个进程的地址空间地址空间第二个进程的第二个进程的地址空间地址空间http:/www.sunxin.org新页面http:/www.sunxin.org客户程序ODBC驱动程序管理器ODBC驱动程序 各种关系数据库http:/www.sunxin.orghttp:/www.sunxin.orgADO使用ADO的客户程序使用OLE DB访问数据库的程序ODBCOLE DB用户程序ODBC数据库ODBC数据库电子表格电子邮件其它非关系型存储http:/www.sunxin.orgOLE DB提供程序http:/www.sunxin.org