《Windows程序设计》笔记之一——第一个Windows程序以及Windows程序原理

1.API和内存模式:

       API包含了所有应用程序构造操作系统的函数调用,也包含了相关的数据类型和结构。

       它形成了一个一个特殊的程序体系结构

       Windows1.0到3.0中使用分段内存模式,在分段内存模式下,内存地址分为两部分—— 一个16位段指针和一个16为偏移指针。其中带来了long或far指针(包括段地址和偏移量地址)和short或near指针(包括带有假定段地址的偏移量地址)的区别。

       WindowsNT和Windows 95开始支持使用32位模式的32位平面内存模式。32位版本Windows编写的程序使用在平面线性空间寻址的简单的32位指针。

       Win32操作系统都支持Win16 API调用,但是NT和95,98的工作方式不同,NT中Win16函数调用通过一个转换层被转化为Win32函数调用,然后被操作系统处理。95和98中Win32函数调用通过转换层转换为Win16函数调用,再由操作系统处理。

API文档:MSDN提供了所有API的文档。Windows大致分为Kernel、User和GDI子系统,内核接口在/Platform/SDK/WindowsBase Services中,用户接口函数在/PlatformSDK/User Iterface Services中,GDI位于/PlatformSDK/Graphics and Multimedia Services/GDI中。

2.编写第一个Windows程序:

#include <windows.h>

int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,

PSTR szCmdLine, int iCmdShow)

{

       MessageBox(NULL,TEXT("Hello, Windows 98!"), TEXT("HelloMSG"), 0);

       return0;

}

Windows程序都是以#include <Windows.h>开始,另外一些比较重要的基本的头文件包括:

       Windef.h基本类型定义

       WinNT.h支持Unicode的类型定义

       WinBase.h内核函数

       WinUser.h用户接口函数

       WinGDI.h图形设备接口函数

Windows程序入口点是WinMain,以如下的形式出现:

       intWINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)

       其中的WINAPI被定义为__stdcall,用于指定函数调用中在堆栈中放置函数调用参数的方式。第一个参数为实例句柄,Windows编程中句柄是一个应用程序用来识别某些事物的数字。句柄唯一标识该程序,其他的Windows函数需要用该句柄作为参数调用该程序。在Win32中废除了第二个参数,设置为NULL。第三个参数为用于运行程序的命令行,使用这个参数在程序启动时将文件加载到内存。第四个参数指出程序最初显示的方式,正常地或是最大化充满窗口,或是最小化显示在任务栏中。

       程序生成过程:

       CSource --> [Compiler] --> .obj --> [Linker] --> .exe

                                                         + .lib

3. Unicode简介

       Unicode是ASCII字符编码的一个扩展,严格的ASCII码中字符用7位表示,计算机上普遍使用8位表示,Unicode使用全16位字符编码。使得Unicode能够表示世界上所有的书写语言中可能用于计算机通讯的字符,象形文字和其他的符号。

       对于ASCII码的扩展,使用8位表示一个字符,甚至是扩展到双字符集,都不能很好解决对所有字符编码的问题,双字符集问题不在于用两个字节表示字符,而在于一些字符是使用一个字节表示,另外的一些字符是使用两个字节表示。这样在解析字符串时要确定是单字节字符还是双字节字符。

       Unicode是完全用两个字节表示一个字符,允许表示65535个字符,这就足以表示世界上的所有语言的文字,字符以及数学,符号和货币集合      

宽字符:Unicode为宽字符的一种编码形式。

       C中的宽字符基于wchar_t数据类型,几个头文件(WCHAR.H)中都有定义:

       typedefunsigned short wchar_t;

       wchar_t数据类型与无符号短整型相同,都是16位宽。wchar_t c = ‘A’; 由于Unicode字符是两个字节,因此在保存宽字符文件时应该注意,CPU的字节序不同可能会出现问题。定义宽字符串指针 wchar_t *p =L”Hello!”; 其中的L表示字符串按照宽字符保存,每个字符占用两个字节。末尾的结束符0也需要两个字节。

