Win32编程笔记

Win32编程笔记

ID:37241418

大小:1.01 MB

页数:108页

时间:2019-05-20

上传者:U-2494
Win32编程笔记_第1页
Win32编程笔记_第2页
Win32编程笔记_第3页
Win32编程笔记_第4页
Win32编程笔记_第5页
资源描述:

《Win32编程笔记》由会员上传分享,免费在线阅读,更多相关内容在行业资料-天天文库

Day015一Windows编程基础51Windows应用程序的类型5二Windows开发环境61VisualStudioC++62VC的编译工具63Windows库和头文件64HelloWorld程序75编译环境准备86编译程序-CL87链接程序-LINK98执行9三第一个窗口程序9四资源的使用9五NMake的使用9day029一Windows字符处理10二字符集的使用101ASC码102代码页的切换103宽字节字符104TCHAR原型如下:115UNICODE字符打印11三窗口的注册121窗口程序的创建过程122窗口类的分类143系统窗口类的使用144应用程序局部窗口类155窗口类的风格156窗口类的查找过程157相关API16day0316一窗口的创建161创建窗口162子窗口的创建173窗口类和窗口的附加数据17二消息机制181程序执行机制192消息机制193常用的几个消息204消息的获取和发送21三消息的分类21day0422一消息队列22-107- 1消息队列222消息队列的类型223消息队列的关系224消息和消息队列23二消息的获取和发送231消息的获取232消息的发送24三WM_PAINT绘图窗口241WM_PAINT242窗口无效区域243WM_PAINT用法24四键盘消息251键盘消息252消息参数253消息的使用25五鼠标消息281鼠标消息282基本鼠标消息283双击鼠标消息294鼠标滚轮WM_MOUSEWHEEL31day0532一定时器消息321定时器消息322消息的参数323定时器使用33二菜单的使用351菜单的分类352窗口的顶层菜单353弹出式菜单Popup364菜单命令处理365菜单项的状态37三系统菜单371系统菜单的使用38四右键菜单ContextMenu381右键菜单使用382菜单处理位置40day0640一资源的使用411资源相关412菜单资源的使用413图标资源ICON424光标资源425字符串资源446加速键资源44-107- 二绘图451绘图相关452颜色453点的使用454线的使用(直线、圆形、弧线)465封闭图形46day0747一绘图对象482GDI绘图对象-画笔483GDI绘图对象-画刷494GDI绘图对象-位图(Bitmap)50二坐标系511坐标系分类512坐标系映射51三文字和字体521文字的绘制532文字颜色和背景533字体54day0854一对话框551对话框窗口552对话框基本使用553模式对话框的使用554无模式对话框565对话框/普通窗口56二子控件57三静态框57四按钮581按钮相关582下压式按钮583分组框584复选框585单选按钮59day0959一编辑框601编辑框相关602编辑框的使用60二组合框601组合框相关612组合框的使用613通知消息62三列表框62四滚动条控件631滚动条相关63-107- 2滚动条的使用633通知消息63day1064一MDI窗口64二Windows库程序671Windows库程序672静态库程序673动态库程序69day1172一DLL中类的使用732使用DLL中的类733动态库的程序入口73二Windows文件751Windows文件系统752Windows目录753Windows文件76三文件查找791查找文件792获取下一个文件(不包含子目录下的)793关闭查找80四Windows内存管理801Windows内存地址空间802Windows内存813虚拟内存(硬盘交换文件)分配82day1283一堆内存Heap831堆内存分配832堆的使用833VirtualAlloc/HeapAlloc/malloc/new84二栈内存(简介)85三内存映射文件851内存映射文件852内存映射文件的使用85四Windows进程871Windows进程872进程环境信息(简介即可,不需要写代码)873进程的信息884进程的使用89五Windows线程901Windows线程902线程的使用91day1395一线程的同步961多线程的问题96-107- 2线程同步技术963等候函数96二原子锁961相关问题972原子锁的使用98三临界区981问题982临界区983使用994原子锁和临界区100四事件1001相关问题1002事件的使用100五互斥Mutex1021相关的问题1022互斥的使用102六信号量1031相关的问题1032信号量的使用103七可等候定时器1061相关问题1062使用106Day01一Windows编程基础1Windows应用程序的类型1.1控制台程序ConsoleDOS程序。本身没有窗口,通过Windows的DOS窗口执行。1.2窗口程序拥有自己的窗口,可以与用户交互。1.3库程序存放代码、数据的程序,执行文件可以从中取出代码执行和获取数据。1.3.1动态库扩展名DLL,在执行文件执行时从中获取代码1.3.2静态库扩展名LIB,在编译链接程序时,将代码放入到执行文件中。1.4对比-107- 1.4.1入口函数控制台程序-main窗口程序-WinMain动态库程序-DllMain静态库程序-无入口函数1.4.2文件存在方式控制台程序、窗口程序-EXE文件动态库程序-DLL文件静态库程序-LIB文件1.4.3执行方式控制台程序-在DOS窗口内执行。窗口程序-拥有自己的窗口的执行文件动态库程序-本身无法执行,由可执行程序或其他的DLL调用静态库程序-执行是不存在,代码会嵌入到可执行文件或DLL等中。二Windows开发环境1VisualStudioC++VC1.5-VC6.0-VC2010(10.0)2VC的编译工具2.1编译器CL.EXE将源代码编译成目标代码2.2链接器LINK.EXE将目标代码、库链接生成最终文件2.3资源编译器RC.EXE将资源编译,最终通过链接器存入最终文件。3Windows库和头文件3.1Windows库kernel32.dll-提供了核心的API,例如进程、线程、内存管理等。user32.dll-提供了窗口、消息等API-107- gdi32.dll-绘图相关的API3.2头文件Windows.h-所有windows头文件的集合windef.h-windows数据类型winbase.h-kernel32的APIwingdi.h-gdi32的APIwinuser.h-user32的APIwinnt.h-UNICODE字符集支持4HelloWorld程序intWINAPIWinMain(HINSTANCEhInstance,//当前程序的实例句柄HINSTANCEhPrevInstance,//当前程序前一个实例句柄LPSTRlpCmdLine,//命令行参数字符串intnCmdShow//窗口的显示方式);参数说明:hPrevInstance-Win32下,一般为NULL。第一个参数hInstance,是标识该应用程序当前的实例的句柄。它是HINSTANCE类型,HINSTANCE是HandleofInstance的缩写,表示实例的句柄。HInstance是一个很关键的数据,它唯一的代表该应用程序,在后面初始化程序主窗口的过程中需要用到这个参数。这里有两个概念,一个是实例,一个是句柄。实例代表的是应用程序执行的整个过程和方法,一个应用程序如果没有被执行,只是存在于磁盘上,那么就说它是没有被实例化的;只要一执行,则说该程序的一个实例在运行。句柄,顾名思义,指的是一个对象的把柄。在Windows中,有各种各样的句柄,它们都是32位的指针变量,用来指向该对象所占据的内存区。句柄的使用,可以极大的方便Windows管理其内存中的各种对象。第二个参数是hPrevInstance,它是用来标识该应用程序的前一个实例句柄,对于基于Win32的应用程序来说,这个参数总是NULL。第三个参数是lpCmdLine,是指向应用程序命令行参数字符串的指针。如在Win95的“开始”菜单中单击“运行”,输入“easywinhello”,则此参数指向的字符串为“hello”。最后一个参数是nCmdShow,是一个用来指定窗口显示方式的整数。这个整数值可以是SW_SHOW、SW_HIDE、SW_SHOWMAXIMIZED、SW_SHOWMINIMIZED等。intMessageBox(HWNDhWnd,//父窗口句柄LPCTSTRlpText,//显示在消息框中的文字LPCTSTRlpCaption,//显示在标题栏中的文字UINTuType//消息框中的按钮、图标显示类型);//返回点击的按钮ID扩展说明:-107- 句柄是整个windows编程的基础。一个句柄是指使用的一个唯一的整数值,即一个四字节长的数值,来标志应用程序中的不同对象和同类对象中的不同的实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等。应用程序能够通过句柄访问相应的对象的信息,但是句柄不是一个指针,程序不能利用它句柄来直接阅读文件中的信息。如果句柄不用在I/O文件中,它是毫无用处的。句柄是windows用来标志应用程序中建立或是使用的对象的唯一整数,windows使用了大量的句柄来标志很多对象。WINDOWS程序中并不是用物理地址来标识一个内存块,文件,任务或动态装入模块的。相反,WINDOWSAPI给这些项目分配确定的句柄,并将句柄返回给应用程序,然后通过句柄来进行操作。  在《WINDOWS编程短平快》(南京大学出版社)一书中是这么说的:句柄是WINDOWS用来标识被应用程序所建立或使用的对象的唯一整数,WINDOWS使用各种各样的句柄标识诸如应用程序实例、窗口、控制、位图、GDI对象等等。WINDOWS句柄有点象C语言中的文件句柄。  从上面的2个定义中我们可以看到,句柄是一个标识符,是拿来标识对象或者项目的。它就像我们的车牌号一样,每一辆注册过的车都会有一个确定的号码,不同的车号码各不相同,但是也可能会在不同的时期出现两辆号码相同的车,只不过它们不会同时处于使用之中罢了。从数据类型上来看它只是一个32位的无符号整数。应用程序几乎总是通过调用一个WINDOWS函数来获得一个句柄,之后其他的WINDOWS函数就可以使用该句柄,以引用相应的对象。在WINDOWS编程中会用到大量的句柄,比如:HINSTANCE(实例句柄),HBITMAP(位图句柄),HDC(设备描述表句柄),HICON(图标句柄)等等。这当中还有一个通用的句柄,就是HANDLE。内核对象句柄,是用来标识某个内核对象的一个ID,同一个对象的该id对于每个进程是不同的,具体如何实现是ms不公开的算法,以下是一个近似的,可能的算法:进程创建时,windows系统为进程构造了一个句柄表当该进程希望获得一个内核对象句柄或者创建一个内核对象从而获得该对象句柄时,系统会将在句柄表中增加一个表项,表项的内容中存储了指向目标内核对象的指针,同时,系统返回这个表项在句柄表中的索引作为句柄。作用:句柄是一个标识符,是拿来标识对象或者项目的。应用程序几乎总是通过调用一个WINDOWS函数来获得一个句柄,之后其他的WINDOWS函数就可以使用该句柄,以引用相应的对象。如果想更透彻一点地认识句柄,我可以告诉大家,句柄是一种指向指针的指针。我们知道,所谓指针是一种内存地址。应用程序启动后,组成这个程序的各对象是驻留在内存中的。简单地理解,似乎我们只要获知这个内存的首地址,就可以随时用这个地址访问对象了。如果您真的这样认为,那您可就大错特错了。我们知道,Windows是一个以虚拟内存为基础的操作系统。在这种系统环境下,Windows内存管理器经常在内存中来回移动对象,以此来满足各种应用程序的内存需要。对象被移动意味着它的地址变化了。如果地址总是如此变化,我们该到哪里去找那一个对象呢?为了解决这个问题,Windows操作系统为全体应用程序腾出一些内存单元,用来专门登记各应用程序的对象在内存中的地址的变化,而前者的物理地址在系统运行期间是始终保持不变的。Windows内存管理器移动了对象在内存中的位置后,会把该对象新的地址及时地告知给对应的句柄进行更新。这样我们只要知道这个句柄,就可以间接地知道对象具体在内存中的哪个位置了。这个地址是在对象装载(Load)时由系统分配给的,当对象卸载时(Unload)又释放给系统。5编译环境准备VC98BINVCVARS32.BAT6编译程序-CLCL.EXE-显示CL的帮助-107- /c只编译不链接/Tc编译C文件/Tp编译C++文件/I头文件路径7链接程序-LINKLINK.EXExxx.objxxx.lib8执行*.exe或直接使用以.exe为扩展名的文件的名称即可。三第一个窗口程序1WinMain函数2定义窗口处理函数3注册窗口类4创建窗口5显示窗口6消息循环7消息处理四资源的使用1资源的文件2rc资源脚本文件3编译rc文件RC.EXE4将资源链接到程序中LINK.EXE五NMake的使用-107- day02一Windows字符处理1字符的编码方式1.1ASC码7位表示一个字符,128个1.2ASCII码8位表示一个字符,256个,但是前128个是不变的,还是ASC的128个。引入Codepage代码页可以切换数字所代表的字符。1.3DBCS字符DoubleByteCharacterSet由1个或2个字节表示一个字符:A我是程序员0102030405060708090A0B0102030405060708090A0B0102030405060708090A0B例:1.4UNICODE码在Windows平台下,采用2字节表示一个字符A我是程序员000102030405060708090A0BUTF-16UTF-8UTF-24二字符集的使用1ASC码2代码页的切换设置控制台下,输出的代码页BOOLSetConsoleOutputCP(UINTwCodePageID//代码页ID);-107- 3宽字节字符wchar_t每个字符占2个字节char每个字符占1个字节wchar_t实际是unsignedshort类型,定义时需要增加“L”,通知编译器按照双字节编译字符串,采用UNICODE编码。需要使用支持wchar_t函数操作宽字节字符串。例如:wchar_t*pwszText=L"Hellowchar";wprintf(L"%s ",pwszText);4TCHAR原型如下:#ifdefUNICODE//r_winnttypedefWCHARTCHAR,*PTCHAR;#define__TEXT(quote)L##quote#elsetypedefcharTCHAR,*PTCHAR;#define__TEXT(quote)quote#endif5UNICODE字符打印printf对UNICODE字符打印支持不完善。在Windows下可以使用WriteConsoleAPI打印UNICODE字符,它支持所有字符的打印。WriteConsole函数原型如下:BOOLWriteConsole(//API打印字符到控制台(dos窗口)HANDLEhOutput,//标准输出句柄CONSTVOID*lpBuffer,//输出字符串的BUFFDWORDnNumberOfCharsToWrite,//准备输出的字符串的长度LPDWORDlpNumberOfCharsWritten,//实际输出的字符串长度LPVOIDlpReserved//备用参数,可以为NULL);一般地,在使用时需要在入口函数添加两个函数:AllocConsole();和g_hOutput=GetStdHandle(STD_OUTPUT_HANDLE);在笔记中,所有全局变量在定义时都是以‘g_’开头。例打印出所有汉字到控制台:#defineUNICODE//必须放在windows.h文件之前#include"windows.h"voidPrintUnicode(){HANDLEnStdOut=GetStdHandle(STD_OUTPUT_HANDLE);for(WORDnHight=60;nHight<256;nHight++){for(WORDnLow=60;nLow<256;nLow++){-107- wchar_tnWchar=nHight*256+nLow;WriteConsole(nStdOut,&nWchar,1,NULL,NULL);}printf(" ");}}三窗口的注册1窗口程序的创建过程1.1定义WinMain入口函数1.2定义窗口处理函数WindowProc处理消息例:/*窗口处理函数*/LRESULTCALLBACKWindowProc(HWNDhWnd,UINTuMsg,WPARAMwParam,LPARAMlParam){switch(uMsg){/*caseWM_DESTROY:PostQuitMessage(0);//发送WM_QUIT消息break;*/caseWM_SYSCOMMAND://MessageBox(NULL,"WM_SYSCOMMAND","Info",MB_OK);{if(wParam==SC_CLOSE){PostQuitMessage(0);}}break;}returnDefWindowProc(hWnd,uMsg,wParam,lParam);}1.3注册窗口类:RegisterClass/RegisterClassEX(加强版)ATOMRegisterClass(CONSTWNDCLASS*lpWndClass//窗口类的数据);//注册成功后,返回一个数字标识。ATOMRegisterClassEx(CONSTWNDCLASSEX*lpwcx//窗口类的数据);其中,WNDCLASSEX是结构体类型数据,其原型如下:typedefstruct_WNDCLASSEX{//绿色是在struct_WNDCLASS上多加的UINTcbSize;//结构体的大小UINTstyle;//窗口类的风格-107- WNDPROClpfnWndProc;//窗口处理函数intcbClsExtra;//窗口类的附加数据大小intcbWndExtra;//窗口的附加数据大小HINSTANCEhInstance;//当前模块的实例句柄HICONhIcon;//窗口图标句柄HCURSORhCursor;//鼠标的句柄HBRUSHhbrBackground;//绘制窗口背景的画刷句柄LPCTSTRlpszMenuName;//窗口菜单的资源ID字符串LPCTSTRlpszClassName;//窗口类的名称HICONhIconSm;//窗口的小图标句柄}WNDCLASSEX,*PWNDCLASSEX;应用程序全局窗口类的注册,需要在窗口类的风格中增加CS_GLOBALCLASS,操作上只需要在注册窗口类时修改如下代码,例如:WNDCLASSEXwce={0};wce.style=CS_GLOBALCLASS;窗口类包含了窗口的各种参数信息的数据结构。每个窗口都具有一个窗口类,基于窗口类创建窗口。每个窗口类都具有一个名称,并且在使用前必须注册到系统。例:/*注册窗口类*/BOOLRegister(LPSTRpszClassName){WNDCLASSEXwce={0};wce.cbSize=sizeof(wce);wce.cbClsExtra=100;wce.cbWndExtra=100;wce.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);wce.hCursor=NULL;wce.hIcon=NULL;wce.hIconSm=NULL;wce.hInstance=g_hInstance;wce.lpfnWndProc=(WNDPROC)WindowProc;wce.lpszClassName=pszClassName;wce.lpszMenuName=NULL;wce.style=CS_HREDRAW|CS_VREDRAW;ATOMnAtom=RegisterClassEx(&wce);if(nAtom==0){returnFALSE;}returnTRUE;}1.4创建窗口函数CreateWindow例:/*创建窗口*/-107- HWNDCreate(LPSTRpszClassName,LPSTRpszWndName){HWNDhWnd=CreateWindowEx(0,pszClassName,pszWndName,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,g_hInstance,NULL);returnhWnd;}1.5显示窗口ShowWindow/UpdateWindow例:/*显示窗口*/voidDisplay(HWNDhWnd){ShowWindow(hWnd,SW_SHOW);UpdateWindow(hWnd);}1.6消息循环GetMessageTranslateMessageDisptachMessage//派发消息例:/*消息循环*/voidMessage(){MSGnMsg={0};while(GetMessage(&nMsg,NULL,0,0)){TranslateMessage(&nMsg);DispatchMessage(&nMsg);}}TranslateMessage函数是翻译消息,如按下一个按键,那么这个函数就会把WM_KEYDOWN和WM_KWYUP两个消息转换为WM_CHAR消息。DispatchMessage函数是为了把翻译后的消息分发给操作系统,有操作系统区调用回调函数去处理,回调函数是在设置窗口注册类的时候指定的,在其中一般会有很多case语句对不同的消息进行不同的处理(消息处理函数,有自己定义)。回调函数可以如下:LRESULTCALLBACKWindowProc(HWNDhwnd,//handletowindowUINTuMsg,//messageidentifierWPARAMwParam,//firstmessageparameterLPARAMlParam//secondmessageparameter)注意:其中函数名可以随便指定,如WinSunProc,但是LRESULTCALLBACK不能缺少。1.7消息处理(在窗口处理函数中处理WindowProc)-107- 2窗口类的分类2.1系统窗口类-系统已经定义好的窗口类,所有应用程序都可以直接使用。2.2应用程序全局窗口类-由用户自己定义,当前应用程序所有模块都可以使用。2.3应用程序局部窗口类-由用户自己定义,当前应用程序中本模块可以使用。3系统窗口类的使用不需要注册,直接使用窗口类即可。系统已经定义好相应名称,例如:按钮-BUTTON编辑框-EDIT例:简单的编辑窗口,并在窗口上写上“OK”。#include"stdafx.h"#include"windows.h"intAPIENTRYWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPSTRlpCmdLine,intnCmdShow){HWNDhWnd=CreateWindow("EDIT","OK",WS_OVERLAPPEDWINDOW,100,100,500,500,NULL,NULL,hInstance,NULL);ShowWindow(hWnd,SW_SHOW);UpdateWindow(hWnd);MSGnMsg={0};while(GetMessage(&nMsg,NULL,0,0)){TranslateMessage(&nMsg);DispatchMessage(&nMsg);}return0;}这个程序可以生成一个简易的编辑窗口,并且在其中写上“OK”。4应用程序局部窗口类在注册窗口类时,不要添加CS_GLOBALCLASS风格。5窗口类的风格CS_GLOBALCLASS-应用程序全局窗口类-107- CS_BYTEALIGNCLIENT-窗口客户区的水平位置,8倍数对齐CS_BYTEALIGNWINDOW-窗口的水平位置,8倍数对齐CS_HREDRAW-当窗口水平变化时,窗口重新绘制CS_VREDRAW-当窗口垂直变化时,窗口重新绘制CS_CLASSDC-该类型的窗口,都是有同一个绘图(DC)设备CS_PARENTDC-该类型的窗口,使用它的父窗口的绘图(DC)设备CS_OWNDC-该类型的窗口,每个窗口都使用自己的绘图(DC)设备CS_SAVEBITS-允许窗口保存成图(位图),提高窗口绘图效率,但是耗费内存资源CS_DBLCLKS-允许窗口接收鼠标左键双击CS_NOCLOSE-窗口没有关闭按钮6窗口类的查找过程6.1系统根据传入的窗口类名称,在应用程序局部窗口类中查找,如果找到执行6.2,如果未找到执行6.3。6.2比较局部窗口类与创建窗口时传入的HINSTANCE变量。如果发现相等,创建和注册的窗口类在同一模块,创建窗口返回。如果不相等,继续执行6.3。6.3在应用程序全局窗口类,如果找到,执行6.4,如果未找到执行6.56.4使用找到的窗口类的信息,创建窗口返回。6.5在系统窗口类中查找,如果找到创建窗口返回,否则创建窗口失败。7相关APIRegisterClass/Ex-注册GetClassInfo-获取信息UnregisterClass-卸载...day03一窗口的创建1创建窗口CreateWindow/CreateWindowExCreateWindowEx,函数原型如下:HWNDCreateWindowEx(-107- DWORDdwExStyle,//窗口的扩展风格LPCTSTRlpClassName,//已经注册的窗口类名称LPCTSTRlpWindowName,//窗口标题栏的名字DWORDdwStyle,//窗口的基本风格intx,//窗口左上角水平坐标位置inty,//窗口左上角垂直坐标位置intnWidth,//窗口的宽度intnHeight,//窗口的高度HWNDhWndParent,//窗口的父窗口句柄HMENUhMenu,//窗口菜单句柄HINSTANCEhInstance,//应用程序实例句柄LPVOIDlpParam//窗口创建时附加参数);//创建成功返回窗口句柄/*创建窗口*/HWNDCreate(LPSTRpszClassName,LPSTRpszWndName){HWNDhWnd=CreateWindowEx(0,pszClassName,pszWndName,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,g_hInstance,NULL);returnhWnd;}2子窗口的创建2.1创建时要设置父窗口句柄2.2创建风格要增加WS_CHILD|WS_VISIBLE2.3可以通过MoveWindow函数移动子窗口,其中的最后一个参数为刷新操作,没有则会是两个一样名字的子窗口/*创建子窗口*/HWNDCreateChild(LPSTRpszClassName,LPSTRpszWndName,HWNDhParent){HWNDhChild=CreateWindowEx(0,pszClassName,pszWndName,WS_CHILD|WS_VISIBLE|WS_OVERLAPPEDWINDOW,100,100,200,200,hParent,NULL,g_hInstance,NULL);returnhChild;}3窗口类和窗口的附加数据3.1作用-107- 注册窗口时,可以设置下面这两个数据内存空间的大小。intcbClsExtra;//窗口类的附加数据大小,一般定义为4字节的倍数intcbWndExtra;//窗口的附加数据大小它们可以提供窗口类和窗口以存放自己的数据的空间(缓冲区)。3.2窗口类使用1)定义数据空间的大小intcbClsExtra;//一般定义为4字节倍数(由其扩展性决定)2)存入数据DWORDSetClassLong(HWNDhWnd,//窗口句柄intnIndex,//字节索引号LONGdwNewLong//存入数据,占4个字节);//返回旧数据3)读取数据DWORDGetClassLong(HWNDhWnd,//窗口句柄intnIndex//字节索引号);//返回获取值3.3窗口使用1)定义数据空间的大小intcbWndExtra;//一般定义为4字节倍数2)存入数据LONGSetWindowLong(HWNDhWnd,//handletowindowintnIndex,//offsetofvaluetosetLONGdwNewLong//newvalue);3)读取数据LONGGetWindowLong(HWNDhWnd,//handletowindowintnIndex//offsetofvaluetoretrieve);4)不同点窗口类的附加数据提供BUFF,是所有该类窗口共享的BUFF。窗口附加数据提供BUFF,只属于该窗口所有,是窗口私有的BUFF。例:/*存数据*/voidSetExtra(HWNDhWnd){CHAR*szText="hellohellohello";//慎用SetClassLong(hWnd,0,(LONG)szText);SetWindowLong(hWnd,0,100L);}/*读取附加数据*/-107- voidGetExtra(HWNDhWnd){LONGnClassExtra=GetClassLong(hWnd,0);LONGnWndExtra=GetWindowLong(hWnd,0);CHARszText[256]={0};sprintf(szText,"CLS:%s,WND:%d",(CHAR*)nClassExtra,nWndExtra);MessageBox(NULL,szText,"Infor",MB_OK);}二消息机制1程序执行机制过程驱动-程序的执行过程是按照预定好的顺序执行;事件驱动-程序的执行是无序,用户可以根据需要随机触发相应的事件。Win32窗口程序就是采用事件驱动方式执行,即所谓之消息机制。2消息机制2.1消息定义1)消息当系统通知窗口工作时,就采用消息的方式派发给窗口。2)消息组成typedefstructtagMSG{//msgHWNDhwnd;//窗口句柄UINTmessage;//消息IDWPARAMwParam;//消息参数LPARAMlParam;//消息参数DWORDtime;//消息产生的时间POINTpt;//消息产生时的鼠标位置}MSG;2.2窗口处理函数每个窗口(包括子窗口)都必须具有窗口处理函数。函数原型为:LRESULTCALLBACKWindowProc(HWNDhwnd,//窗口句柄UINTuMsg,//消息IDWPARAMwParam,//消息参数LPARAMlParam//消息参数);当系统通知窗口时,会调用窗口处理函数,同时将消息ID和消息参数传递给窗口处理函数。-107- 在窗口处理函数中,不处理的消息使用缺省窗口处理函数,例如DefWindowProc。2.3消息相关函数1)GetMessage-获取消息。BOOLGetMessage(LPMSGlpMsg,//存放获取到的消息BUFFHWNDhWnd,//窗口句柄UINTwMsgFilterMin,//获取消息的最小IDUINTwMsgFilterMax//获取消息的最大ID);//只在当GetMessage函数获取到消息ID为WM_QUIT时返回0lpMsg-当获取到消息后,将消息的参数存放到MSG结构中。hWnd-获取到hWnd所指定窗口的消息。wMsgFilterMin和wMsgFilterMax只能获取到由它们指定的消息范围内的消息,如果都为0,表示没有范围。2)TranslateMessage-翻译消息。将按键消息,翻译成字符消息。BOOLTranslateMessage(CONSTMSG*lpMsg//要翻译的消息地址);检查消息是否是按键的消息,如果不是按键消息,不做任何处理,继续执行。3)DispatchMessage-派发消息。将消息派发到该消息所属窗口的窗口处理函数上。LRESULTDispatchMessage(CONSTMSG*lpmsg//要派发的消息);3常用的几个消息3.1WM_DESTROY-窗口被销毁时的消息,无消息参数。常用于在窗口被销毁之前,做相应的善后处理,例如资源、内存等。3.2WM_SYSCOMMAND-系统命令消息,当点击窗口的最大化、最小化、关闭等命令时,收到这个消息。常用在窗口关闭时,提示用户处理。wParam–制定具体命令,例如SC_CLOSE(关闭)等.lParam-鼠标位置LOWORD低字-水平位置HIWORD高字-垂直位置3.3WM_CREATE-在窗口创建成功但还未显示之前,收到这个消息。常用于初始化窗口的参数、资源等等,包括创建子窗口等。WPARAM-不使用LPARAM-是CREATESTRUCT结构的指针,保存了CreatWindowEx中的12个参数。3.4WM_SIZE-在窗口的大小发生变化后,会收到WM_SIZE。常用于窗口大小变化后,调整窗口内各个部分的布局。-107- WPARAM-窗口大小变化的原因。LPARAM-变化窗口客户区的大小LOWORD-变化后的客户区的宽度,低16位HIWORD-变化后的客户区的高度,高16位3.5WM_QUIT-用于结束消息循环处理,不会进入窗口处理函数。wParam-PostQuitMessage函数传递的参数。lParam-不使用当GetMessage收到这个消息后,会返回FALSE,结束while处理,退出消息循环。3.6WM_PAINT-绘图消息3.7键盘消息3.8鼠标消息3.9定时器消息4消息的获取和发送4.1消息的获取GetMessage-从系统获取消息,将消息从系统中移除,阻塞函数。当系统无消息时,GetMessage会等候下一条消息。PeekMessage-以查看的方式从系统获取消息,可以不将消息从系统移除,非阻塞函数。当系统无消息时,返回FALSE,并继续执行后续代码。BOOLPeekMessage(LPMSGlpMsg,//messageinformationHWNDhWnd,//handletowindowUINTwMsgFilterMin,//firstmessageUINTwMsgFilterMax,//lastmessageUINTwRemoveMsg//移除标识(PM_NOREMOVE));4.2消息的发送1)SendMessage/PostMessageBOOLPostMessage(HWNDhWnd,//消息发送的目的窗口UINTMsg,//消息IDWPARAMwParam,//消息参数LPARAMlParam//消息参数);LRESULTSendMessage(HWNDhWnd,//消息发送的目的窗口UINTMsg,//消息IDWPARAMwParam,//消息参数LPARAMlParam//消息参数);2)SendMessage-发送消息,会等候消息处理的结果。3)PostMessage-投递消息,消息发出后立刻返回,不等候消息执行结果。-107- 三消息的分类1系统消息-ID范围0-0x03FF(1024个),由系统定义好的消息,可以在程序中直接使用。2用户自定义消息-ID范围0x0400-0x7FFF(31743个),由用户自己定义,满足用户自己的需求。由用户自己发出消息,并响应处理。自定义消息宏:WM_USER。#defineWM_MYMESSAGEWM_USER+1//后面加的数字可以任意,但必须小于317433应用程序消息-ID范围0x8000-0xBFFF,程序之间通讯时使用的消息。应用程序消息宏:WM_APP。4系统注册消息-ID范围0xC000-0xFFFF,在系统注册并生成相应消息,然后可以在各个程序中使用这个消息。day04一消息队列1消息队列用于存放消息的一个队列,消息在队列中先入先出。所有窗口程序都具有消息队列。程序可以从队列中获取消息。2消息队列的类型1)系统消息队列是由系统维护的消息队列。存放系统产生的消息,例如鼠标、键盘等。2)程序消息队列属于每一个应用程序(线程)的消息队列。由应用程序(线程)维护。-107- 3消息队列的关系1)当鼠标、键盘产生消息时,会将消息存放到系统消息队列;2)系统会根据存放的消息,找到对应窗口的消息队列;3)将消息投递到程序的消息队列中;4)GetMessage/PeekMessage从程序的消息队列中获取消息。4消息和消息队列4.1根据消息和消息队列之间使用关系,将消息分成两类:队列消息-消息的发送和获取,都是通过消息队列完成。非队列消息-消息的发送和获取,是直接调用消息的窗口处理函数完成。4.2队列消息消息发送后,首先放入队列,然后通过消息循环,从队列当中获取。GetMessage/PeekMessage-从消息队列中获取消息PostMessage-将消息投递到消息队列常见队列消息:WM_PAINT、键盘、鼠标、定时器。4.3非队列消息消息发送时,首先查找消息接收窗口的窗口处理函数,直接调用处理函数,完成消息。SendMessage-直接将消息发送给窗口的处理函数,并等候处理结果。常见消息:WM_CREATE、WM_SIZE等。-107- 二消息的获取和发送1消息的获取1.1消息循环1)GetMessage从程序的消息队列当中,获取到消息。2)TranslateMessage检查获取到的消息,如果发现是按键消息,产生一个字符消息,并放入程序的消息队列。3)DispatchMessage根据消息找到窗口处理函数,并调用窗口处理函数完成消息的处理。1.2GetMessage/PeekMessage1)在程序(线程)消息队列查找消息,如果队列有消息,检查消息是否满足指定条件(HWND,ID范围),不满足条件就不会取出消息,否则从队列取出消息返回。2)如果程序(线程)消息队列没有消息,向系统消息队列获取属于本程序的消息;如果系统队列的当前消息属于本程序系统,则会将消息转发到程序队列,程序获取消息返回,然后处理消息。3)如果系统消息队列也没有消息,检查当前窗口需要重新绘制的区域,如果发现有需要绘制的区域,产生WM_PAINT消息,取得消息返回处理。4)如果没有重新绘制区域,检查定时器如果有到时的定时器,产生WM_TIMER,返回处理执行。5)如果没有到时的定时器,整理程序的资源、内存等等。6)GetMessage会继续等候下一条消息;PeekMessage则会返回FALSE,并交出程序的控制权。注意:GetMessage如果获取到是WM_QUIT,函数会返回FALSE。2消息的发送2.1SendMessage发送消息到指定的窗口,并等候对方将消息处理,然后消息执行结果。2.2PostMessage将消息放到消息队列中,立刻返回。无法获知消息是否被对方处理。三WM_PAINT绘图窗口1WM_PAINT当窗口需要绘制的时候,会发送窗口处理函数。-107- 2窗口无效区域这个区域被声明成需要重新绘制的区域。BOOLInvalidateRect(HWNDhWnd,//窗口句柄CONSTRECT*lpRect,//区域的矩形坐标BOOLbErase//重绘前是否先擦除);在程序中,如果需要绘制窗口,调用函数要声明窗口无效区域。3WM_PAINT用法3.1参数WPARAM-不使用LPARAM-不使用3.2消息处理1)开始绘图处理HDCBeginPaint(HWNDhwnd,//绘图窗口LPPAINTSTRUCTlpPaint//绘图参数的BUFF);返回绘图设备句柄HDC2)绘图3)结束绘图处理BOOLEndPaint(HWNDhWnd,//绘图窗口CONSTPAINTSTRUCT*lpPaint//绘图参数的指针,即BeginPaint返回);/*在窗口中绘画(写上)“hello”字符串*/voidOnPaint(HWNDhWnd){PAINTSTRUCTps={0};HDChDC=BeginPaint(hWnd,&ps);TextOut(hDC,100,100,"hello",strlen("hello"));EndPaint(hWnd,&ps);}四键盘消息1键盘消息WM_KEYDOWN-按键被按下时产生,如果按着不放,那么就会频繁产生该消息WM_KEYUP-按键被放开时产生,只产生一次-107- WM_SYSKEYDOWN-系统键按下时产生,排除ALT、F10WM_SYSKEYUP-系统键放开时产生WM_CHAR-字符消息(由TranslateMessage发送的)TranslateMessage执行机制:1)检查消息是否是WM_KEYDOWMN,如果是执行2);如果不是直接返回,不做任何处理。2)检查是否是可见字符的按键,如果是执行3);如果不是,不做任何处理直接返回。3)向消息队列中发送一个消息ID为WM_CHAR消息。2消息参数按键消息:WPARAM-按键的VirtualKeyLPARAM-按键的参数,例如按下次数WM_CHAR消息:WPARAM-输入的字符LPARAM-按键的相关参数3消息的使用1)KEYDOWN可以重复出现,KEYUP只能在按键松开时出现1次2)TranslateMessage在转换WM_KEYWODN消息时,对于可见字符可以产生WM_CHAR,不可见字符无此消息。3)WM_KEYWODN/UP的wParam是表示的按键,WM_CHAR是表示输入的字符。示例代码:#include"stdafx.h"#include"stdio.h"HINSTANCEg_hInstance=0;HANDLEg_hOutput=0;intg_xPos=100;intg_yPos=100;voidOnPaint(HWNDhWnd){CHARszText[256]={0};sprintf(szText,"%s ","WM_PAINT");//WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);PAINTSTRUCTps={0};HDChDC=BeginPaint(hWnd,&ps);TextOut(hDC,g_xPos,g_yPos,"hellochar",strlen("hellochar"));EndPaint(hWnd,&ps);}voidOnKeyDown(HWNDhWnd,WPARAMwParam){CHARszText[256]={0};sprintf(szText,"WM_KEYDOWN:%08X ",wParam);WriteConsole(g_hOutput,szText,strlen(szText),NULL,-107- NULL);switch(wParam){caseVK_UP:g_yPos--;break;caseVK_DOWN:g_yPos++;break;caseVK_LEFT:g_xPos--;break;caseVK_RIGHT:g_xPos++;break;}InvalidateRect(hWnd,NULL,TRUE);}voidOnKeyUp(HWNDhWnd,WPARAMwParam){CHARszText[256]={0};sprintf(szText,"WM_KEYUP:%08X ",wParam);WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);}voidOnChar(HWNDhWnd,WPARAMwParam){CHARszText[256]={0};sprintf(szText,"WM_CHAR:%08X ",wParam);WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);}/*窗口处理函数*/LRESULTCALLBACKWindowProc(HWNDhWnd,UINTuMsg,WPARAMwParam,LPARAMlParam){switch(uMsg){caseWM_PAINT:OnPaint(hWnd);break;caseWM_DESTROY:PostQuitMessage(0);break;caseWM_LBUTTONDOWN:InvalidateRect(hWnd,NULL,TRUE);break;caseWM_KEYDOWN:OnKeyDown(hWnd,wParam);break;caseWM_KEYUP:OnKeyUp(hWnd,wParam);break;caseWM_CHAR:OnChar(hWnd,wParam);break;}returnDefWindowProc(hWnd,uMsg,wParam,lParam);}/*注册窗口类*/BOOLRegister(LPSTRpszClassName){WNDCLASSEXwce={0};wce.cbSize=sizeof(wce);wce.cbClsExtra=0;wce.cbWndExtra=0;wce.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);wce.hCursor=NULL;wce.hIcon=NULL;wce.hIconSm=NULL;wce.hInstance=g_hInstance;wce.lpfnWndProc=(WNDPROC)WindowProc;-107- wce.lpszClassName=pszClassName;wce.lpszMenuName=NULL;wce.style=CS_HREDRAW|CS_VREDRAW;ATOMnAtom=RegisterClassEx(&wce);if(nAtom==0){returnFALSE;}returnTRUE;}/*创建窗口*/HWNDCreate(LPSTRpszClassName,LPSTRpszWndName){HWNDhWnd=CreateWindowEx(0,pszClassName,pszWndName,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,g_hInstance,NULL);returnhWnd;}/*显示窗口*/voidDisplay(HWNDhWnd){ShowWindow(hWnd,SW_SHOW);UpdateWindow(hWnd);}/*消息循环*/voidMessage(){MSGnMsg={0};while(GetMessage(&nMsg,NULL,0,0)){TranslateMessage(&nMsg);DispatchMessage(&nMsg);}}intAPIENTRYWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPSTRlpCmdLine,intnCmdShow){AllocConsole();g_hOutput=GetStdHandle(STD_OUTPUT_HANDLE);g_hInstance=hInstance;BOOLnRet=Register("Main");if(nRet==0){MessageBox(NULL,"注册失败","Infor",MB_OK);return0;-107- }HWNDhMain=Create("Main","主窗口");Display(hMain);Message();return0;}五鼠标消息1鼠标消息1.1基本鼠标消息WM_LBUTTONDOWN-鼠标左键按下WM_LBUTTONUP-鼠标左键抬起WM_RBUTTONDOWN-鼠标右键按下WM_RBUTTONUP-鼠标右键抬起WM_MOUSEMOVE-鼠标移动消息1.2双击消息WM_LBUTTONDBLCLK-鼠标左键双击WM_RBUTTONDBLCLK-鼠标右键双击1.3滚轮消息WM_MOUSEWHEEL-鼠标滚轮消息2基本鼠标消息2.1消息参数WPARAM-其他按键的状态,例如:Ctrl/Shift等LPARAM-鼠标的位置,窗口客户区坐标系。LOWORDX坐标位置HIWORDY坐标位置2.2鼠标消息使用一般情况鼠标按下/抬起成对出现。在鼠标移动过程中,会根据移动速度产生一系列的WM_MOUSEMOVE消息。3双击鼠标消息3.1消息参数WPARAM-其他按键的状态,例如:Ctrl/Shift等LPARAM-鼠标的位置,窗口客户区坐标系。-107- LOWORDX坐标位置HIWORDY坐标位置3.2使用注意:要想使鼠标产生双击消息的效果,就必须在注册窗口类中添加CS_DBLCLKS类型的风格。3.3消息产生顺序以WM_LBUTTONDBLCLK(左键双击)为例,在控制台上可以看到如下显示:WM_LBUTTONDOWNWM_LBUTTONUPWM_LBUTTONDBLCLKWM_LBUTTONUP可以看到的结果是只有一次按下,却又两次弹起。代码如下:#include"stdafx.h"#include"stdio.h"HINSTANCEg_hInstance=0;HANDLEg_hOutput=0;voidOnLButtonDown(HWNDhWnd,WPARAMwParam,LPARAMlParam){LONGxPos=LOWORD(lParam);LONGyPos=HIWORD(lParam);CHARszText[256]={0};sprintf(szText,"WM_LBUTTONDOWN按键状态:%08X,X=%d,Y=%d ",wParam,xPos,yPos);WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);}voidOnLButtonUp(HWNDhWnd,WPARAMwParam,LPARAMlParam){LONGxPos=LOWORD(lParam);LONGyPos=HIWORD(lParam);CHARszText[256]={0};sprintf(szText,"WM_LBUTTONUP按键状态:%08X,X=%d,Y=%d ",wParam,xPos,yPos);WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);}voidOnMouseMove(HWNDhWnd,WPARAMwParam,LPARAMlParam){LONGxPos=LOWORD(lParam);LONGyPos=HIWORD(lParam);CHARszText[256]={0};sprintf(szText,"WM_MOUSEMOVE按键状态:%08X,X=%d,Y=%d ",wParam,xPos,yPos);WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);}/*窗口处理函数*/LRESULTCALLBACKWindowProc(HWNDhWnd,UINTuMsg,WPARAMwParam,LPARAMlParam){switch(uMsg){-107- caseWM_LBUTTONDOWN:OnLButtonDown(hWnd,wParam,lParam);break;caseWM_LBUTTONUP:OnLButtonUp(hWnd,wParam,lParam);break;caseWM_DESTROY:PostQuitMessage(0);break;caseWM_MOUSEMOVE:OnMouseMove(hWnd,wParam,lParam);break;}returnDefWindowProc(hWnd,uMsg,wParam,lParam);}/*注册窗口类*/BOOLRegister(LPSTRpszClassName){WNDCLASSEXwce={0};wce.cbSize=sizeof(wce);wce.cbClsExtra=0;wce.cbWndExtra=0;wce.hbrBackground=(HBRUSH)(COLOR_WINDOW+1);wce.hCursor=NULL;wce.hIcon=NULL;wce.hIconSm=NULL;wce.hInstance=g_hInstance;wce.lpfnWndProc=(WNDPROC)WindowProc;wce.lpszClassName=pszClassName;wce.lpszMenuName=NULL;wce.style=CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS;ATOMnAtom=RegisterClassEx(&wce);if(nAtom==0){returnFALSE;}returnTRUE;}/*创建窗口*/HWNDCreate(LPSTRpszClassName,LPSTRpszWndName){HWNDhWnd=CreateWindowEx(0,pszClassName,pszWndName,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,g_hInstance,NULL);returnhWnd;}/*显示窗口*/voidDisplay(HWNDhWnd){ShowWindow(hWnd,SW_SHOW);UpdateWindow(hWnd);}/*消息循环*/voidMessage(){-107- MSGnMsg={0};while(GetMessage(&nMsg,NULL,0,0)){TranslateMessage(&nMsg);DispatchMessage(&nMsg);}}intAPIENTRYWinMain(HINSTANCEhInstance,HINSTANCEhPrevInstance,LPSTRlpCmdLine,intnCmdShow){AllocConsole();g_hOutput=GetStdHandle(STD_OUTPUT_HANDLE);g_hInstance=hInstance;BOOLnRet=Register("Main");if(nRet==0){MessageBox(NULL,"注册失败","Infor",MB_OK);return0;}HWNDhMain=Create("Main","主窗口");Display(hMain);Message();return0;}4鼠标滚轮WM_MOUSEWHEEL4.1消息参数WPARAM:LOWORD-其他按键的状态HIWORD-滚轮的偏移量是120的倍数,通过正负值表示表示滚动方向。正:向前滚动负:向后滚动LPARAM:鼠标当前的位置,屏幕坐标系LOWORD-X坐标HIWORD-Y坐标4.2使用通过偏移量,获取滚动的方向和倍数。注意:98年前的鼠标是没有滚轮的,所以要想使用滚轮消息就必须提高Win32版本。如何提高版本?可以在stdafx.h文件中添加语句:#define_WIN32_WINNT0x400;这是4.0版本。如果要提高到5.0版本,只需要把0x400改成0x500即可,如果要提升到其他版本,则以此类推。实现如下:#if!defined(AFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_)#defineAFX_STDAFX_H__A9DB83DB_A9FD_11D0_BFD1_444553540000__INCLUDED_-107- #if_MSC_VER>1000#pragmaonce#endif//_MSC_VER>1000#defineWIN32_LEAN_AND_MEAN//Excluderarely-usedstufffromWindowsheaders#define_WIN32_WINNT0x400//4.0版本,必须在#includes之前#includeday05一定时器消息1定时器消息可以在程序中设置定时器,当到达时间间隔时,定时器会向程序发送一个WM_TIMER消息。定时器的精度是毫秒,但是准确度很低。例如如果设置时间间隔为1000ms时,会在非1000毫秒到达。2消息的参数WPARAM-定时器IDLPARAM-定时器处理函数的指针3定时器使用3.1创建定时器UINTSetTimer(HWNDhWnd,//定时器窗口句柄UINTnIDEvent,//定时器IDUINTuElapse,//时间间隔TIMERPROClpTimerFunc//定时器处理函数指针);//创建成功,返回非0。说明:如果lpTimerFunc为NULL,则使用窗口处理函数做为定时器的处理函数;如果lpTimerFunc不为NULL,使用定时器处理函数处理定时器消息。例:画一个小球在窗口中跳动。第一步:设置定时器:#include"stdafx.h"#include"stdio.h"HINSTANCEg_hInstance=0;-107- HANDLEg_hOutput=0;#defineELL_LEN50//小球直径intg_xPos=100;intg_yPos=100;intLEFT_RIGHT=1;//从左向右运动intRIGHT_LEFT=0;intTOP_BOTTOM=1;//从上往下运动intBOTTOM_TOP=0;VOIDCALLBACKTimerProc(HWNDhwnd,UINTuMsg,UINTidEvent,DWORDdwTime){//测试所用CHARszText[256]={0};sprintf(szText,"WM_TIMER:%d ",idEvent);WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);}voidOnCreate(HWNDhWnd){SetTimer(hWnd,1,10,NULL);//创建并开启定时器,会调用窗口处理函数WindowProcSetTimer(hWnd,2,2000,TimerProc);//调用上面的(自定义)定时器处理函数TimerProc}3.2消息处理WM_TIMER3.3关闭定时器BOOLKillTimer(HWNDhWnd,//定时器窗口句柄UINTuIDEvent//定时器ID);附:GetClientRect函数可以获取窗口客户区大小,Ellipse为画圆函数BOOLGetClientRect(HWNDhWnd,//handletowindowLPRECTlpRect//clientcoordinates);第二步:写出小球的移动算法:voidOnTimer(HWNDhWnd,WPARAMwParam){CHARszText[256]={0};sprintf(szText,"WM_TIMER:%d ",wParam);WriteConsole(g_hOutput,szText,strlen(szText),NULL,NULL);RECTrcClient={0};GetClientRect(hWnd,&rcClient);if(g_xPos>rcClient.right-ELL_LEN){RIGHT_LEFT=1;LEFT_RIGHT=0;}elseif(g_xPosrcClient.bottom-ELL_LEN){TOP_BOTTOM=0;BOTTOM_TOP=1;}elseif(g_yPos_declspec(dllexport)intCdll_ADD(intadd1,intadd2){returnadd1+add2;}_declspec(dllexport)intCdll_SUB(intsub1,intsub2){returnsub1-sub2;}intCPPdll_ADD(intadd1,intadd2){returnadd1+add2;}intCPPdll_SUB(intsub1,intsub2){returnsub1-sub2;}_declspec(dllexport)intCPPdll_MUL(intmul1,intmul2){returnmul1*mul2;}3.3动态库的使用1)隐式链接2)显式链接-107- #pragmacomment(lib,"../lib/WinClib.lib")intmain(){intsum,sub;sum=C_ADD(3,2);sub=C_SUB(3,2);printf("sum=%d,sub=%d ",sum,sub);return0;}#include"stdio.h"_declspec(dllimport)intCPPdll_ADD(intadd1,intadd2);_declspec(dllimport)intCPPdll_SUB(intsub1,intsub2);_declspec(dllimport)intCPPdll_MUL(intmul1,intmul2);#pragmacomment(lib,"../lib/WinCPPdll.lib")extern"C"_declspec(dllimport)intCdll_ADD(intadd1,intadd2);extern"C"_declspec(dllimport)intCdll_SUB(intsub1,intsub2);#pragmacomment(lib,"../lib/WinCdll.lib")intmain(){intsum=CPPdll_ADD(6,5);intsub=CPPdll_SUB(6,5);intmul=CPPdll_MUL(6,5);printf("sum=%d,sub=%d,mul=%d ",sum,sub,mul);sum=Cdll_ADD(5,3);sub=Cdll_SUB(5,3);printf("sum=%d,sub=%d ",sum,sub);return0;}3.4动态库的函数3.4.1实现动态库的函数3.4.2库函数的导出1)C++的导出使用_declspec(dllexport)导出函数注意:动态库编译链接后,也会有LIB文件生成,它是作为动态库函数映射使用的,与静态库不完全相同。2)C的导出方式extern“C”_declspec(dllexport)intSub(...);3)模块定义文件.def例如:LIBRARYDLLFunc//库,第一行可写可不写EXPORTS//库导出表DLL_Mul@1//导出的函数LIBRARYWinCPPdllEXPORTS-107- CPPdll_ADD@1CPPdll_SUB@23.4.3库函数的使用3.4.3.1隐式链接。没有显式使用DLL文件,而是调用LIB文件。1)头文件和函数原型可以在函数原型的定义前,增加_declspec(dllimport),例如:_declspec(dllimport)intDLL_Add(...);_declspec(dllimport)intCPPdll_ADD(intadd1,intadd2);_declspec(dllimport)intCPPdll_SUB(intsub1,intsub2);_declspec(dllimport)intCPPdll_MUL(intmul1,intmul2);#pragmacomment(lib,"../lib/WinCPPdll.lib")如果库函数使用C格式导出,需要在函数定义前增加extern“C”extern"C"_declspec(dllimport)intCdll_ADD(intadd1,intadd2);extern"C"_declspec(dllimport)intCdll_SUB(intsub1,intsub2);#pragmacomment(lib,"../lib/WinCdll.lib")2)导入动态库的LIB文件3)在程序中使用函数4)隐式链接的情况,DLL可以存放的路径:(1)与执行文件中同一个目录下(2)当前工作目录(3)Windows目录(4)Windows/System32目录(5)Windows/System(6)环境变量PATH指定目录注意:高版本VC的配置文件3.4.3.2显式链接(需要添加头文件windows.h)一般情况下加以使用显式连接。1)定义函数指针类型2)加载动态库HMODULELoadLibrary(LPCTSTRlpFileName//动态库文件名或全路径);//返回DLL的实例句柄(HINSTANCE)HMODULEnDll=LoadLibrary(“WinCPPdll.dll”);3)获取函数地址(动态库中的偏移地址)FARPROCGetProcAddress(HMODULEhModule,//DLL句柄LPCSTRlpProcName//函数名称);//成功返回函数地址DLL_addAdd=(DLL_add)GetProcAddress(nDll,DLL_ADD);4)使用函数5)卸载动态库BOOLFreeLibrary(HMODULEhModule//DLL的实例句柄-107- );FreeLibrary(nDll);详细代码如下所示:#include"windows.h"#include"stdio.h"typedefint(*DLL_add)(intadd1,intadd2);//指针函数typedefint(*DLL_sub)(intsub1,intsub2);typedefint(*DLL_mul)(intmul1,intmul2);intmain(){HMODULEnDll=LoadLibrary("WinCPPdll.dll");printf("nDll=%p ",nDll);DLL_addAdd=(DLL_add)GetProcAddress(nDll,"CPPdll_ADD");printf("Add=%p ",Add);intsum=Add(3,2);printf("sum=%d ",sum);DLL_subSub=(DLL_sub)GetProcAddress(nDll,"CPPdll_SUB");printf("Sub=%p ",Sub);intsub=Sub(3,2);printf("sub=%d ",sub);DLL_mulMul=(DLL_mul)GetProcAddress(nDll,"CPPdll_MUL");printf("Mul=%p ",Mul);intmul=Mul(3,2);printf("mul=%d ",mul);FreeLibrary(nDll);return0;}3.4.3.3两种链接方式对比1)在库函数的定义不变情况下:隐式链接,由于库函数地址是在程序编译链接时设置,所以当动态库变化后,使用程序需要重新编译链接。显式链接,由于库函数地址是在程序执行时,动态的从库中查询,所以库变化后,不需要重新编译链接。2)动态库加载隐式链接,动态库是在程序启动时就被加载,当DLL不存在,程序无法启动,需要用到.lib文件。显式链接,动态库只在使用LoadLibrary函数,才会被加载不需要用到.lib文件。3.5动态库同样可以封装变量(不建议使用)-107- day11一DLL中类的使用1.1DLL中类的实现1.2DLL中类的导出在类名与class之间增加_declspec(dllexport)定义,例如:class_declspec(dllexport)CMath{...};通常使用预编译开关切换类的导入导出定义,例如:#ifdefDLLCLASS_EXPORTS#defineEXT_CLASS_declspec(dllexport)//DLL#else#defineEXT_CLASS_declspec(dllimport)//使用者#endifclassEXT_CLASSCMath{...};2使用DLL中的类2.1导入DLL的LIb2.2类的定义2.3使用类3动态库的程序入口入口程序不是DLL必须的。常用于DLL内部初始化或善后处理。BOOLWINAPIDllMain(HINSTANCEhinstDLL,//动态库实例句柄DWORDfdwReason,//被调用的原因LPVOIDlpvReserved//保留值);//返回TRUE,表示动态库加载成功。动态库的加载或卸载时会被调用。例如:使用LoadLibrary或FreeLibrary时会被调用。下面给出带有自定义头文件的库的生成和使用的代码:-107- 自定义一个头文件:#ifndefDLL_CLASS#defineDLL_CLASS#ifdefDLLCLASS_EXPORTS#defineEXT_CLASS_declspec(dllexport)//用宏EXT_CLASS替代函数_declspec(dllexport)#else#defineEXT_CLASS_declspec(dllimport)#endifclassEXT_CLASSCMath{public:intadd(intadd1,intadd2);intsub(intsub1,intsub2);};#endif实现:#include"windows.h"#include"stdio.h"#defineDLLCLASS_EXPORTS#include"dllclass.h"BOOLWINAPIDllMain(HINSTANCEhinstDLL,DWORDfdwReason,LPVOIDlpvReserved){switch(fdwReason){caseDLL_PROCESS_ATTACH:printf("Load...... ");break;caseDLL_PROCESS_DETACH:printf("UnLoad...... ");break;}returnTRUE;}intCMath::add(intadd1,intadd2){returnadd1+add2;}intCMath::sub(intsub1,intsub2){returnsub1-sub2;}使用动态库的:#include"stdio.h"#include"..//dllclass//dllclass.h"#pragmacomment(lib,"..\lib\dllclass.lib")intmain(){-107- CMathmath;intsum=math.add(10,50);intsub=math.sub(50,30);printf("sum=%d,sub=%d ",sum,sub);return0;}二Windows文件1Windows文件系统1)操作系统管理磁盘数据的几种方式:FAT、、FAT32、NTFS2)硬盘的管理磁道扇扇区–每个扇区的大小为512字节3)文件系统最小管理单位簇-由连续的几个扇区组成8/16/32/64文件存储时,是以簇为单位占用硬盘空间,即使文件只有1个字节,也要占用1簇的空间。FAT32-1簇16/32扇区,即便文件中只有一个字符,也要占用16K或32KNTFS-1簇8扇区4K,即便文件占用不到的4k,那么实际占用还是4k。2Windows目录2.1Windows相关的目录1)程序当前工作目录①获取当前工作目录:DWORDGetCurrentDirectory(DWORDnBufferLength,//sizeofdirectorybufferLPTSTRlpBuffer//directorybuffer);例:获取当前工作目录:CHARszPath[256]={0};GetCurrentDirectory(256,szPath);printf("CurrentDir:%s ",szPath);②设置当前工作目录BOOLSetCurrentDirectory(LPCTSTRlpPathName//newdirectoryname);例:SetCurrentDirectory("C:/");-107- GetCurrentDirectory(256,szPath);printf("CurrentDir:%s ",szPath);2)Windows目录UINTGetWindowsDirectory(LPTSTRlpBuffer,//bufferforWindowsdirectoryUINTuSize//sizeofdirectorybuffer);3)Windows的System目录UINTGetSystemDirectory(LPTSTRlpBuffer,//bufferforsystemdirectoryUINTuSize//sizeofdirectorybuffer);4)临时文件目录DWORDGetTempPath(DWORDnBufferLength,//sizeofbufferLPTSTRlpBuffer//pathbuffer);2.2目录创建BOOLCreateDirectory(LPCTSTRlpPathName,//目录名称LPSECURITY_ATTRIBUTESlpSecurityAttributes//安全属性,默认为NULL);2.3目录删除BOOLRemoveDirectory(LPCTSTRlpPathName//目录名称);//要删除的目录中不能包含子目录或文件,即只能删除空目录2.4目录修改BOOLMoveFile(LPCTSTRlpExistingFileName,//当前的名称LPCTSTRlpNewFileName//新的名称);//不能改盘移动如果在C:/下建有123空目录,那么,如果要想把hello目录移到目录名为123的目录中就可以使用下面的语句:MoveFile("C:/hello","C:/123/hello");如果要把移动后的hello的名字给为Nohello,那么就可以这样:MoveFile("C:/hello","C:/123/Nohello");如果没有指定移动后的名字,如:MoveFile("C:/hello","C:/123/");那么就会移动失败。3Windows文件3.1创建或打开Windows文件HANDLECreateFile(-107- LACTATEfilenames,//文件名称DWORDdwDesiredAccess,//访问权限DWORDdwShareMode,//共享方式LPSECURITY_ATTRIBUTESlpSecurityAttributes,//安全属性,默认为NULLDWORDdwCreationDisposition,//创建方式DWORDdwFlagsAndAttributes,//文件属性HANDLEhTemplateFile//文件句柄模板,默认为NULL,同步传输);//成功返回文件句柄。创建方式:CREATE_ALWAYS-如果没有指定文件,那么创建;如果有,那么清空原始文件中的内容。CREATE_NEW-新建。例:voidCreate(){HANDLEhFile=CreateFile("C:/file.txt",GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);CloseHandle(hFile);}3.2写数据BOOLWriteFile(HANDLEhFile,//文件句柄LPCVOIDlpBuffer,//数据BUFFDWORDnNumberOfBytesToWrite,//数据长度LPDWORDlpNumberOfBytesWritten,//返回实际写入的数据LPOVERLAPPEDlpOverlapped//默认为NULL,同步传输);例:voidWrite(){HANDLEhFile=CreateFile("C:/file.txt",GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);CHARszText[]="hellofile";DWORDnWrite=0;WriteFile(hFile,szText,strlen(szText),&nWrite,NULL);printf("准备输出:%d,实际输出:%d ",strlen(szText),nWrite);CloseHandle(hFile);}3.3读数据BOOLReadFile(HANDLEhFile,//文件句柄LPVOIDlpBuffer,//数据BUFFDWORDnNumberOfBytesToRead,//要读的字节数LPDWORDlpNumberOfBytesRead,//实际读到字节数LPOVERLAPPEDlpOverlapped//默认为NULL-107- );例:voidRead(){HANDLEhFile=CreateFile("C:/file.txt",GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);CHARszText[256]={0};DWORDnRead=0;LONGnLen=GetFileSize(hFile,NULL);ReadFile(hFile,szText,5,&nRead,NULL);printf("准备读到:%s,%d,实际读到:%d ",szText,nLen,nRead);CloseHandle(hFile);}3.4关闭文件BOOLCloseHandle(HANDLEhObject//文件句柄);3.5文件长度DWORDGetFileSize(HANDLEhFile,//文件句柄LPDWORDlpFileSizeHigh//文件长度的高32位);//返回值是文件长度的低32位如果文件长度大于4G,那么32位显然不够,所以请思考为什么上面的函数可行?WORDnLen=GetFileSize(hFile,NULL);3.6文件指针DWORDSetFilePointer(HANDLEhFile,//文件句柄LONGlDistanceToMove,//偏移量的低32位PLONGlpDistanceToMoveHigh,//偏移量的高32位DWORDdwMoveMethod//偏移的相对位置);//函数返回实际偏移量的低32位,lpDistanceToMoveHigh返回实际偏移量的高32位。3.7文件相关操作CopyFile-拷贝文件,可以跨盘BOOLCopyFile(LPCTSTRlpExistingFileName,//nameofanexistingfileLPCTSTRlpNewFileName,//nameofnewfileBOOLbFailIfExists//TRUE-不覆盖;FALSE-覆盖);//可以更改名字例:voidCopy(){//该名字复制CopyFile("C:/file.txt","C:/123/file.txt",FALSE);}voidCopy(){//不改名字复制-107- CopyFile("C:/file.txt","C:/123/nofile.txt",FALSE);}DeleteFile-删除文件,即便文件时打开的也会删除BOOLDeleteFile(LPCTSTRlpFileName//filename);例:voidDelete(){DeleteFile("C:/file.txt");}MoveFile-移动文件,并且移动的过程中可以更改名称例:voidMove(){MoveFile("C:/123/nofile.txt","d:/file.txt");}3.8文件属性GetFileAttributes-获取文件属性DWORDGetFileAttributes(LPCTSTRlpFileName//nameoffileordirectory);//返回属性SetFileAttributes-设置文件属性,必须先获得文件属性BOOLSetFileAttributes(LPCTSTRlpFileName,//filenameDWORDdwFileAttributes//attributes);例:voidAttr(){DWORDnAttr=GetFileAttributes("C:/file.txt");SetFileAttributes("C:/file.txt",nAttr|FILE_ATTRIBUTE_READONLY);}GetFileAttributesEx-获取文件属性、时间等三文件查找1查找文件HANDLEFindFirstFile(//只找到指定路径下的第一个文件LPCTSTRlpFileName,//查找路径LPWIN32_FIND_DATAlpFindFileData//查找的数据);//返回查找句柄说明:WIN32_FIND_DATA是个结构体类型。-107- 2获取下一个文件(不包含子目录下的)BOOLFindNextFile(HANDLEhFindFile,//查找句柄LPWIN32_FIND_DATAlpFindFileData//查找的数据);//找到返回TRUE,否则返回FALSE3关闭查找BOOLFindClose(HANDLEhFindFile//查找句柄);例获取C:/下所有文件:voidFind(){WIN32_FIND_DATAwfd={0};HANDLEhFind=FindFirstFile("C:/*.*",&wfd);BOOLgoFind=TRUE;while(goFind){if(wfd.dwFileAttributes==FILE_ATTRIBUTE_ARCHIVE){//文件printf("%s ",wfd.cFileName);}else{//目录printf("[%s] ",wfd.cFileName);}goFind=FindNextFile(hFind,&wfd);}FindClose(hFind);}四Windows内存管理1Windows内存地址空间1.1地址空间程序中可以寻址的最大范围。对于32位操作系统,地址空间范围为0-4G(232),地址空间越大,相对程序的编写就会容易。1.2地址空间的划分1)用户地址空间0-2G(7FFFFFFF)存放用户的程序和数据。用户空间的代码是不能访问内核空间的数据和代码。①空指针区(NULL区,0-64K)系统将地址小于64K指针,都认为是空指针。②用户区-107- ③64K禁入区(0x7FFEFFFF-0x7FFFFFFF),用户区不能访问内核区,但是内核区可以访问用户区。其作用就是防止用户访问内核区域。2)内核地址空间2G-4G存放内核的代码和数据,例如系统驱动。内核空间代码是可以访问用户空间。2Windows内存2.1区域区域就是连续的一块内存。区域的大小一般为64K,或者64K的倍数。每个区域都有自己的状态:1)空闲:没有被使用2)私有:被预定的区域3)映像:存放代码4)映射:存放数据2.2物理内存系统可以使用的实际内存(内存条)。CPU可以直接访问的内存。2.3虚拟内存将硬盘文件虚拟成内存使用。(pagefile.sys文件,即内存交换文件)CPU如果要访问虚拟内存数据,必须将虚拟内存数据放到物理内存。2.4内存页(地址概念,与内存无关)系统管理内存的最小单位。内存页大小为4K,每个内存页有自己的权限。2.5页目表指针地址(所有指针都占32位)2.6从内存获取数据过程1)根据地址在物理内存中查找相应的位置。如果找到物理内存,取回数据。如果未找到,执行2);2)根据地址去虚拟内存中查找相应的位置。如果未找到,那么该地址没有内存空间,返回错误。如果找到,执行3);3)将该地址所在内存页,置换到物理内存中,同时将原物理内存数据,存入到虚拟内存中。4)使用完后将物理内存中的数据返回给使用者。2.7内存分配1)虚拟内存分配适合大内存分配,一般是1M之上的内存。2)堆内存分配适合小内存分配,一般是1M以下的内存。比如malloc/new申请的内存。-107- 3)栈内存分配适合小内存分配,一般是1M以下的内存。每个线程都有自己的栈。3虚拟内存(硬盘交换文件)分配3.1虚拟内存分配速度快,大内存效率高。将内存和地址分配分别执行,可以在需要的时候再提交内存。常用字大型电子表格等处理。3.2虚拟内存使用1)内存分配LPVOIDVirtualAlloc(LPVOIDlpAddress,//NULL或提交地址SIZE_TdwSize,//分配的大小DWORDflAllocationType,//分配方式DWORDflProtect//内存访问方式,最好是可读可写);//分配成功返回地址分配方式:MEM_COMMIT-提交内存,分配之后返回地址和内存空间MEM_RESERVE-保留地址,分配之后只返回地址,内存空间不生成。要使用内存必须再次提交。申请的内存不能使用,类似野指针。下面两个函数都是分配4G内存,试说明这两种分配方式的不同(差异):voidVirtual_Commit(){CHAR*pszText=(CHAR*)VirtualAlloc(NULL,4096,MEM_COMMIT,PAGE_READWRITE);strcpy(pszText,"HelloCommit");printf("%s ",pszText);VirtualFree(pszText,0,MEM_RELEASE);}voidVirtual_Reserve(){CHAR*pszText=(CHAR*)VirtualAlloc(NULL,4096,MEM_RESERVE,PAGE_READWRITE);strcpy(pszText,"HelloCommit");printf("%s ",pszText);VirtualFree(pszText,0,MEM_RELEASE);}说明:第一个函数是正确的,但是第二个函数错误。那么该怎么修改?可以这样:voidVirtual_Reserve(){CHAR*pszText=(CHAR*)VirtualAlloc(NULL,4096,MEM_RESERVE,PAGE_READWRITE);CHAR*pszText1=(CHAR*)VirtualAlloc(pszText,256,MEM_COMMIT,PAGE_READWRITE);strcpy(pszText,"HelloCommit");printf("%s ",pszText);VirtualFree(pszText,0,MEM_RELEASE);}这时候的pszText和pszText1是一样的,即是说把strcpy(pszText,"HelloCommit");改为strcpy(pszText1,"HelloCommit");也一样,调用后都可以打印"HelloCommit"。2)使用-107- 3)释放BOOLVirtualFree(LPVOIDlpAddress,//释放地址SIZE_TdwSize,//释放的大小,为0就可以了,也可以是申请的空间大小DWORDdwFreeType//释放方式);释放方式:MEM_DECOMMIT-只释放内存MEM_RELEASE-地址和内存都释放day12一堆内存Heap1堆内存分配适合分配小内存,一般是小于1M的内存。一般每个程序都有自己的堆,默认大小为1M,会根据使用情况进行调整。2堆的使用2.1堆的信息GetProcessHeap-获得程序的堆HANDLEGetProcessHeap(VOID);GetProcessHeaps-获取程序中所有的堆DWORDGetProcessHeaps(DWORDNumberOfHeaps,//缓冲区个数PHANDLEProcessHeaps//bufferforheaphandles);//返回实际堆的个数例:voidHeapIfo(){HANDLEhHeap=GetProcessHeap();printf("第一个堆:%p ",hHeap);-107- HANDLEhHeaps[256]={0};//事实上一般不会超过50个intnCount=GetProcessHeaps(256,hHeaps);for(inti=0;iHeapAlloc->VirtualAlloc……二栈内存(简介)5.1栈内存每个线程都具有自己的栈,默认大小1M。5.2一般是系统维护栈Windows提供了_alloca,可以在栈上分配内存。三内存映射文件1内存映射文件将文件映射成内存来使用。当使用内存时(比如多线程通信),就是在使用文件。2内存映射文件的使用2.1创建或打开文件,甚至可以是任意不存在的后缀名CreateFile2.2创建内存映射文件HANDLECreateFileMapping(HANDLEhFile,//文件句柄LPSECURITY_ATTRIBUTESlpAttributes,//安全属性DWORDflProtect,//访问方式DWORDdwMaximumSizeHigh,//内存映射文件大小的高32DWORDdwMaximumSizeLow,//内存映射文件大小的低32,小于4GLPCTSTRlpName//命名,可以为NULL);//创建成功返回句柄2.3加载内存映射文件LPVOIDMapViewOfFile(HANDLEhFileMappingObject,//内存映射文件句柄DWORDdwDesiredAccess,//访问模式DWORDdwFileOffsetHigh,//偏移量的高32位DWORDdwFileOffsetLow,//偏移量的低32位SIZE_TdwNumberOfBytesToMap//映射的字节数量);//成功返回地址dwFileOffsetHigh和dwFileOffsetLow合成的偏移量,必须是区域粒度(64K)的整数倍2.4使用内存2.5卸载内存映射文件(地址)BOOLUnmapViewOfFile(-107- LPCVOIDlpBaseAddress//卸载地址);2.6关闭(销毁)内存映射文件CloseHandle2.7关闭文件CloseHandle例:#include#include"windows.h"TCHARszName[]=__TEXT("hucong");//下划线可加可不加voidMapWrite(){/*创建一个文件*/HANDLEhFile=CreateFile("C:/map.txt",GENERIC_WRITE|GENERIC_READ,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);/*用磁盘中的文件创建一个内存映射文件*/HANDLEhMapping=CreateFileMapping(hFile,NULL,PAGE_READWRITE,0,1024*1024,szName);/*加载内存映射文件,并依据偏移量得到地址*/LPSTRszText=(LPSTR)MapViewOfFile(hMapping,FILE_MAP_ALL_ACCESS,0,0,1024);strcpy(pszText,"hello");printf("%s ",pszText);/*卸载内存映射文件*/UnmapViewOfFile(pszText);CloseHandle(hMapping);CloseHandle(hFile);}说明:给map.txt文件1M的容量,尽管map.txt中只有起始位置有“hello”一个字符串,但是占有1M的空间。试想,如果把低32位参数dwFileOffsetLow由0改为64,思考会出现什么情况?通过运行可以发现有意想不到的情况,我们必须改为64*1024,那么hello会出现在什么地方。3打开内存映射文件函数HANDLEOpenFileMapping(DWORDdwDesiredAccess,//accessmode访问方式BOOLbInheritHandle,//inheritflag继承标识LPCTSTRlpName//objectname内存映射文件名称);写进程步骤:1、创建文件2、创建内存映射文件3、内存映射文件4、写内存-107- 5、卸载内存映射文件(销毁地址)6、销毁内存映射文件7、关闭文件读进程步骤:1、打开内存映射文件2、加载内存映射文件3、读内存4、卸载内存映射文件5、销毁内存映射文件(销毁地址)例:上面的例子已经有了一个写进程,那么请再开启另一个工程写出一个读进程。#include"stdafx.h"#include#include"windows.h"TCHARszName[]=__TEXT("hucong");//下划线可加可不加voidMapRead(){/*打开内存映射文件*/HANDLEhFile=OpenFileMapping(FILE_MAP_ALL_ACCESS,TRUE,"C:/map.txt");/*加载内存映射文件,并依据偏移量得到地址*/LPSTRpszText=(LPSTR)MapViewOfFile(hFile,FILE_MAP_ALL_ACCESS,0,64*1024,256);printf("%s ",pszText);/*卸载内存映射文件*/UnmapViewOfFile(pszText);CloseHandle(hFile);}四Windows进程1Windows进程进程是一个容器,包含程序执行需要的代码、数据、资源等等信息。Windows是多任务操作系统,可以同时执行多个进程。Windows进程的特点:1)每个进程都有自己的ID号:PID=0的进程占用CPU越多,说明CPU空闲越多;PID=4的进程为系统进程,操作系统不允许结束,即除非关机,否则结束不了。2)每个进程都有自己的地址空间,进程之间无法访问对方的地址空间。3)每个进程都有自己的安全属性。4)每个进程当中至少包含一个线程。-107- 2进程环境信息(简介即可,不需要写代码)2.1获取环境信息1)获取:得到进程上下文LPVOIDGetEnvironmentStrings(VOID);获取每段信息都是“”结束,接着就是第二条,然后又是‘’,再接着是第三条……如此到获取所有信息。但是,如果字符串遇到‘’就意味着结束,那么该怎么做?下面给出一个例子,思考为什么?#include"stdafx.h"#include"windows.h"#include"stdio.h"voidEnv(){LPSTRpszText=(LPSTR)GetEnvironmentStrings();LPSTRpszText1=pszText;while(pszText1[0]){printf("%s ",pszText1);pszText1=pszText1+strlen(pszText1)+1;//每次都跳过‘’}FreeEnvironmentStrings(pszText);}2)释放:BOOLFreeEnvironmentStrings(LPTSTRlpszEnvironmentBlock//environmentstrings);2.2获取和设置环境变量GetEnvironmentVariableSetEnvironmentVariable例:voidVaria(){SetEnvironmentVariable("hucong","good");CHARszText[256]={0};GetEnvironmentVariable("hucong",szText,256);printf("%s ",szText);//good}3进程的信息3.1得到当前进程的IDDWORDGetCurrentProcessId(VOID);3.2进程句柄HANDLEGetCurrentProcess(VOID);返回进程的伪句柄(值为-1),可以使用该句柄访问该进程的所有操作。例:-107- voidProcess(){DWORDnOld=GetCurrentProcessId();HANDLEnHandle=GetCurrentProcess();printf("ID=%d,HANDLE=%d ",nOld,nHandle);}其中一次的运行结果为:ID=3700,HANDLE=-1。其中ID每次都不可能一样,而HANDLE每次都是-1。4进程的使用4.1创建进程WinExec-早期16位ShellExecute-Shell操作CreateProcess-目前最多使用BOOLCreateProcess(LPCTSTRlpApplicationName,//应用程序名称LPTSTRlpCommandLine,//命令行参数LPSECURITY_ATTRIBUTESlpProcessAttributes,//进程安全属性SDLPSECURITY_ATTRIBUTESlpThreadAttributes,//线程安全属性SDBOOLbInheritHandles,//进程的句柄继承DWORDdwCreationFlags,//创建方式LPVOIDlpEnvironment,//环境信息LPCTSTRlpCurrentDirectory,//当前目录NULLLPSTARTUPINFOlpStartupInfo,//起始信息LPPROCESS_INFORMATIONlpProcessInformation//返回进程和线程的句柄ID);例:voidProc(){STARTUPINFOsi={0};si.cb=sizeof(si);PROCESS_INFORMATIONpi={0};CreateProcess("C:/WINDOWS/system32/notepad.exe"/*记事本程序*/,"",NULL,NULL,FALSE,0,NULL,NULL,&si,&pi);printf("ProcID=%d,ProcHandle=%d ",pi.dwProcessId,pi.hProcess);}调用这个函数就可以打开一个记事本,进程ID=180,ProcHandle=4048。4.2结束进程VOIDExitProcess(//不能结束经过双击打开的进程,只能自杀UINTuExitCode//exitcodeforallthreads退出码,可以任意);BOOLTerminateProcess(//可以杀别的HANDLEhProcess,//handletotheprocessUINTuExitCode//exitcodefortheprocess-107- );4.3打开进程其作用并不是打开进程,但是程序员可以通过进程ID拿到进程句柄。HANDLEOpenProcess(DWORDdwDesiredAccess,//访问权限BOOLbInheritHandle,//继承标识DWORDdwProcessId//进程ID);//返回进程句柄例:voidKill(){HANDLEhProc=OpenProcess(PROCESS_ALL_ACCESS,FALSE,180);TerminateProcess(hProc,100);}调用这个函数可以关闭上例所打开的一个记事本。4.4关闭进程句柄CloseHandle4.5进程间的等候等候可等候的句柄的信号。DWORDWaitForSingleObject(HANDLEhHandle,//句柄DWORDdwMilliseconds//指定等候的时间(INFINITE),毫秒为单位);阻塞函数,等候句柄的信号,只在句柄有信号或超出等候时间,才会结束等候。句柄存在有信号和没有信号两种属性,运行过程中句柄没有信号,运行结束句柄才会有信号。另外,前面所见过的句柄都是没有信号的。例:voidProc(){STARTUPINFOsi={0};si.cb=sizeof(si);PROCESS_INFORMATIONpi={0};CreateProcess("C:/WINDOWS/system32/notepad.exe"/*记事本程序*/,"",NULL,NULL,FALSE,0,NULL,NULL,&si,&pi);printf("ProcID=%d,ProcHandle=%d ",pi.dwProcessId,pi.hProcess);WaitForSingleObject(pi.hProcess,INFINITE);printf("WaitOver ");//关闭记事本后才会在控制台打印出来}五Windows线程1Windows线程Windows线程是可以执行的代码的实例。系统是以线程为单位调度程序。一个程序当中可以有多个线程,实现多任务的处理。-107- Windows线程的特点:1)每个线程都具有1个ID;2)线程具有自己的安全属性;3)每个线程都具有自己的内存栈;4)每个线程都具有自己的寄存器信息。进程多任务和线程多任务:进程多任务是每个进程都使用私有地址空间,线程多任务是进程内的多个线程使用同一个地址空间。线程的调度:将CPU的执行时间划分成时间片,依次根据时间片执行不同的线程。所有进程到CPU都是串行的。线程轮询:线程A->线程B->线程A......2线程的使用2.1定义线程处理函数DWORDWINAPIThreadProc(LPVOIDlpParameter//创建线程时,传递给线程的参数);2.2创建线程HANDLECreateThread(LPSECURITY_ATTRIBUTESlpThreadAttributes,//安全属性SIZE_TdwStackSize,//线程栈的大小,默认为1MLPTHREAD_START_ROUTINElpStartAddress,//线程处理函数的函数地址LPVOIDlpParameter,//传递给线程处理函数的参数DWORDdwCreationFlags,//线程的创建方式,LPDWORDlpThreadId//创建成功,返回线程的ID);//创建成功,返回线程句柄dwCreationFlags:0-创建之后线程立刻执行CREATE_SUSPENDED-创建之后线程处于挂起状态,调用ResumeThread函数。例:#include"stdafx.h"#include"windows.h"#include"stdio.h"DWORDWINAPITestProc1(LPVOIDpParam){LPSTRpszText=(LPSTR)pParam;while(1){printf("%s ",pszText);Sleep(1000);//1s}return0;}-107- DWORDWINAPITestProc2(LPVOIDpParam){LPSTRpszText=(LPSTR)pParam;while(1){printf("%s ",pszText);Sleep(2000);}return0;}voidThread(){DWORDnID=0;CHARszText1[]="hellothread1";HANDLEhThread1=CreateThread(NULL,0,TestProc1,szText1,CREATE_SUSPENDED,&nID);CHARszText2[]="hellothread2";HANDLEhThread2=CreateThread(NULL,0,TestProc2,szText2,0,&nID);getchar();}intmain(intargc,char*argv[]){Thread();return0;}如果没有getchar函数的话,控制台是不会有任何打印信息的,因为在main函数中主线程会结束。当把getchar函数放在main函数中的Thread之后,不会出现循环打印,为什么?因为一旦Thread函数执行结束,两个变量就销毁了,所以必须将之放到Thread函数中。2.3结束线程结束指定线程:BOOLTerminateThread(HANDLEhThread,//handletothreadDWORDdwExitCode//exitcode);结束函数所在的线程:VOIDExitThread(DWORDdwExitCode//exitcodeforthisthread);2.4关闭线程句柄,并非结束线程,而是将其句柄值置为-1CloseHandle2.5线程的挂起和执行挂起:DWORDSuspendThread(HANDLEhThread//handletothread);执行挂起的进程:-107- DWORDResumeThread(HANDLEhThread//handletothread);例:voidThread(){DWORDnID=0;CHARszText1[]="************";HANDLEhThread1=CreateThread(NULL,0,TestProc1,szText1,0,&nID);//创建并执行CHARszText2[]="-------------";HANDLEhThread2=CreateThread(NULL,0,TestProc2,szText2,CREATE_SUSPENDED,&nID);//创建并休眠getchar();SuspendThread(hThread1);//使TestProc1休眠ResumeThread(hThread2);//使TestProc2执行getchar();}2.6线程的信息GetCurrentThreadId-获取当前线程的IDGetCurrentThread-获取当前线程的句柄打开指定ID的线程,获取其句柄HANDLEOpenThread(DWORDdwDesiredAccess,//accessrightBOOLbInheritHandle,//handleinheritanceoptionDWORDdwThreadId//threadidentifier);例:#include"stdafx.h"#include"stdio.h"#include"windows.h"_declspec(thread)CHARg_szText[256]={0};DWORDWINAPITestProc1(LPVOIDpParam){strcpy(g_szText,(CHAR*)pParam);while(1){printf("%s ",g_szText);Sleep(1000);}return0;}DWORDWINAPITestProc2(LPVOIDpParam){strcpy(g_szText,(CHAR*)pParam);while(1){printf("%s ",g_szText);Sleep(2000);}-107- return0;}voidThread(){DWORDnID=0;HANDLEhThread[2]={0};CHARszText1[]="**********";hThread[0]=CreateThread(NULL,0,TestProc1,szText1,0,&nID);CHARszText2[]="----------";hThread[1]=CreateThread(NULL,0,TestProc2,szText2,0,&nID);getchar();CloseHandle(hThread[0]);CloseHandle(hThread[1]);}intmain(intargc,char*argv[]){Thread();return0;}如果在定义全局变量g_szText时没有加_declspec(thread),那么每次打印出来的都是线程2的执行结果,为什么?因为g_szText是全局变量,thread2每次后悔覆盖Thread1的。下面介绍几种其他函数:TlsAlloc–分配一个索引值DWORDTlsAlloc(VOID);TlsSetValue–向分配好的索引设置值BOOLTlsSetValue(DWORDdwTlsIndex,//TLSindex索引LPVOIDlpTlsValue//valuetostore要设置的值);TlsGetValue–向分配好的索引中,获取值LPVOIDTlsGetValue(DWORDdwTlsIndex//TLSindex索引);//返回值,得到索引内存储的值TlsFree–释放索引BOOLTlsFree(DWORDdwTlsIndex//TLSindex);下面的这段代码也可以实现上例中的结果:#include"stdafx.h"#include"stdio.h"#include"windows.h"CHAR*g_pszText=NULL;DWORDg_Index=0;DWORDWINAPITestProc1(LPVOIDpParam){-107- g_pszText=(LPSTR)malloc(100);memset(g_pszText,0,100);strcpy(g_pszText,(LPSTR)pParam);TlsSetValue(g_Index,g_pszText);while(1){LPSTRpszText=(LPSTR)TlsGetValue(g_Index);printf("%s ",pszText);Sleep(1000);}return0;}DWORDWINAPITestProc2(LPVOIDpParam){g_pszText=(LPSTR)malloc(100);memset(g_pszText,0,100);strcpy(g_pszText,(LPSTR)pParam);TlsSetValue(g_Index,g_pszText);while(1){LPSTRpszText=(LPSTR)TlsGetValue(g_Index);printf("%s ",pszText);Sleep(1000);}return0;}voidThread(){g_Index=TlsAlloc();DWORDnID=0;HANDLEhThread[2]={0};CHARszText1[]="*******";hThread[0]=CreateThread(NULL,0,TestProc1,szText1,0,&nID);CHARszText2[]="-------";hThread[1]=CreateThread(NULL,0,TestProc2,szText2,0,&nID);getchar();TlsFree(g_Index);CloseHandle(hThread[0]);CloseHandle(hThread[1]);}intmain(intargc,char*argv[]){Thread();return0;}-107- day13一线程的同步1多线程的问题线程A->线程B->线程A。。。。。当线程A执行printf输出时,如果线程A的执行时间结束,系统会将线程A的相关信息(栈、寄存器)压栈保护,同时将线程B相关信息恢复,然后执行线程B,线程B继续输出字符。由于线程A正输出字符,线程B会继续输出,所以画面字符会产生混乱。2线程同步技术1)原子锁2)临界区(段)3)事件4)互斥5)信号量6)可等候定时器3等候函数WaitForSingleObject-等候单个WaitForMultipleObjects-等候多个句柄DWORDWaitForMultipleObjects(DWORDnCount,//句柄数量CONSTHANDLE*lpHandles,//句柄BUFF的地址BOOLbWaitAll,//等候方式DWORDdwMilliseconds//等候时间);bWaitAll-等候方式:TRUE-表示所有句柄都有信号,才结束等候(通过阻塞)FASLE-表示句柄中只要有1个有信号,就结束等候(通过阻塞)。函数返回值:如果bWaitAll为TRUE说明所有被等候的句柄都有信号;如果bWaitAll为FALSE,返回有信号的句柄在数组中的下标。-107- 二原子锁1相关问题多个线程对同一个数据进行原子操作会产生结果丢失。分析下面的代码,运行后会得到什么结果。#include"stdafx.h"#include"windows.h"#include"stdio.h"DWORDg_nValue=0;DWORDWINAPITestProc1(LPVOIDpParam){for(inti=0;i<100000000;i++)g_nValue++;return0;}DWORDWINAPITestProc2(LPVOIDpParam){for(inti=0;i<100000000;i++)g_nValue++;return0;}voidThread(){DWORDnID=0;HANDLEhThread[2]={0};hThread[0]=CreateThread(NULL,0,TestProc1,NULL,0,&nID);hThread[1]=CreateThread(NULL,0,TestProc2,NULL,0,&nID);WaitForMultipleObjects(2,hThread,TRUE,INFINITE);printf("%d ",g_nValue);//如果能得到正确的结果,说明CPU好,即便是一样的CPU也会有不一样的结果CloseHandle(hThread[0]);CloseHandle(hThread[1]);}intmain(intargc,char*argv[]){Thread();return0;}是200000000吗?不是。每次的执行结果都是不一样的,如果循环次数低倒是可以得到预期的的结果。当线程A执行g_nValue1++时,如果线程(CPU)切换时间正好是在线程A将值保存到g_nValue1之前,线程B继续执行g_nValue1++,那么当线程A再次被切换回来之后,会将原来线程A保存的值保存到g_nValue1上,线程B进行的加法操作被覆盖。g_nValue1++;ecx=&g_nValue1;-107- ecx=ecx+1---------------------*g_nValue1=ecxg_nValue1++;在汇编中的代码如下:movecx,dwordptr[g_nValue1(00423ba8)]addecx,1movdwordptr[g_nValue1(00423ba8)],ecx2原子锁的使用2.1原子锁对单条指令的操作。2.2APIInterlockedIncrement//自加运算原子锁如果把上面代码中的g_nValue1++替换成InterlockedIncrement((LPLONG)&g_nValue);就可以得到正确的结果,但是运行速度太慢,因为线程间的排斥。InterlockedDecrement//对自减运算进行原子锁InterlockedCompareExchangeInterlockedExchange//第二个参数代替第一个参数(=)...原子锁的实现:直接对数据所在的内存操作,并且在任何一个瞬间只能有一个线程访问。加法举例InterlockedIncrement(&g_nValue2);ecx=&g_nValue2;---------------------lock(*ecx)+1movecx,dwordptr[esp+4]moveax,1lockxadddwordptr[ecx],eax三临界区1问题printf输出混乱,多线程情况下同时使用一段代码。-107- 2临界区锁定一段代码,防止多个线程同时使用该段代码3使用3.1初始化一个临界区VOIDInitializeCriticalSection(LPCRITICAL_SECTIONlpCriticalSection//临界区变量);3.2进入临界区添加到被锁定的代码之前VOIDEnterCriticalSection(LPCRITICAL_SECTIONlpCriticalSection//criticalsection);3.3离开临界区添加到被锁定的代码之后VOIDLeaveCriticalSection(LPCRITICAL_SECTIONlpCriticalSection//criticalsection);3.4删除临界区VOIDDeleteCriticalSection(LPCRITICAL_SECTIONlpCriticalSection//临界区变量);之前两个进程都打印的情况下都会乱,但是使用临界区之后就可以很好的避免上面的代码的不足,例如下面代码:#include"stdafx.h"#include"stdio.h"#include"windows.h"CRITICAL_SECTIONcs={0};DWORDWINAPITestProc1(LPVOIDpParam){while(1){EnterCriticalSection(&cs);printf("long--------------- ");Sleep(1000);LeaveCriticalSection(&cs);}return0;}DWORDWINAPITestProc2(LPVOIDpParam){while(1){EnterCriticalSection(&cs);printf("---------------long ");-107- Sleep(2000);LeaveCriticalSection(&cs);}return0;}voidCritical(){InitializeCriticalSection(&cs);//申请临界区DWORDnID=0;HANDLEhThread[]={0};hThread[0]=CreateThread(NULL,0,TestProc1,NULL,0,&nID);hThread[1]=CreateThread(NULL,0,TestProc2,NULL,0,&nID);WaitForMultipleObjects(2,hThread,TRUE,INFINITE);CloseHandle(hThread[0]);CloseHandle(hThread[1]);DeleteCriticalSection(&cs);}intmain(intargc,char*argv[]){Critical();return0;}4原子锁和临界区原子锁-单条指令。临界区-单条或多行代码。四事件1相关问题程序之间的通知的问题,即多线程之间有通知的作用。2事件的使用2.1创建事件HANDLECreateEvent(LPSECURITY_ATTRIBUTESlpEventAttributes,//安全属性NULLBOOLbManualReset,//事件重置(复位)方式,TRUE手动,FALSE自动BOOLbInitialState,//事件初始状态,如果为TRUE则说明创建前就有信号LPCTSTRlpName//事件命名,可以为NULL);//创建成功返回事件句柄-107- 2.2等候事件WaitForSingleObject/WaitForMultipleObjects2.3触发事件1)将事件设置成有信号状态BOOLSetEvent(HANDLEhEvent//handletoevent);2)将事件设置成无信号状态BOOLResetEvent(HANDLEhEvent//handletoevent);2.4关闭事件CloseHandle例:两个线程,一个输出,一个控制输出。方法:主线程中创建事件,并采用手动复位。#include"stdafx.h"#include"windows.h"#include"stdio.h"HANDLEg_hEvent=0;DWORDWINAPITestProc1(LPVOIDpParam){while(1){WaitForSingleObject(g_hEvent,INFINITE);//等候时间无限长printf("--------------- ");ResetEvent(g_hEvent);//如果选择自动复位,这句可以去掉}return0;}DWORDWINAPITestProc2(LPVOIDpParam){while(1){Sleep(1000);SetEvent(g_hEvent);}return0;}voidEvent(){g_hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);DWORDnID=0;HANDLEhThread[]={0};hThread[0]=CreateThread(NULL,0,TestProc1,NULL,0,&nID);hThread[1]=CreateThread(NULL,0,TestProc2,NULL,0,&nID);WaitForMultipleObjects(2,hThread,TRUE,INFINITE);CloseHandle(hThread[0]);CloseHandle(hThread[1]);CloseHandle(g_hEvent);-107- }intmain(intargc,char*argv[]){Event();return0;}小心事件的死锁。什么是死锁?如果线程1在等待事件1,并把事件2设置为信号状态,而线程2在等待事件1,并把事件2设置为信号状态,如此以来,线程1和线程2相互都不能满足彼此,此所谓之死锁。五互斥Mutex1相关的问题多线程下代码或资源的共享使用。2互斥的使用2.1创建互斥,一般用在主线程中HANDLECreateMutex(LPSECURITY_ATTRIBUTESlpMutexAttributes,//安全属性BOOLbInitialOwner,//初始的拥有者LPCTSTRlpName//命名);//创建成功返回互斥句柄bInitialOwner-初始的拥有者TRUE-调用CreateMutex的线程拥有互斥FALSE-创建的时没有线程拥有互斥2.2等候互斥WaitForSingleObject....互斥的等候遵循谁先等候谁先获取。2.3释放互斥BOOLReleaseMutex(HANDLEhMutex//handletomutex);2.4关闭互斥句柄CloseHandle例:#include"stdafx.h"#include"windows.h"#include"stdio.h"HANDLEg_hMutex=0;DWORDWINAPITestProc1(LPVOIDpParam){-107- while(1){WaitForSingleObject(g_hMutex,INFINITE);printf("--------------- ");Sleep(1000);ReleaseMutex(g_hMutex);}return0;}DWORDWINAPITestProc2(LPVOIDpParam){while(1){WaitForSingleObject(g_hMutex,INFINITE);printf("*************** ");Sleep(2000);ReleaseMutex(g_hMutex);}return0;}voidMutex(){g_hMutex=CreateMutex(NULL,FALSE,NULL);DWORDnID=0;HANDLEhThread[]={0};hThread[0]=CreateThread(NULL,0,TestProc1,NULL,0,&nID);hThread[1]=CreateThread(NULL,0,TestProc2,NULL,0,&nID);WaitForMultipleObjects(2,hThread,TRUE,INFINITE);CloseHandle(hThread[0]);//下面三句始终不会执行CloseHandle(hThread[1]);CloseHandle(g_hMutex);}intmain(intargc,char*argv[]){Mutex();return0;}说明:先执行线程1还是线程2是不确定的。2.5互斥和临界区的区别临界区-用户态,执行效率高,只能在同一个进程中使用。互斥-内核态,执行效率低,可以通过命名的方式跨进程使用。六信号量1相关的问题类似于事件,解决通知的相关问题。但是可以提供一个计数器,并且可以设置次数。-107- 2信号量的使用2.1创建信号量HANDLECreateSemaphore(LPSECURITY_ATTRIBUTESlpSemaphoreAttributes,//安全属性LONGlInitialCount,//初始化信号量数量LONGlMaximumCount,//信号量的最大值LPCTSTRlpName//信号量的命名,可以为NULL);//创建成功返回信号量句柄2.2等候信号量WaitFor...每等候通过一次,信号量的信号减1,直到为0阻塞。2.3释放信号量BOOLReleaseSemaphore(HANDLEhSemaphore,//信号量句柄LONGlReleaseCount,//释放数量,如果上面的信号量全都通过,那么再制定一个数字时,将再次进入等候信号量LPLONGlpPreviousCount//释放前原来信号量的数量,可以为NULL);2.4关闭句柄CloseHandle例:#include"stdafx.h"#include"stdio.h"#include"windows.h"HANDLEg_hSemap=0;DWORDWINAPITestProc(LPVOIDpParam){LONGnIndex=0;while(1){WaitForSingleObject(g_hSemap,INFINITE);printf("%d-******** ",nIndex);nIndex++;}return0;}voidSemaphore(){g_hSemap=CreateSemaphore(NULL,3,10,NULL);DWORDnID=0;HANDLEhThread=CreateThread(NULL,0,TestProc,NULL,0,&nID);/*每打一次回车就在输出7次,但不能超过最大值*/getchar();ReleaseSemaphore(g_hSemap,7,NULL);WaitForSingleObject(hThread,INFINITE);-107- CloseHandle(hThread);CloseHandle(g_hSemap);}intmain(intargc,char*argv[]){Semaphore();return0;}例:两个线程,当点击e或E按键时,两个线程执行结束;当点击0-9时,输出相应的次数。提示:利用信号量和事件,一个线程负责输出,另一个负责控制。另外,getch可以获取按键的字符。#include"stdafx.h"#include"windows.h"#include"stdio.h"#include"conio.h"HANDLEg_hEvent=0;HANDLEg_hSema=0;DWORDWINAPIInputProc(LPVOIDpParam){//控制//得到键盘按键字符-getch//如果是e或E将事件置为由信号,并return//如果是1-9重新设置信号量的值。while(1){CHARcInput=getch();if(cInput=='e'||cInput=='E'){SetEvent(g_hEvent);break;}intnCount=cInput-'0';if(nCount>0&&nCount<10){ReleaseSemaphore(g_hSema,nCount,NULL);}}return0;}DWORDWINAPIPrintProc(LPVOIDpParam){//输出//等候事件和信号量两个句柄等候方式FALSE//如果事件有信号,return//如果信号量有信号printfHANDLEhHandle[2]={0};hHandle[0]=g_hSema;hHandle[1]=g_hEvent;LONGnIndex=0;while(1){-107- DWORDnRet=WaitForMultipleObjects(2,hHandle,FALSE,INFINITE);if(nRet==WAIT_OBJECT_0){printf("%d-***** ",nIndex);nIndex++;}else{return0;}}return0;}voidTest(){g_hSema=CreateSemaphore(NULL,3,10,NULL);g_hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);DWORDnID=0;HANDLEhThread[2]={0};hThread[0]=CreateThread(NULL,0,InputProc,NULL,0,&nID);hThread[1]=CreateThread(NULL,0,PrintProc,NULL,0,&nID);WaitForMultipleObjects(2,hThread,TRUE,INFINITE);printf("执行完毕 ");CloseHandle(hThread[0]);CloseHandle(hThread[1]);CloseHandle(g_hEvent);CloseHandle(g_hSema);}intmain(intargc,char*argv[]){Test();return0;}七可等候定时器1相关问题定时器通知程序做某些操作,精度100ns为单位(只适用于第一次启动),以后的时间间隔仍以毫秒为单位。2使用1)创建定时器-107- HANDLECreateWaitableTimer(LPSECURITY_ATTRIBUTESlpTimerAttributes,//安全属性BOOLbManualReset,//True为手动,FALSE为自动LPCTSTRlpTimerName//objectname定时器命名,可以为NULL);//返回可等候定时器句柄2)设置定时器BOOLSetWaitableTimer(HANDLEhTimer,//handletotimerconstLARGE_INTEGER*pDueTime,//第一次启动时间,ns单位LONGlPeriod,//timerinterval间隔时间PTIMERAPCROUTINEpfnCompletionRoutine,//APC回调函数LPVOIDlpArgToCompletionRoutine,//APC回调函数参数BOOLfResume//resumestate待机唤醒标识);pDueTime(第一次启动时间):如果为正数,则应为具体时间(年月日时分秒);如果为负数,则为相对时间,即相对于系统时间往后推,如-10000000代表一秒以后启动。fResume(待机唤醒标识):如果为TRUE,将处于待机状态的计算机重新启动;如果为FALSE则不作任何处理。1)等候WaitFor……(时间未到句柄无信号,时间到了句柄有信号)2)关闭句柄CloseHandle注意:可等候定时器是NT4.0以后的版本才可以使用。例:#include"stdafx.h"#include"windows.h"#include"stdio.h"HANDLEg_hTimer=0;DWORDWINAPITestProc(LPVOIDpParam){while(1){WaitForSingleObject(g_hTimer,INFINITE);printf("********* ");}return0;}voidTimer(){g_hTimer=CreateWaitableTimer(NULL,FALSE,NULL);INT64nDueTime=-10000000;//64位整形SetWaitableTimer(g_hTimer,(LARGE_INTEGER*)&nDueTime,2000/*2s*/,NULL,NULL,FALSE);DWORDnID=0;HANDLEhThread=CreateThread(NULL,0,TestProc,NULL,0,&nID);WaitForSingleObject(hThread,INFINITE);-107- CloseHandle(hThread);CloseHandle(g_hTimer);}intmain(intargc,char*argv[]){Timer();return0;}直接编译会出错,所以需要修改NT版本,怎么修改在前面学习鼠标滚轮时就已经见识过了,即在StdAfx.h中添加语句:#define_WIN32_WINNT0x0500,将之提升到NT5.0版本。小结:进程/现程句柄–当运行时,句柄无信号,运行结束句柄有信号。互斥–谁先执行WaitFor……谁得到互斥,其他现程阻塞阻在WaitFor……事件–SetEvent/ResetEvent–WaitFor……等候事件有信号信号量–按照信号量的计数,通过WaitFor……可等候定时器–时间到了句柄有信号,时间未到句柄无信号-107-

当前文档最多预览五页,下载文档查看全文

此文档下载收益归作者所有

当前文档最多预览五页,下载文档查看全文
温馨提示:
1. 部分包含数学公式或PPT动画的文件,查看预览时可能会显示错乱或异常,文件下载后无此问题,请放心下载。
2. 本文档由用户上传,版权归属用户,天天文库负责整理代发布。如果您对本文档版权有争议请及时联系客服。
3. 下载前请仔细阅读文档内容,确认文档内容符合您的需求后进行下载,若出现内容与标题不符可向本站投诉处理。
4. 下载文档时可能由于网络波动等原因无法下载或下载错误,付费完成后未能成功下载的用户请联系客服处理。
关闭