VC 笔记

Windows程序基于API函数和事件驱动,通过窗口句柄标识窗口,消息队列处理消息。窗口创建涉及窗口类设计、注册、创建及显示。本文详细介绍了窗口类、窗口创建、消息处理机制、WinMain函数、线程概念、线程创建函数以及线程同步方法,包括互斥对象、事件对象和关键代码段。同时讨论了多线程中的死锁问题和确保应用程序单实例运行的策略。
摘要由CSDN通过智能技术生成

 Windows程序内部运行机制
1、API与SDK
Windows操作系统提供了各种各样的函数,以方便我们开发Windows应用程序,这些函数是Windows操作系统提供给应用程序编程的接口(Application Programming Interface),简称为API函数。我们在编写Windows程序时所说的API函数,就是指系统提供的函数,所有主要的Windows函数都在Window.h头文件中进行了说明
微软提供的API函数大多是有意义的单词组合,每个单词的首字母大写
MSDN是微软为开发人员提供的一套帮助系统,其中包含大量的开发文档、技术文章和示例代码
SDK的全称是Software Development Kit,中文译为软件开发包。SDK实际上就是开发所需资源的一个集合,包括API函数库、帮助文档、使用手册、辅助工具等资源
2、窗口与句柄
窗口是Windows应用程序中一个非常重要的元素,一个Windows应用程序至少要有一个窗口,称为主窗口。窗口是屏幕上的一块矩形区域,是Windows应用程序与用户进行交互的接口。利用窗口,可以接受用户的输入,以及显示输出
一个应用程序窗口通常包括标题栏、菜单栏、系统菜单、最小化框、最大化框、可调边框,有的还有滚动条
窗口可以分为客户区和非客户区。客户区是窗口的一部分,应用程序通常在客户区中显示文字或者绘制图形。标题栏、菜单栏、系统菜单、最小化框、最大化框、可调边框统称为窗口的非客户区,它们由Windows系统来管理,而应用程序则主要管理客户区的外观及操作
在Windows应用程序中,窗口是通过窗口句柄(HWND)来标识的。我们要对某个窗口进行操作,首先就要得到这个窗口的句柄。在Windows程序中,有各种各样的资源(窗口,图标、光标等),系统在创建这些资源时会为它们分配内存,并返回标识这些资源的标识号,即句柄。
3、消息与消息队列
在Windows中,不仅用户程序可以调用系统的API函数,反过来,系统也会调用用户程序,这个调用是通过消息队列来进行的
Windows程序设计是一种基于事件驱动方式的程序设计模式,主要是基于消息的。用户与应用程序交互时,操作系统感知事件,将事件包装成一个消息,投递到应用程序的消息队列中,然后应用程序从消息队列中取出消息并进行响应。在这个处理过程中,操作系统也会给应用程序“发送消息”。所谓“发送消息”,实际上是操作系统调用程序中一个专门负责处理消息的函数,这个函数称为窗口过程
1、消息
在Windows程序中,消息是由MSG结构体定义的。MSG结构体的定义如下:
typedef struct tagMSG {     // msg 
    HWND   hwnd;     
    UINT   message;
    WPARAM wParam;
    LPARAM lParam;
    DWORD  time;
    POINT  pt;
} MSG;
第一个成员变量hwnd表示消息所属的窗口。我们通常开发的程序都是窗口应用程序,一个消息一般都是与某个窗口相关联的。例如,在某个活动窗口中按下鼠标左键,产生的按键消息就是发给该窗口的。在Windows程序中,用HWND类型的变量来标识窗口
第二个成员变量message指定了消息的标识符。在Windows中,消息是由一个数值来表示的,不同的消息对应不同的数值。但是由于数值不便于记忆,所以Windows将消息对应的数值定义为WM_XXX宏的形式,WM是Windows Message的缩写,XXX对应某种消息的英文拼写的大写形式。在程序中,我们通常都是以WM_XXX宏的形式来使用消息的
第三、第四个成员变量wParam和lParam,用于指定消息的附加信息。wParam、lParam表示的信息随消息的不同而不同,如果想知道这两个成员变量具体表示的信息,可以在MSDN中关于某个具体消息的说明文档查看到。在VC++开发环境下通过goto definition查看WPARAM和LPARAM这两种类型的定义,可以发现这两种类型实际上就是unsigned int和long
最后两个变量分别表示消息投递到消息队列中的时间和鼠标的当前位置
2、消息队列
每一个Windows应用程序开始执行后,系统都会为该程序创建一个消息队列,这个消息队列用来存放该程序创建的窗口的消息。Windows将产生的消息依次放到消息队列中,而应用程序则通过一个消息循环不断地从消息队列中取出消息,并进行响应。这种消息机制,就是Windows程序运行的机制
3、进队消息和不进队消息
Windows程序中的消息可以分为“进队消息”和“不进队消息”。进队的消息将由系统放入到应用程序的消息队列中,然后由应用程序取出并发送。不进队的消息在系统调用窗口过程时,直接发送给窗口。不管是进队消息还是不进队消息,最终都由系统调用窗口过程函数对消息进行处理
4、WinMain函数
当Windows操作系统启动一个程序时,调用该程序的WinMain函数(实际是由插入到可执行文件中的启动代码调用的)。WinMain是Windows程序的入口点函数,与DOC程序的入口点函数main的作用相同,当WinMain函数结束或返回时,Windows应用程序结束
编写一个完整的Win32程序,该程序实现的功能是创建一个窗口,并在窗口中响应键盘及鼠标消息,程序实现的步骤为:
WinMain函数的定义
创建一个窗口
进行消息循环
编写窗口过程函数
4.1、WinMain函数的定义
WinMain函数的原形声明如下:
int WINAPI WinMain(
  HINSTANCE hInstance,  // handle to current instance
  HINSTANCE hPrevInstance,  // handle to previous instance
  LPSTR lpCmdLine,      // pointer to command line
  int nCmdShow          // show state of window
);
WinMain函数接收4个参数,这些参数都是在系统调用WinMain函数时,传递给应用程序的
第一个参数hInstance表示该程序当前运行的实例的句柄,这是一个数值。当程序在Windows下运行时,它唯一标识运行中的实例(注意,只有运行中的程序实例,才有实例句柄)。一个应用程序可以运行多个实例,每运行一个实例,系统都会给该实例分配一个句柄值,并通过hInstance参数传递给WinMain函数
第二个参数hPrevInstance表示当前实例的前一个实例的句柄,在Win32环境下,这个参数总是NULL,即在Win32环境下,这个参数不再起作用
第三个参数lpCmdLine是一个以空终止的字符串,指定传递给应用程序的命令行参数。要在VC++开发环境中向应用程序传递参数,可以单击菜单Project-Setting,选择“Debug”选项卡,在“Program arguments”编辑框中输入你想传递给应用程序的参数
第四个参数nCmdShow指定程序的窗口应该如何显示,例如最大化、隐藏等。这个参数的值由该程序的调用者所指定,应用程序通常不需要去理会这个参数的值
关于WinMain函数前的修饰符WINAPI,其实就是_stdcall
4.2、窗口的创建
创建一个完整的窗口,需要经过下面几个操作步骤:
设计一个窗口类
注册窗口类
创建窗口
显示及更新窗口
1、设计一个窗口类
一个完整的窗口具有许多特征,包括光标、图标、背景色等。在创建一个窗口前,必须对该类型的窗口进行设计,指定窗口的特征。Windows提供了WNDCLASS结构体来定义窗口特征,该结构体定义好一个窗口所具有的基本属性。WNDCLASS结构体的定义如下:
typedef struct _WNDCLASS {
    UINT    style;
    WNDPROC lpfnWndProc;
    int     cbClsExtra;
    int     cbWndExtra;
    HANDLE  hInstance;
    HICON   hIcon;
    HCURSOR hCursor;
    HBRUSH  hbrBackground;
    LPCTSTR lpszMenuName;
    LPCTSTR lpszClassName;
} WNDCLASS;
第一个成员变量style指定了这一类型窗口的样式,常用的样式:
CS_HREDRAW
当窗口水平方向上的宽度发生变化时,将重新绘制整个窗口。当窗口发生重绘时,窗口中的文字和图形将被擦除。如果没有指定这一样式,那么在水平方向上调整窗口的宽度时,将不会重绘窗口
CS_VREDRAW
当窗口垂直方向上的宽度发生变化时,将重新绘制整个窗口。如果没有指定这一样式,那么在垂直方向上调整窗口的宽度时,将不会重绘窗口
知识点:在Windows.h中,以CS_开头的类样式(Class Style)标识符被定义为16位的类常量,这些常量都只有某一位为1。用这种方式定义的标识符称为“位标志”,我们可以使用位运算操作符来组合使用这些样式
第二个成员变量lpfnWndProc是一个函数指针,指向窗口过程函数,窗口过程函数是一个回调函数,回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用的,用于对该事件或条件进行响应。回调函数的实现机制是:
1、定义一个回调函数
2、提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者
3、当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理
针对Windows的消息处理机制,窗口过程函数被调用的过程如下:
1、在设计窗口类的时候,将窗口过程函数的地址付给lpfnWndProc成员变量
2、调用RegisterClass(&wndclass)注册窗口类,那么系统就有了我们所编写的窗口过程函数的地址
3、当应用程序接收到某一窗口的消息时,调用DispatchMessage(&msg)将消息回传给系统,系统则利用先前注册窗口类时得到的函数指针,调用窗口过程函数对消息进行处理
一个Windows程序可以包含多个窗口过程函数,一个窗口过程总是与某一特定的窗口类相关联,基于该窗口类创建的窗口使用同一个窗口过程
lpfnWndProc成员变量的类型是WNDPROC,在VC++开发环境中使用goto definition功能,可以看到WNDPROC的定义:
typedef LRESULT (CALLBACK * WNDPROC)(HWND,UINT,WPARAM,LPARAM);
LRESULT和CALLBACK实际上是long和_stdcall
从WNDPROC的定义可以知道,WNDPROC实际上是函数指针类型
注意:WNDPROC被定义为指向窗口过程函数的指针类型,窗口过程函数的格式必须与WNDPROC相同
知识点:在函数调用过程中,会使用栈。_stdcall与_cdecl是两种不同的函数调用约定,定义了函数参数入栈的顺序,由调用函数还是被调用函数将参数弹出栈,以及产生函数修饰名的方法。
第三个成员变量cbClsExtra:Windows为系统中的每一窗口类管理一个WNDCLASS结构。在应用程序注册一个窗口类时,它可以让Windows系统为WNDCLASS结构分配和追加一定字节数的附加内存空间,这部分内存空间称为类附加内存,由属于这种窗口类的所有窗口所共享,类附加内存空间用于存储类的附加信息。Windows系统把这部分内存初始化为0。一般我们将这个参数设置为0
第四个成员变量cbWndExtra:Windows系统为每一个窗口管理一个内部数据结构,在注册一个窗口类时,应用程序能够指定一定字节数的附加内存空间,称为窗口附加内存。在创建这类窗口时,Windows系统就为窗口的结构分配和追加指定数目的附加内存空间,应用程序可用这部分内存存储窗口特有的数据。Windows系统把这部分内存初始化为0。一般我们将这个参数设置为0
第五个成员变量hInstance指定包含窗口过程的程序的实例句柄
第六个成员变量hIcon指定窗口类的图标句柄,这个成员变量必须是一个图标资源的句柄,如果这个成员为NULL,那么系统将提供一个默认的图标
在为hIcon变量赋值时,可以调用LoadIcon函数加载一个图标资源,返回系统分配给该图标的句柄。
HICON LoadIcon(
  HINSTANCE hInstance, // handle to application instance
  LPCTSTR lpIconName   // icon-name string or icon resource
                       // identifier
);
LoadIcon的第二个参数是LPCTSTR类型,实际被定义为CONST CHAR *,即指向字符常量的指针,而图标的ID是一个整数。对于这种情况,我们需要MAKEINTERSOURCE宏把资源ID标识符转换为需要的LPCTSTR类型
知识点:在VC++中,对于自定义的菜单、图标、光标、对话框等资源,都保存在资源脚本(通常扩展名为.rc)文件中。在VC++开发环境中,要访问资源文件,可以单击左边项目视图窗口底部的ResourceView选项卡,你将看到以树状列表形式显示的资源项目。在任何一种资源上双击鼠标左键,将打开资源编辑器。在资源编辑器中,以“所见即所得”的方式对资源进行编辑。资源文件本身是文本文件格式,如果了解资源文件的编写格式,也可以直接使用文本编辑器对资源进行编辑
在VC++中,资源是通过标识符(ID)来标识的,同一个ID可以标识多个不同的资源。资源的ID实质上是一个整数,在“resource.h”中定义为一个宏。我们在为资源指定ID的时候,应该养成一个良好的习惯,即在“ID”后附加特定资源英文名称的首字母,例如,菜单资源为IDM_XXX,图标资源为:IDI_XXX。采用这种方式时,我们在程序中使用资源ID时,可以一目了然
WNDCLASS结构体第七个成员变量hCursor指定窗口类的光标句柄,这个成员变量必须是一个光标资源的句柄
第八个成员变量hbrBackground指定窗口类的背景画刷句柄。当窗口发生重绘时,系统使用这里指定的画刷来擦除窗口的背景。我们可以调用GetStockObject函数来得到系统的标准画刷,GetStockObject函数不仅可以用于获取画刷句柄,还可以用于获取画笔、字体和调色板的句柄
第九个成员变量lpszMenuName是一个以空终止的字符串,制定菜单资源的名字。如果使用菜单资源的ID号,那么需要用MAKEINTERSOURCE宏来进行转换。
要注意,菜单并不是一个窗口
第十个成员变量lpszClassName是一个以空终止的字符串,指定窗口类的名字
2、注册窗口类
设计完窗口类(WNDCLASS)后,需要调用RegisterClass函数对其进行注册,注册成功后,才可以创建该类型的窗口。注册函数的原型声明如下:
ATOM RegisterClass(
  CONST WNDCLASS *lpWndClass   // address of structure with class
                               // data
);
3、创建窗口
设计好窗口类并且将其注册成功之后,就可以用CreateWindow函数产生这种类型的窗口了。CreateWindow函数的原型声明如下:
HWND CreateWindow(
  LPCTSTR lpClassName,  // pointer to registered class name
  LPCTSTR lpWindowName, // pointer to window name
  DWORD dwStyle,        // window style
  int x,                // horizontal position of window
  int y,                // vertical position of window
  int nWidth,           // window width
  int nHeight,          // window height
  HWND hWndParent,      // handle to parent or owner window
  HMENU hMenu,          // handle to menu or child-window identifier
  HANDLE hInstance,     // handle to application instance
  LPVOID lpParam        // pointer to window-creation data
);
参数lpClassName指定窗口类的名称。产生窗口的过程是由操作系统完成的,如果再调用CreateWindow函数之前,没有用RegisterClass函数注册过窗口类,操作系统将无法得知这一类型窗口的相关信息,从而导致创建窗口失败
参数lpWindowName指定窗口的名字。如果窗口样式指定了标题栏,那么这里指定的窗口名字将显示在标题栏上
参数dwStyle指定创建的窗口的样式,要注意区分WNDCLASS中的style成员和CreateWindow函数的dwStyle参数,前者指定窗口类的样式,基于该窗口类创建的窗口都有这些格式,后者是指定某个具体的窗口的样式。下面是几种常用的窗口类型的说明:
WS_OVERLAPPED:产生一个层叠的窗口,一个层叠的窗口有一个标题栏和一个边框
WS_CAPTION:创建一个有标题栏的窗口
WS_SYSMENU:创建一个在标题栏上带有系统菜单的窗口
WS_THICKFRAME:创建一个具有可调边框的窗口
CreateWindow函数的参数x,y,nWidthn,Hight分别指定窗口左上角的x,y坐标,窗口的宽度,高度
参数hWndParent指定被创建窗口的父窗口句柄。窗口之间有父子关系,子窗口必须具有WS_CHILD样式
参数hMenu指定窗口菜单的句柄
参数hInstance:指定窗口所属的应用程序实例的句柄
参数lpParam:作为WM_CREATE消息的附加参数lParam传入的数据指针。在创建多文档界面的客户窗口时,lpParam必须指向CLIENTCREATESTRUCT结构体
如果窗口创建成功,CreateWindow函数将返回系统为该窗口分配的句柄,否则返回NULL。注意,在创建窗口之前应先定义一个窗口句柄变量来接受创建窗口之后返回的句柄值
4、显示及更新窗口
显示窗口
窗口创建之后,调用函数ShowWindow来显示窗口,该函数的原型声明如下:
BOOL ShowWindow(
  HWND hWnd,     // handle to window
  int nCmdShow   // show state of window
);
更新窗口
在调用ShowWindow函数之后,紧接着调用UpdateWindow来刷新窗口,该函数的原型声明如下:
BOOL UpdateWindow(
  HWND hWnd   // handle of window
);
UpdateWindow函数通过发送一个WM_PAINT消息来刷新窗口,UpdateWindow将WM_PAINT消息直接发送给了窗口过程函数进行处理,而没有放到消息队列中,请注意这一点
4.3、消息循环
在创建窗口、显示窗口、更新窗口后,需要编写一个消息循环,不断地从消息队列中取出消息,并进行响应。要从消息队列中取出消息,需要调用GetMessage()函数,该函数的原型声明如下:
BOOL GetMessage(
  LPMSG lpMsg,         // address of structure with message
  HWND hWnd,           // handle of window
  UINT wMsgFilterMin,  // first message
  UINT wMsgFilterMax   // last message
);
参数lpMsg指向一个消息结构体,GetMessage从线程的消息队列中取出的消息信息将保存在该结构体对象中
参数hWnd指定接收属于哪一个窗口的消息。通常将其设置为NULL,用于接收属于调用线程的所有窗口的窗口消息
参数wMsgFilterMin指定要获取的消息的最小值,通常设置为0
参数wMsgFilterMax指定要获取的消息的最大值。如果wMsgFilterMin和wMsgFilterMax都设置为0,则接受所有消息
GetMessage函数接收到除了WM_QIUT外的消息均返回非零值。对于WM_QIUT消息,该函数返回零。如果出现了错误,该函数返回-1
编写的消息循环代码如下:
MSG msg;
 whi

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值