宽字符库函数:

       直接将传统的字符串函数用于宽字符串就会出现问题,无法通过编译。各个字符串函数都提供了宽字符版本,strlen的宽字符版本为wcslen(),声明在string.h和wchar.h中都有声明。改成宽字节后,字符串的字符长度并不改变,只有所占用的字节长度改变。

       Unicode也有缺点:程序中的每个字符串都将占用两倍的存储空间,再者宽字符运行库中的函数比常规的函数大。这样可能想要建立两个版本的程序——一个处理ASCII码字符串,一个处理Unicode字符串。对此问题的解决办法是维护既能按照ASCII编译又能按照Unicode编译的单一源代码文件。

       维护单一源代码文件的方法是使用TCHAR.H头文件,该文件不是ANSI C标准的一部分,定义的每个函数和宏定义的前面都有一条下划线。TCHAR.H为需要字符串参数的标准运行库函数提供了一些列的替代名称,如_tprintf和_tsclen,这些名称被称为“通用”函数名,既可以指向函数的Unicode版,也可以指向非Unicode版。

       例如:

       如果定义了名为_UNICODE的标识符,并且程序中包含了TCHAR.H头文件,那么_tcslen就定义为wcslen:

       #define_tcslen wcslen

       如果没有定义UNICODE,则_tcslen定义为strlen:

       #define_tcslen strlen

      

       且对于TCHAR类型,定义了_UNICODE标识符,那么TCHAR就是wchar_t,否则TCHAR被定义为char。

       typedefwchar_t TCHAR 或者 typedefchar TCHAR

       对于字符串中的L问题。如果定义了_UNICODE标识符,那么就有一个__T的宏定义如下:

       #define__T(x) L##x

       这种语法比较晦涩,属于C预处理的ANSI C标准。一对##称为粘贴符号,将字母L添加到宏参数上。

       如果没有_UNICODE定义,那么 __T被定义为 #define __T(x) x

       #define_T(x) __T(x)

       #define_TEXT(x) __T(x)

       可以根据喜好确定使用那个宏定义。

4. Windows头文件类型

       WINDEF.H中包含了Windows中使用的基本类型定义,本身也包括WINNT.H,WINNT.H中处理基本的Unicode支持。

       WINNT.H包含了C的头文件CTYPE.H,C的众多头文件之一,包含了wchar_t的定义。WINNT.H定义了新的数据类型,称为CHAR和WCHAR

       typedefchar CHAR

       typedefwchar_t WCHAR

       建议在Windows程序中使用数据类型CHAR和WCHAR,一个基于WCHAR数据类型的变量可以在前面附加上字母wc以说明是一个宽字符。

       WINNT.H中定义了可用作8位字符串指针的6中数据类型和4个可用作const 8位字符串指针的数据类型:

       typedefCHAR  * PCHAR, *LPCH, *PCH, *NPSTR,*LPSTR, *PSTR;

       typedefCONST CHAR  * LPCCH, *PCCH, *LPCSTR,*PCSTR;

       N和 L表示near和long,16位的Windows中两种大小不同的指针,在Win32中near和long指针没有区别。

       typedefCHAR  * PWCHAR, *LPWCH, *PWCH, *NWPSTR,*LPWSTR, *PWSTR;

       typedefCONST CHAR  * LPCWCH, *PCWCH, *LPCWSTR,*PCWSTR;

       WINNT.H将TCHAR定义为一般的字符类型,如果定义了标识符UNICODE,则TCHAR和指向TCHAR的指针就分别定义为WCHAR和指向WCHAR的指针。

       #ifdefUNICODE

              typedefWCHAR TCHAR, *PTCHAR

              typedefLPWSTR LPTCH, PTSTR, LPTSTR;

              typedefLPCWSTR LPCTSTR;

       #else

              typedefchar TCHAR, *PTCHAR

              typedefLPSTR LPTCH, PTSTR, LPTSTR;

              typedefLPCSTR LPCTSTR;

       #endif

       无论何时再程序中使用其他头文件,都应在所有其他头文件之前包含Windows.h

       Windows函数调用类似与字符串的设置,需要支持Unicode字符USER32.DLL中对每一个函数都定义了两个入口点:一个名为MessageBoxA(ASCII版),另外一个名为MessageBoxW(宽字符版)。

       WINUSER.H中对MessgeBox的两个入口点的定义不同,但是类似:

       WINUSERAPIint WINAPI MessageBoxA( HWND hWnd, LPCSTR lpText, LPCSTR lpCaptioin, UINT uType);

       WINUSERAPIint WINAPI MessageBoxW( HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaptioin, UINT uType);

      

