WindowsAPI笔记(一)

句柄

在Windows应用程序中,窗口是通过窗口句柄(HWND)来标识的。我们要对某个窗口进行操作,首先就要得到这个窗口的句柄。句柄(HANDLE)是Windows程序中一个重要的概念,使用也非常频繁。在Windows程序中,有各种各样的资源(窗口、图标、光标等),系统在创建这些资源时会为它们分配内存,并返回标识这些资源的标识号,即句柄。在后面的内容中我们还会看到图标句柄(HICON)、光标句柄(HCURSOR)和画刷句柄(HBRUSH)。

消息

在Windows程序中,消息是由MSG结构体来表示的。MSG结构体的定义如下(参见MSDN):

typedef struct tagMSG {      
            HWND   hwnd;     
            UINT   message;
            WPARAM wParam;
            LPARAM lParam;
            DWORD  time;
            POINT  pt;
} MSG;

该结构体中各成员变量的含义如下:

第一个成员变量hwnd表示消息所属的窗口。我们通常开发的程序都是窗口应用程序,一个消息一般都是与某个窗口相关联的。例如,在某个活动窗口中按下鼠标左键,产生的按键消息就是发给该窗口的。在Windows程序中,用HWND类型的变量来标识窗口。

第二个成员变量message指定了消息的标识符。在Windows中,消息是由一个数值来表示的,不同的消息对应不同的数值。但是由于数值不便于记忆,所以Windows将消息对应的数值定义为WM_XXX宏(WM是Window Message的缩写)的形式,XXX对应某种消息的英文拼写的大写形式。例如,鼠标左键按下消息是WM_LBUTTONDOWN,键盘按下消息是WM_KEYDOWN,字符消息是WM_CHAR,等等。在程序中我们通常都是以WM_XXX宏的形式来使用消息的。

第三、第四个成员变量wParam和lParam,用于指定消息的附加信息。例如,当我们收到一个字符消息的时候,message成员变量的值就是WM_CHAR,但用户到底输入的是什么字符,那么就由wParam和lParam来说明。wParam、lParam表示的信息随消息的不同而不同。如果想知道这两个成员变量具体表示的信息,可以在MSDN中关于某个具体消息的说明文档查看到。读者可以在VC++的开发环境中通过goto definition查看一下WPARAM和LPARAM这两种类型的定义,可以发现这两种类型实际上就是unsigned int和long。

最后两个变量分别表示消息投递到消息队列中的时间和鼠标的当前位置。


WinMain函数

WinMain函数的原型声明如下:

int WINAPI WinMain(
    HINSTANCE hInstance,         // handle to current instance
    HINSTANCE hPrevInstance, // handle to previous instance
    LPSTR lpCmdLine,              // command line
    int nCmdShow                  // show state
);

WinMain函数接收4个参数,这些参数都是在系统调用WinMain函数时,传递给应用程序的。

第一个参数hInstance表示该程序当前运行的实例的句柄,这是一个数值。当程序在Windows下运行时,它唯一标识运行中的实例(注意,只有运行中的程序实例,才有实例句柄)。一个应用程序可以运行多个实例,每运行一个实例,系统都会给该实例分配一个句柄值,并通过hInstance参数传递给WinMain函数。

第二个参数hPrevInstance表示当前实例的前一个实例的句柄。通过查看MSDN我们可以知道,在Win32环境下,这个参数总是NULL,即在Win32环境下,这个参数不再起作用。

第三个参数lpCmdLine是一个以空终止的字符串,指定传递给应用程序的命令行参数。例如:在D盘下有一个sunxin.txt文件,当我们用鼠标双击这个文件时将启动记事本程序(notepad.exe),此时系统会将D:\sunxin.txt作为命令行参数传递给记事本程序的WinMain函数,记事本程序在得到这个文件的全路径名后,就在窗口中显示该文件的内容。要在VC++开发环境中向应用程序传递参数,可以单击菜单【Project】→【Settings】,选择“Debug”选项卡,在“Program arguments”编辑框中输入你想传递给应用程序的参数。