Windows定义的一组字符串函数,这些函数用来计算字符串长度,复制字符串,连接字符串和比较字符串:

       lLength= lenstr( pString);

       pString= lstrcpy( pString1, pString2);

       pString= lstrcpyn( pString1, pString2, iCount);

       pString= lstrcat( pString1, pString2);

       pString= lstrcmp( pString1, pString2);

       pString= lstrcmpi( pString1, pString2);          

5. 变长参数的处理:

       在Windows编程中不能使用printf函数,可以使用fprintf和sprintf,sprintf以及本系列的函数将内容格式化输出到第一个参数所提供的字符串缓冲区,其他的功能和printf相同。

       intsprintf(char * szBuffer, const char * szFormat, …);

       第一个参数是字符缓冲区,第二个参数为格式字符串。函数返回值为格式化后的字符串长度,也即szBuffer中的字符串长度。

       在使用sprintf时,有一个负担:定义的字符串缓冲区必须足够大,以存放结果。Microsoft的专用函数_snprintf解决了这个问题,它有另外一个参数,以字符数给出缓冲区大小。

#include<Windows.h>
#include<tchar.h>
#include<stdio.h> 
int CDECL MessageBoxPrintf(TCHAR * szCaption,TCHAR * szFormat,...)
{
    TCHAR szBuffer[1024];
    va_list pArgList;
    va_start( pArgList, szFormat);
    _vsntprintf(szBuffer, sizeof(szBuffer) / sizeof(TCHAR), szFormat, pArgList);
    va_end(pArgList);
    // va_list,va_start和va_end宏帮助我们处理堆栈指针。
    return MessageBox(NULL, szBuffer, szCaption,0);
}
int WINAPI WinMain(IN HINSTANCE hInstance, IN HINSTANCE hPrevInstance,IN LPSTR lpCmdLine, IN int nShowCmd)
{
    int cxScreen, cyScreen;
    cxScreen = GetSystemMetrics(SM_CXSCREEN);
    cyScreen = GetSystemMetrics(SM_CYSCREEN);
 
    MessageBoxPrintf(TEXT("ScmSize"), TEXT("The screen is %i pixels wide by %i pixelshigh"), cxScreen, cyScreen);
 
    return 0;
}
va_list来做变量,使用va_start和va_end来处理变参数 

6. 窗口和消息

       桌面上最明显的窗口是应用程序窗口,这些窗口含有标题栏,菜单,甚至是工具栏和滚动条。另外一类窗口为对话框。

       装饰窗口的还有各式各样的按钮,单选钮,复选框,列表框,滚动条和文本输入区域。每一个小的可视对象都是一个窗口,更确切的应该为子窗口,控件窗。

       窗口以“消息”的形式接受窗口的输入,窗口也用消息与其他的窗口通信。      

       例如对于窗口大小的调整,程序将通过改变窗口中的内容来响应这种大小变化,Windows本身而不是应用程序正在处理与用户会重新调整窗口大小相关的全部代码,由于应用程序能改变现实的格式,因此应用程序本身也“知道”窗口大小改变了。

       Windows所使用的体系结构:当用户改变窗口的大小时,Windows给程序发送一条消息,指出新窗口的大小,然后程序就可以调整窗口中的内容,以反映大小的变化。所谓“Windows给程序发送消息”是指Windows调用程序中的一个函数,该函数的参数描述了这个特定消息。这种位于Windows程序中的函数被称为“窗口过程”。程序创建的每一个窗口都有相关的窗口过程,这个窗口过程是一个函数可以在程序中,也可以在动态连接库中。Windows通过调用窗口过程来给窗口发送消息。窗口过程根据此消息进行处理,然后将控制返回给Windows。

       窗口通常是在“窗口类”的基础上创建的,窗口类标识了处理窗口消息的窗口过程。使用窗口类使多个窗口能够基于同一个窗口类,并且使用同一个窗口过程。面向对象的程序设计中,对象是代码与数据的组合。窗口是一种对象,其代码是窗口过程。数据是窗口过程保存的信息,以及Windows为每一个窗口系统中那个窗口类保存的信息。

       窗口过程处理给窗口发送的消息,这些消息告知窗口,用户正在使用键盘或鼠标进行输入。这也是按钮窗口知道它被按下的奥妙所在。在窗口大小改变或窗口表面需要重绘时,由其他的消息通知窗口。

       Windows程序开始执行后,Windows为该程序创建一个“消息队列”,这个消息队列用来存放该程序可能创建的各种不同窗口的消息。程序中有一小段代码,叫做“消息循环”,用来从队列中取出消息,并将它们发送给相应的窗口过程。有些消息直接发送给窗口过程,不用放入消息队列中。      

HelloWin程序:

/* ---------------------------------------------------------------
    HelloWin.c -- Displays"Hellow Windows 98" in client area
                  (@) Charles Petzold, 1998
  --------------------------------------------------------------- */
#include<windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain( HINSTANCE hInstance,HINSTANCE hPreInst,PSTR szCmdLine,int iCmdShow)
{
    static TCHAR szAppName[] =TEXT("HelloWin");
    HWND hwnd;
    MSG msg;
    WNDCLASS winClass;
    winClass.style = CS_HREDRAW| CS_VREDRAW;
    winClass.lpfnWndProc = WndProc;
    winClass.cbClsExtra = 0;
    winClass.cbWndExtra = 0;
    winClass.hInstance = hInstance;
    winClass.hIcon = LoadIcon( NULL, IDI_APPLICATION);
    winClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    winClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    winClass.lpszMenuName = NULL;
    winClass.lpszClassName = szAppName;
 
    if(!RegisterClass(&winClass))
    {
        MessageBox(NULL, TEXT("Thisprogram require Windows NT!"), szAppName,MB_ICONERROR);
        return0;
    }
 
    hwnd = CreateWindow( szAppName,                 // 窗口名称
                        TEXT("Thehello Program"),  // 窗口标题
                        WS_OVERLAPPEDWINDOW,        // 窗口样式
                        CW_USEDEFAULT,              // 窗口初始x位置
                        CW_USEDEFAULT,              // 窗口初始y位置
                        CW_USEDEFAULT,              // 初始x大小
                        CW_USEDEFAULT,              // 初始y大小
                        NULL,                       //父窗口句柄
                        NULL,                       // 窗口菜单句柄
                        hInstance,                  // 程序实例句柄
                        NULL);                      //创建参数
 
    ShowWindow(hwnd, iCmdShow);
    UpdateWindow(hwnd);
 
    while (GetMessage(&msg,NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
 
    return (int)msg.wParam;
    return 0;
}
 
LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCTps;
    RECT rect;
    switch ( message)
    {
    case WM_CREATE:
        PlaySound("HelloWin.wav",NULL, SND_FILENAME|SND_ASYNC);
        //MessageBox(hwnd, TEXT("Window Created!"), TEXT("Note"),MB_OK);
        return0;
    case WM_PAINT:
            hdc= BeginPaint( hwnd,&ps);
            GetClientRect(hwnd, &rect);
            DrawText(hdc, TEXT("HelloWindows 98!"), -1, &rect, DT_SINGLELINE | DT_CENTER| DT_VCENTER);
            EndPaint(hwnd, &ps);
            return0;
    case WM_DESTROY:
            PostQuitMessage(0);
    }
 
    return DefWindowProc(hwnd,message, wParam,lParam);
}

       程序中有两个函数,一个是WinMain函数,另外一个是WndProc,这就是窗口过程。在程序中没有对WinProc调用的代码,在WinMain中有对WndProc的声明。

       对WinMain中调用的函数进行解析:

       LoadIcon:加载图标供程序使用

       LoadCursor:加载鼠标指针由程序使用

       GetStockObject:获取一个图形对象,在这个例子中是获取绘制窗口背景的刷子

       RegisterClass为程序窗口注册窗口类

       MessageBox显示消息框

       CreateWindow根据窗口类创建一个窗口

       ShowWindow在屏幕上显示窗口

       UpdateWindow指示窗口刷新自身

       GetMessage()从消息队列中获取消息

       TranslateMessage转换某些键盘消息

       DispatchMessage敬爱那个消息发送给窗口过程

       PlaySound播放一个声音文件

       BeginPaint开始窗口绘制

       GetClientRect获取窗口客户区的尺寸

       DrawText显示文本串

       EndPaint结束窗口绘制

       PostQuitMessage在消息队列中插入一条“退出”消息

       DefWindowProc执行默认的消息处理

       使用的常量的标识符前缀:

       CS          类风格选项

       CW         创建窗口选项

       DT          绘制文本选项

       IDI         图标ID号

       IDC        光标ID号

       MB         消息框选项

       SND       声音选项

       WM        窗口消息

       WS         窗口风格

 

Windows对一些数据类型进行别名的命名:

       UINT是unsigned int类型

       PSTR是 char * 类型

       WPARAM被定义为 UINT

       LPARAM被定义为 LONG

       LRESULT被定义为 LONG

       WINAPI和 CALLBACK 被定义为 __stdcall,指在Windows本身和用户的应用程序之间发生的函数调用的特殊调用序列。

      

使用新的数据结构:

       MSG                     消息结构

       WNDCLASS         窗口类结构

       PAINTSTRUCT     绘图结构体

       RECT                   矩形结构      

句柄介绍:

       HINSTANCE       实例(程序本身)句柄

       HWND                 窗口句柄

       HDC                     设备描述表句柄

       HICON                 图标句柄

       HCURSOR            鼠标指针句柄

       HBRUSH              图形刷句柄

      

       匈牙利命名法:根据变量类型,在前面加小写字母表示其类型

       类型              数据类型

       c                    char 或 WCHAR 或 TCHAR

       by                  BYTE 无符号字符

       n                   short

       i                    int

       x,y                int分别用作x坐标和y坐标

       cx,cy             int非别用作x长度和y长度,c代表count

       b或 f           BOOL(int); f代表flag

       w                   WORD 无符号short

       l                    Long 长整数

       dw                 DWORD 无符号长整数

       fn                  函数

       s                    string

       sz                  以0结尾的串

       h                   句柄

       p                   指针

      

注册窗口类:

       窗口总是在窗口类的基础上创建的,窗口类用以标识处理窗口消息的窗口过程

       因此在创建窗口之前要调用RegisterClass注册窗口类,该函数只需要一个指向类型为WNDCLASS的结构体指针。这个结构体也有Unicode和ASCII码两个版本。

创建窗口:

       窗口类定义了窗口的一般特征,可以使用同一个窗口类创建许多窗口,实际调用CreateWindow创建窗口时可以指定有关窗口更详细的信息

       窗口风格为重叠式,含有一个标题栏,标题栏左边有一个系统菜单框,标题栏右边有缩小放大和关闭的图标,四周还有窗口大小的边框:WS_OVERLAPPEDWINDOW

       #defineWS_OVERLAPPEDWINDOW ( WS_OVERLAPPED | \

                                                               WS_CAPTION | \

                                                               WS_SYSMENU | \

                                                               WS_THICKFRAME | \

                                                               WS_MINIMIZEBOX | \

                                                               WS_MAXIMIZEBOX )

      

       窗口的初始x,y位置默认值,窗口的小小也使用默认值。

       父窗口句柄是在当前窗口为某窗口的子窗口时设置,否则不需要进行设置。

       窗口菜单句柄,需要设置为菜单的句柄,此处也设置为NULL,没有菜单。

       程序实例句柄则传递WinMain参数中的hInstance参数。创建参数设置为NULL。      

       CreateWindow调用返回被创建爱你的窗口的句柄,该句柄存在变量hwnd中,其为HWND类型(窗口句柄类型)。Windows中的每个窗口都有一个句柄,程序用句柄来引用窗口。窗口句柄是Windows程序处理的最重要的句柄之一。      