第四个参数nCmdShow指定程序的窗口应该如何显示,例如最大化、最小化、隐藏等。这个参数的值由该程序的调用者所指定,应用程序通常不需要去理会这个参数的值。


创建一个Win32应用程序的步骤

编写WinMain函数,可以在MSDN上查找并复制。

设计窗口类(WNDCLASS)。

注册窗口类。

创建窗口。

显示并更新窗口。

编写消息循环。

编写窗口过程函数。窗口过程函数的语法,可通过MSDN查看WNDCLASS的lpfnWndProc成员变量,在这个成员的解释中可查到。

设计一个窗口类

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

当窗口垂直方向上的高度发生变化时,将重新绘制整个窗口。如果没有指定这一样式,那么在垂直方向上调整窗口高度时,将不会重绘窗口。

 CS_NOCLOSE

禁用系统菜单的Close命令,这将导致窗口没有关闭按钮。

CS_DBLCLKS

当用户在窗口中双击鼠标时,向窗口过程发送鼠标双击消息。

第二个成员变量lpfnWndProc是一个函数指针,指向窗口过程函数,窗口过程函数是一个回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外一方调用的,用于对该事件或条件进行响应。回调函数实现的机制是:

(1)定义一个回调函数。

(2)提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者。

(3)当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。

针对Windows的消息处理机制,窗口过程函数被调用的过程如下:

(1)在设计窗口类的时候,将窗口过程函数的地址赋值给lpfnWndProc成员变量。

(2)调用RegsiterClass(&wndclass)注册窗口类,那么系统就有了我们所编写的窗口过程函数的地址。

(3)当应用程序接收到某一窗口的消息时,调用DispatchMessage(&msg)将消息回传给系统。系统则利用先前注册窗口类时得到的函数指针,调用窗口过程函数对消息进行处理。

一个Windows程序可以包含多个窗口过程函数,一个窗口过程总是与某一个特定的窗口类相关联(通过WNDCLASS结构体中的lpfnWndProc成员变量指定),基于该窗口过程。

WNDCLASS结构体第三个成员变量cbClsExtra:Windows为系统中的每一个窗口类管理一个WNDCLASS结构。在应用程序注册一个窗口类时,它可以让Windows系统为WNDCLASS结构分配和追加一定字节数的附加内存空间,这部分内存空间称为类附加内存,由属于这种窗口类的所有窗口所共享,类附加内存空间用于存储类的附加信息。Windows系统把这部分内存初始化为0。一般我们将这个参数设置为0。

第四个成员变量cbWndExtra:Windows系统为每一个窗口管理一个内部数据结构,在注册一个窗口类时,应用程序能够指定一定字节数的附加内存空间,称为窗口附加内存。在创建这类窗口时,Windows系统就为窗口的结构分配和追加指定数目的窗口附加内存空间,应用程序可用这部分内存存储窗口特有的数据。Windows系统把这部分内存初始化为0。如果应用程序用WNDCLASS结构注册对话框(用资源文件中的CLASS伪指令创建),必须给DLGWINDOWEXTRA设置这个成员。一般我们将这个参数设置为0。

第五个成员变量hInstance指定包含窗口过程的程序的实例句柄。

第六个成员变量hIcon指定窗口类的图标句柄。这个成员变量必须是一个图标资源的句柄,如果这个成员为NULL,那么系统将提供一个默认的图标。

在为hIcon变量赋值时,可以调用LoadIcon函数来加载一个图标资源,返回系统分配给该图标的句柄。该函数的原型声明如下所示:

HICON LoadIcon( HINSTANCE hInstance, LPCTSTR lpIconName)

LoadIcon函数不仅可以加载Windows系统提供的标准图标到内存中,还可以加载由用户自己制作的图标资源到内存中,并返回系统分配给该图标的句柄,请参看MSDN关于LoadIcon的解释。但要注意的是,如果加载的是系统的标准图标,那么第一个参数必须为NULL。

LoadIcon的第二个参数是LPCTSTR类型,利用goto definition命令将会发现它实际被定义成CONST CHAR*,即指向字符常量的指针,而图标的ID是一个整数。对于这种情况我们需要用MAKEINTRESOURCE宏把资源ID标识符转换为需要的LPCTSTR类型。

WNDCLASS结构体第七个成员变量hCursor指定窗口类的光标句柄。这个成员变量必须是一个光标资源的句柄,如果这个成员为NULL,那么无论何时鼠标进入到应用程序窗口中,应用程序都必须明确地设置光标的形状。

在为hCursor变量赋值时,可以调用LoadCursor函数来加载一个光标资源,返回系统分配给该光标的句柄。该函数的原型声明如下所示:

HCURSOR LoadCursor(HINSTANCE hInstance, LPCTSTR lpCursorName);

LoadCursor函数除了加载的是光标外,其使用方法与LoadIcon函数一样。

第八个成员变量hbrBackground指定窗口类的背景画刷句柄。当窗口发生重绘时,系统使用这里指定的画刷来擦除窗口的背景。我们既可以为hbrBackground成员指定一个画刷的句柄,也可以为其指定一个标准的系统颜色值。

第九个成员变量lpszMenuName是一个以空终止的字符串,指定菜单资源的名字。如果你使用菜单资源的ID号,那么需要用MAKEINTRESOURCE宏来进行转换。如果将lpszMenuName成员设置为NULL,那么基于这个窗口类创建的窗口将没有默认的菜单。要注意,菜单并不是一个窗口,很多初学者都误以为菜单是一个窗口。

第十个成员变量lpszClassName是一个以空终止的字符串,指定窗口类的名字。这和汽车的设计类似,设计一款新型号的汽车,需要给该型号的汽车取一个名字。同样的,设计了一种新类型的窗口,也要为该类型的窗口取个名字。

注册窗口类

设计完窗口类(WNDCLASS)后,需要调用RegisterClass函数对其进行注册,注册成功后,才可以创建该类型的窗口。

注册函数的原型声明如下:

ATOM RegisterClass(CONST WNDCLASS *lpWndClass); 

该函数只有一个参数,即上一步骤中所设计的窗口类对象的指针。

创建窗口 

创建窗口设计好窗口类并且将其成功注册之后,就可以用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指定窗口类的名称,即设计一个窗口类中为WNDCLASS的lpszClassName成员指定的名称,表示要产生这一类型的窗口。

产生窗口的过程是由操作系统完成的,如果在调用CreateWindow函数之前,没有用RegisterClass函数注册过名称的窗口类型,操作系统将无法得知这一类型窗口的相关信息,从而导致创建窗口失败。

 参数lpWindowName指定窗口的名字。如果窗口样式指定了标题栏,那么这里指定的窗口名字将显示在标题栏上。 

参数dwStyle指定创建的窗口的样式。就好像同一型号的汽车可以有不同的颜色一样,同一型号的窗口也可以有不同的外观样式。要注意区分WNDCLASS中的style成员与CreateWindow函数的dwStyle参数,前者是指定窗口类的样式,基于该窗口类创建的窗口都具有这些样式,后者是指定某个具体的窗口的样式。 在这里,我们可以给创建的窗口指定WS_OVERLAPPEDWINDOW这一类型,该类型的定义为:

 #define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED |  WS_CAPTION |  WS_SYSMENU | WS_THICKFRAME |  WS_MINIMIZEBOX | WS_MAXIMIZEBOX)

 可以看到,WS_OVERLAPPEDWINDOW是多种窗口类型的组合,其原理和前面知识点所讲的内容是一致的。

下面是这几种常用窗口类型的说明。 

WS_OVERLAPPED:产生一个层叠的窗口,一个层叠的窗口有一个标题栏和一个边框。

WS_CAPTION:创建一个有标题栏的窗口。

WS_SYSMENU:创建一个在标题栏上带有系统菜单的窗口,要和WS_CAPTION类型一起使用。 

WS_THICKFRAME:创建一个具有可调边框的窗口。 

WS_MINIMIZEBOX:创建一个具有最小化按钮的窗口,必须同时设定WS_ SYSMENU类型。 

WS_MAXIMIZEBOX:创建一个具有最大化按钮的窗口,必须同时设定WS_ SYSMENU类型。