显示窗口:

       ShowWindow(hwnd,iCmdShow); 第一个参数为窗口句柄,第二个参数为传递给WinMain的iCmdShow参数,确定了在屏幕上显示窗口的方式。

       ShowWindow函数在显示器上显示窗口,如果ShowWindows的第二个参数是SW_SHOWNORMAL,则窗口的客户区域就被窗口类中定义的背景刷所覆盖,函数调用UpdateWindow(hwnd);导致客户区域被绘制。通过向窗口的WndProc函数发送一个WM_PAINT消息实现这一点。

      

消息循环:

       在窗口显示之后,程序必须准备读入用户用键盘和鼠标输入的数据。Windows为当前运行的每个Windows程序维护一个“消息队列”,当发生输入事件之后,Windows将时间转换为一个“消息”,并将消息放入程序的消息队列中。

       通过消息循环将消息从消息队列中取出:

       while(GetMessage(&msg, NULL, 0, 0))

       {

              TranslateMessage(&msg);

              DispatchMessage(&msg);

       }

       msg是MSG的结构,类型如下:

       typedefstruct tag_MSG

       {

              HWNDhwnd;               // 窗口句柄

              UINTmessage;             // 消息类型 WM_开头的消息常量

              WPARAMwParam;      // 32位的 message parameter 消息参数

              LPARAMlParam;         // 32为的消息参数,其值与消息有关

              DWORDtime;              // 消息放入队列的时间

              POINTpt;                    // 消息放入消息队列时鼠标坐标

       } MSG, *PMSG

       消息循环从GetMessage开始,这一调用传递给Windows一个指向名为msg的MSG结构体的指针,第二个,第三个,第四个参数设为NULL或0,表示程序接收他自己创建的所有窗口的消息。

       WM_QUIT消息将导致GetMessge返回一个0,其他消息返回的都是非0值。