CreateWindow函数的参数x,y,nWidth,nHeight分别指定窗口左上角的x,y坐标,窗口的宽度,高度。

如果参数x被设为CW_USEDEFAULT,那么系统为窗口选择默认的左上角坐标并忽略y参数。

如果参数nWidth被设为CW_USEDEFAULT,那么系统为窗口选择默认的宽度和高度,参数nHeight被忽略。

参数hWndParent指定被创建窗口的父窗口句柄。窗口之间可以有父子关系,子窗口必须具有WS_CHILD样式。对父窗口的操作同时也会影响到子窗口。

参数hMenu指定窗口菜单的句柄。 

参数hInstance指定窗口所属的应用程序实例的句柄。

 参数lpParam:作为WM_CREATE消息的附加参数lParam传入的数据指针。在创建多文档界面的客户窗口时,lpParam必须指向CLIENTCREATESTRUCT结构体。多数窗口将这个参数设置为NULL。 如果窗口创建成功,CreateWindow函数将返回系统为该窗口分配的句柄,否则,返回NULL。注意,在创建窗口之前应先定义一个窗口句柄变量来接收创建窗口之后返回的句柄值。 

显示窗口

窗口创建之后,我们要让它显示出来,这就跟汽车生产出来后要推向市场一样。

调用函数ShowWindow来显示窗口,该函数的原型声明如下所示: 

BOOL ShowWindow( HWND hWnd, // handle to window 
int nCmdShow // show state ); 

ShowWindow函数有两个参数

第一个参数hWnd就是在上一步骤中成功创建窗口后返回的那个窗口句柄;

第二个参数nCmdShow指定了窗口显示的状态,常用的有以下几种。 

SW_HIDE:隐藏窗口并激活其他窗口。 

SW_SHOW:在窗口原来的位置以原来的尺寸激活和显示窗口。 

SW_SHOWMAXIMIZED:激活窗口并将其最大化显示。 

SW_SHOWMINIMIZED:激活窗口并将其最小化显示。 

SW_SHOWNORMAL:激活并显示窗口。如果窗口是最小化或最大化的状态,系统将其恢复到原来的尺寸和大小。应用程序在第一次显示窗口的时候应该指定此标志。 

更新窗口

在调用ShowWindow函数之后,我们紧接着调用UpdateWindow来刷新窗口。

UpdateWindow函数的原型声明如下: 

BOOL UpdateWindow( HWND hWnd // handle to window );

其参数hWnd指的是创建成功后的窗口的句柄。

UpdateWindow函数通过发送一个WM_PAINT消息来刷新窗口,UpdateWindow将WM_PAINT消息直接发送给了窗口过程函数进行处理。到此,一个窗口就算创建完成了。

消息循环

在创建窗口、显示窗口、更新窗口后,我们需要编写一个消息循环,不断地从消息队列中取出消息,并进行响应。要从消息队列中取出消息,我们需要调用GetMessage()函数,该函数的原型声明如下:

BOOL GetMessage(
        LPMSG lpMsg,            // address of structure with message
        HWND hWnd,               // handle of window
        UINT wMsgFilterMin,     // first message
        UINT wMsgFilterMax      // last message
);

参数lpMsg指向一个消息(MSG)结构体,GetMessage从线程的消息队列中取出的消息信息将保存在该结构体对象中。

参数hWnd指定接收属于哪一个窗口的消息。通常我们将其设置为NULL,用于接收属于调用线程的所有窗口的窗口消息。

参数wMsgFilterMin指定要获取的消息的最小值,通常设置为0。

参数wMsgFilterMax指定要获取的消息的最大值。如果wMsgFilterMin和wMsgFilter Max都设置为0,则接收所有消息。

GetMessage函数接收到除WM_QUIT外的消息均返回非零值。对于WM_QUIT消息,该函数返回零。如果出现了错误,该函数返回-1,例如,当参数hWnd是无效的窗口句柄或lpMsg是无效的指针时。


posted on 2013-02-12 21:43  电子幼体  阅读(396)  评论(0编辑  收藏  举报

导航