窗口过程:

       实际的动作发生在窗口过程中,窗口过程确定了在窗口的客户区域显示什么,以及窗口怎么相应用户输入。

       窗口过程可以任意命名,一个Windows程序可以包含多个窗口过程,一个窗口过程总是与调用RegisterClass注册的特定窗口类相关联。CreateWindow函数根据特定窗口类创建一个窗口,但是基于一个窗口类可以创建多个窗口。

       LRESULT CALLBACK WndProc(HWND hwnd, UINTmessage, WPARAM wParam, LPARAM lParam);

       窗口过程的4个参数与MSG结构的前四个域相同,hwnd用于标识一个唯一的窗口,如果本窗口类有多个被创建的窗口,则根据hwnd区别窗口。

       程序通常不直接调用窗口过程,窗口过程通常由Windows本身调用。通过调用SendMessage函数,程序能够直接调用它自己的窗口过程。

处理消息:

       窗口过程接收的每个消息均用一个数值标识,也就是传递给窗口过程的message参数。参数的取值为WM_开头的标识符。

       Windows程序员使用switch case结构来确定窗口过程接收的是什么消息,以及如何处理它。窗口过程处理消息时必须返回0,窗口不予处理的所有消息均由DefWindowsProc来处理,从DefWindowProc返回的值必须由窗口过程返回

       调用DefWindowProc来为窗口过程不予处理的所有消息提供默认处理。      

WM_PAINT消息:

       WM_PAINT消息在Windows程序设计中是很重要的,当客户区域的一部分或者全部变为“无效”,以致于必须“刷新”时,将由这个消息通知程序。

       1.在最初创建窗口的时候,整个客户区都是无效的,因为程序还没有在窗口上画东西,第一个WM_PAINT消息(通常发生在WinMain中调用UpdateWindow时)指示窗口过程在客户区域画一些东西。

       2.在用户改变窗口的大小时,客户端区域重新变得无效。

       3.当程序最小化,然后再次将窗口恢复为以前的大小时,Windows将不会保存客户区域的内容。

       4.在移动窗口以使得相互重叠时,Windows不保存一个窗口中被另外一个窗口所掩盖的内容。这一部分不再被遮盖之后,就被标志为无效。

       WM_PAINT的处理从一个BeginPaint调用开始,以EndPaint结束。

       BeginPaint调用使得整个客户区有效,并返回一个“设备描述表句柄”。设备描述表是指物理输出设备(视频显示器)以及其设备驱动程序。在窗口的客户区域显示文本和图形需要设备描述表句柄。(注意:不能从BeginPaint返回的设备描述表句柄在客户区域之外绘图)。EndPaint释放设备描述表句柄,使之不再有效。      

WM_DESTROY消息:

       WM_DESTROY消息是Windows的另外一个重要消息,消息指示,Windows正是根据用户输入命令来清除窗口。消息是用户单击Close按钮或者在系统菜单中选择Close时发生。

       在本程序中通过调用PostQuitMessage(0)以标准方式响应WM_DESTROY消息。该函数调用在消息队列中插入一条WM_PAINT消息。

7. Windows编程的难点

       Windows程序的所有实际行动均在窗口过程中发生。Windows程序所做的一切都是相应发送给窗口过程的消息,这事概念上的主要难点之一。

       窗口过程与一个窗口类相关联,窗口类是程序调用RegisterClass注册的。基于该类创建的窗口使用这个窗口过程来处理窗口的酥油消息。Windows通过调用窗口过程来给窗口发送一个消息。

       窗口中发生的一切都以消息的形式传递给窗口过程。窗口过程以某种方式相应这个消息或者将这个消息传给DefWindowProc进行默认处理。 

例子:

       一旦窗口的客户区域大小发送变化,Windows就调用窗口的窗口过程。窗口过程的hwnd参数是改变大小的窗口的句柄(记住:一个窗口过程能处理基于同一个窗口类创建的多个窗口的消息,参数hwnd让窗口过程知道是那个窗口在接收消息。)参数message是WM_SIZE,消息WM_SIZE的参数wParam的值是SIZE_RESTORED、SIZE_MINIMIZED、SIZE_MAXIMIZED、SIZE_MAXSHOW或SIZE_MAXHIDE,也即wParam表明窗口是非最小化还是非最大化,最小化,最大化,还是隐藏。

       lParam参数包含了新窗口的大小,新宽度和新高度均为16位值,合在一起成为32为的lParam。

       DefWindowProc处理完消息后会产生其他的消息,例如用户单击了关闭按钮,或者用键盘或鼠标从系统菜单中选择了Close,DefWindowProc处理这一键盘或鼠标输入,检测到用户选择了CLOSE选项之后,给窗口发送WM_SYSCOMMAND消息,WndProc将这个消息传递给DefWindowProc,DefWindowProc给窗口过程发送一个WM_CLOSE消息来响应之。WndProc再次将他传递个DefWindowProc,DestroyWindow调用DestroyWindow来响应这个WM_CLOSE消息。DestroyWindow导致Windows给窗口过程发送一个WM_DESTROY消息。WndProc再调用PostQuitMessage,将一个WM_QUIT消息放入消息队列中,一次来响应此消息。      

进队消息和不进队消息:

       消息能够被分为“进队的”和“不进队的”。进队的消息是由Windows放入程序消息队列中的。在程序的消息循环中重新返回并分配给窗口过程。不进队消息在Windows调用窗口时直接送给窗口过程,也就是说进队的消息被发送给消息队列,不进队的消息则发送给窗口过程。任何情况下,窗口过程都将获得窗口所有的消息——包括进队的和不进队的。窗口过程是窗口的“消息中心”。

       进队消息基本上是用户输入的结果,以击键(WM_KEYDOWN和WM_KEYUP消息),击键产生的字符(WM_CHAR)、鼠标移动(WM_MOUSEMOVE)和鼠标键(WM_LBUTTONDOWN)的形式给出。进队消息还有时钟消息(WM_TIMER)、刷新消息(WM_PAINT)和退出消息(WM_QUIT)。

       不进队的消息则是其他的消息,不进队消息来自特定的Windows函数,WinMain调用CreateWindow时,Windows将创建窗口并在处理中给窗口发送一个WM_CREATE消息。当WinMain调用ShowWindows时,Windows将给窗口过程发送WM_SIZE和WM_SHOWWINDOW消息。WinMain调用UpdateWindow时,Windows将调用窗口过程发送WM_PAINT。鼠标或键盘发出的消息也可能不进队列。

       这一过程很复杂,但是他们都是有Windows解决的,不管我们程序的事情。从窗口过程的角度看,这些消息是一种有序的,同步的方式进出,窗口过程可以处理他们,也可以不处理他们。      

       Windows程序可以多线程执行,但是每个线程的消息队列只为窗口过程在该线程中执行的串口处理消息。也就是说,消息循环和窗口过程不是并发运行的。当一个消息循环从消息队列中接受一个消息,然后调用DispatchMessage将消息发送给窗口过程时,直到窗口过程将控制返回给Windows,DispatchMessage才能返回。

       当然,窗口过程能调用给窗口过程发送另一个消息的函数。这时,窗口过程必须在函数调用返回之前完成对第二个消息的处理。例子:当窗口过程调用UpdateWindow时,Windows将调用窗口过程来处理WM_PAINT消息。窗口过程处理WM_PAINT消息结束以后,UpdateWindow调用将把控制返回给窗口过程。

       也就是说窗口过程必须是可重入的。在多数情况下,这不会带来问题,但是程序员应该意识到这一点。也即 假设在窗口过程处理一个消息时设置了一个静态变量,然后调用一个Windows函数。这个函数返回时,您还能保证那个变量的值还是原来那个吗?很可能在你调用Windows函数产生另外一个消息,并且窗口过程在处理这条消息时改变了该变量的值。


By Andy @ 2013-11-22

你可能感兴趣的:(《Windows程序设计》笔记之一——第一个Windows程序以及Windows程序原理)