Atela

导航

windows程序设计 Fifth 笔记(一)

第一章 开始

早期,Windows的主要部分仅通过三个动态连结程式库实作。Kernel、User和GDI。Kernel(日前由16位元的KRNL386.EXE和32位元的KERNEL32.DLL实现)处理所有在传统上由作业系统核心处理的事务-记忆体管理、档案I/O和多工管理。User(由16位的USER.EXE和32位的USER32.DLL实作)指使用者介面,实作所有视窗运作机制。GDI(由16位的GDI.EXE和32位的GDI32.DLL实作)是一个图形装置介面,允许程式在萤幕和印表机上显示文字和图形。

当您执行Windows程式时,它通过一个称作「动态连结」的过程与Windows相接。一个Windows的 .EXE文件拥有使用的不同动态连结库的引用,所使用的函数即在那些动态连结程式库中。当Windows程序被载入到内存中时,程序中的调用被指向DLL函数的入口。如果该DLL不在内存中,就把它载入到记忆体中。

当您连结Windows程序以产生一个可执行文件时,您必须连结编程环境提供的特定「导入库(import library)」。这些导入库包含了动态连结库名称和所有Windows函数调用的引用信息。链接程序使用该信息在.EXE文件中建立一个表,在加载程序时,Windows使用它将调用转换为Windows函数。

 

第二章 Unicode 简介

DBCS(double-byte character set)宽字节字符集,仍然处理8位值

Unicode是ASCII字符编码的一个扩展,使用全16位字符编码,不处理8位 。windows NT从底层支持Unicode。

宽字符:支持多和字节代表一个字符的字符集

宽字符不需要Unicode,Unicode是一种可能的宽字符编码. 本书把宽字符和Unicode作为同义词

C中的宽字符基于wchar_t,在几个头文件(包括WCHAR.H)中都有定义

typedef unsigned short wchar_t;
wchar_t c = 'A';

不用加L,编译器会对该字符进行零扩充
c是一个双字节值0x0041,因为intel处理器首先从最低位字节开始存储多字节数值,所以该字符实际以0x41、0x00的顺序存放在内存中。

wchar_t *pw = L"Hello!";
iLength = strlen (pw);// from 'unsigned short *' to 'const char *'

继续运行iLength会等于1
字串Hello!:0x0048 0x0065 0x006C 0x006C 0x006F 0x0021
Intel处理器在内存中将其存为:48 00 65 00 6C 00 6C 00 6F 00 21 00,strlen遇字节0返回

 

strlen的宽字符版本是wcslen,在STRING.H(也声明了strlen)和WCHAR.H中都有声明

size_t __cdecl strlen (const char *) ;
size_t __cdecl wcslen (const wchar_t *) ;

 

由于使用Unicode每个字符都要两倍空间,宽字符运行库中函数也比常规的大。所以想维护既能按ASCII编译又能按Unicode编译的单一源码文件。

一个办法是使用Microsoft Visual C++包含的TCHAR.H头文件。该头文件不是ANSI C标准的一部分,因此那里定义的每个函式和宏定义的前面都有一条下划线。TCHAR.H为需要字串参数的标准运行库函数提供了一系列的替代名称(例如,_tprintf和_tcslen)。有时这些名称也称为「通用」函式名称,因为它们既可以指向函式的Unicode版也可以指向非Unicode版。

TCHAR.H中的部分声明:

#ifdef _UNICODE
typedef wchar_t TCHAR ;
#define _tcslen wcslen
#define __T(x) L##x
#else 
typedef char TCHAR ;
#define _tcslen strlen
#define __T(x) x
#endif
#define _T(x)       __T(x)
#define _TEXT(x)    __T(x)

Windows程序都包括WINDOWS.H。该投文件包括许多其他头文件,包括WINDEF.H,该头文件中有许多在Windows中使用的基本类型定义,而且它本身也包括WINNT.H。WINNT.H处理基本的Unicode支持。

WINNT.H的前面包含C的头文件CTYPE.H,这是C的众多头文件之一,包括wchar_t的定义。WINNT.H定义了新的数据类型,称作CHAR和WCHAR:

typedef char CHAR ;
typedef wchar_t WCHAR ;    // wc

当您需要定义8位字符或者16位字符时,推荐您在Windows程序中使用的数据型态是CHAR和WCHAR。WCHAR定义后面的注释是匈牙利标记法的建议:一个基于WCHAR数据型态的变量可在前面附加上字母wc以说明一个宽字符。

WINNT.H表头文件进而定义了可用做8位字符串指针的六种数据型态和四个可用做const 8位字符串指针的数据型态。

typedef CHAR * PCHAR, * LPCH, * PCH, * NPSTR, * LPSTR, * PSTR;   
typedef CONST CHAR * LPCCH, * PCCH, * LPCSTR, * PCSTR;   

前缀N和L表示「near」和「long」,指的是16位Windows中两种大小不同的指标。在Win32中near和long指标没有区别。

类似地,WINNT.H定义了六种可作为16位字符串指针的数据型态和四种可作为const 16位字符串指针的数据型态:

typedef WCHAR * PWCHAR, * LPWCH, * PWCH, * NWPSTR, * LPWSTR, * PWSTR ;        
typedef CONST WCHAR * LPCWCH, * PCWCH, * LPCWSTR, * PCWSTR ;     

与TCHAR.H一样,WINNT.H将TCHAR定义为一般的字符类型。

#ifdef  UNICODE
typedef WCHAR TCHAR, * PTCHAR ;
typedef LPWSTR LPTCH, PTCH, PTSTR, LPTSTR ; 
typedef LPCWSTR LPCTSTR ;  
#define __TEXT(quote) L##quote     
#else
typedef char TCHAR, * PTCHAR ;  
typedef LPSTR LPTCH, PTCH, PTSTR, LPTSTR ; 
typedef LPCSTR LPCTSTR ;
#define __TEXT(quote) quote   
#endif
#define TEXT(quote) __TEXT(quote)

如果已经在某个表头文件或者其它表头文件中定义了TCHAR数据型态,那么WINNT.H和WCHAR.H表头文件都能防止其重复定义。不过,无论何时在程序中使用其它表头文件时,都应在所有其它表头文件之前包含WINDOWS.H。

建议:如果您希望明确定义8位字符变量和字符串,请使用CHAR、PCHAR(或者其它),以及带引号的字符串。为明确地使用16位字符变量和字符串,请使用WCHAR、PWCHAR,并将L添加到引号前面。对于是8位还是16位取决于UNICODE标识符的定义的变量或字符串,要使用TCHAR、PTCHAR和TEXT宏。

 

从Windows 1.0到Windows 3.1的16位Windows中,MessageBox函数位于动态链接库USER.EXE。在Windows 3.1软件开发套件的WINDOWS.H中,MessageBox函数定义如下:

int WINAPI MessageBox (HWND, LPCSTR, LPCSTR, UINT) ;     
在USER32.DLL中,没有32位MessageBox函数的进入点。实际上,有两个进入点,一个名为MessageBoxA(ASCII版),另一个名为MessageBoxW(宽字符版)。

在WINUSER.H头文件中:

WINUSERAPI int WINAPI MessageBoxA (HWND hWnd, LPCSTR lpText, 
                           LPCSTR lpCaption, UINT uType) ;
WINUSERAPI int WINAPI MessageBoxW (HWND hWnd, LPCWSTR lpText,
                           LPCWSTR lpCaption, UINT uType) ;
#ifdef UNICODE      
#define MessageBox  MessageBoxW
#else     
#define MessageBox  MessageBoxA
#endif

 

Windows的字符串函数

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

ILength = lstrlen (pString) ;
pString = lstrcpy (pString1, pString2) ;    
pString = lstrcpyn (pString1, pString2, iCount) ;   
pString = lstrcat (pString1, pString2) ; 
iComp = lstrcmp (pString1, pString2) ;   
iComp = lstrcmpi (pString1, pString2) ;//不区分大小写

这些函数与C链接库中对应的函数功能相同。如果定义了UNICODE标识符,那么这些函数将接受宽字符串,否则只接受常规字符串。宽字符串版的lstrlenW函数可在Windows 98中执行。

 

当格式字符串与被格式化的变量不合时,可能使printf执行错误并可能造成程序当掉。使用sprintf时,您不但要担心这些,而且还有一个新的负担:您定义的字符串缓冲区必须足够大以存放结果。Microsoft专用函数_snprintf解决了这一问题,此函数引进了另一个参数,表示以字符计算的缓冲区大小。

vsprintf是sprintf的一个变形,它只有三个参数。vsprintf用于执行有多个参数的定义函数,类似printf格式。vsprintf的前两个参数与sprintf相同:一个用于保存结果的字符缓冲区和一个格式字符串。第三个参数是指向格式化参数数组的指针。实际上,该指针指向在堆栈中供函数呼叫的变量。va_list、va_start和va_end宏(在STDARG.H中定义)帮助我们处理堆栈指针。使用vsprintf函数,sprintf函数可以这样编写:

typedef char *  va_list;
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) //获取类型占用的空间长度,最小占用长度为int的整数倍
#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )//获取可变参数列表的第一个参数的地址
#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )//获取可变参数的当前参数,将指针指向下一参数
#define va_end(ap)      ( ap = (va_list)0 ) //清空va_list可变参数列表
int __cdecl sprintf (char * szBuffer, const char * szFormat, ...) {   
    int     iReturn ;       
    va_list pArgs ;
    va_start (pArgs, szFormat) ;
    iReturn = vsprintf (szBuffer, szFormat, pArgs) ;
    va_end (pArgs) ;
    return iReturn ;
}

_cdecl调用约定:  1.参数从右到左依次入栈     2.调用者负责清理堆栈     3.参数的数量类型不会导致编译阶段的错误

这章最后,自己写个printf

int myprintf (const char *format, ...) {
    va_list pArgs;
    va_start (pArgs, format);
    while (*format != '\0') {
        if(*format != '%') putchar(*format++);
        else {
            switch(*++format) {
                case 'c': 
                    putchar(va_arg(pArgs, char));
                    break;
                case 'd': {
                    char cbuf[100];
                    itoa(va_arg(pArgs, int), cbuf, 10);
                    char* p = cbuf;
                    while(*p != '\0') putchar(*p++);
                    break;
                }
                case 's': {
                    const char *pstr = va_arg(pArgs,const char*);
                    while (*pstr != '\0') putchar(*pstr++);
                    break;
                }
            //other case...
                default:
                    putchar(*format);
            }
            ++format;
        }
    }
    va_end(pArgs);
    return 0;
}

 

第三章 窗口和消息

#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   PSTR szCmdLine, int iCmdShow)   
{      
    static TCHAR szAppName[] = TEXT ("HelloWin") ;    
    HWND   hwnd ;
    MSG    msg ;
    WNDCLAS wndclass;
    wndclass.style        = CS_HREDRAW | CS_VREDRAW ;    
    wndclass.lpfnWndProc  = WndProc ;    
    wndclass.cbClsExtra   = 0 ;  
    wndclass.cbWndExtra   = 0 ;
    wndclass.hInstance    = hInstance ;
    wndclass.hIcon        = LoadIcon (NULL, IDI_APPLICATION) ;
    wndclass.hCursor      = LoadCursor (NULL, IDC_ARROW) ;    
    wndclass.hbrBackground= (HBRUSH) GetStockObject (WHITE_BRUSH) ;  
    wndclass.lpszMenuNam  = NULL ;   
    wndclass.lpszClassName= szAppName ;
    if (!RegisterClass (&wndclass)) {
            MessageBox (  NULL, TEXT ("This program requires Windows NT!"),
                                  szAppName, MB_ICONERROR) ;
            return 0 ;
    }
    hwnd = CreateWindow( szAppName,      // window class name
                   TEXT ("The Hello Program"),   // window caption
                   WS_OVERLAPPEDWINDOW,  // window style
                   CW_USEDEFAULT,// initial x position
                   CW_USEDEFAULT,// initial y position
                   CW_USEDEFAULT,// initial x size
                   CW_USEDEFAULT,// initial y size
                   NULL,         // parent window handle
                   NULL,         // window menu handle
                   hInstance,    // program instance handle
                   NULL) ;       // creation parameters
    ShowWindow (hwnd, iCmdShow) ;//如果ShowWindow的第二个参数是SW_SHOWNORMAL,则窗口的显示区域就会被窗口类别中定义的背景画刷所覆盖
    UpdateWindow (hwnd) ;  
    while (GetMessage (&msg, NULL, 0, 0)) {
          TranslateMessage (&msg) ;
          DispatchMessage (&msg) ;
    }  
    return msg.wParam ;     
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {   
    HDC  hdc ;
    PAINTSTRUCT ps ;
    RECT rect ;
    switch (message) {   
    case WM_CREATE:  
    PlaySound (TEXT ("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC) ;
            return 0 ;
    case   WM_PAINT:
            hdc = BeginPaint (hwnd, &ps) ;
            GetClientRect (hwnd, &rect) ;
            DrawText (hdc, TEXT ("Hello, Windows 98!"), -1, &rect,
                 DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
            EndPaint (hwnd, &ps) ;
            return 0 ;   
    case   WM_DESTROY:
            PostQuitMessage (0) ;
            return 0 ;
    }
  return DefWindowProc (hwnd, message, wParam, lParam) ;
}

WINUSER.H表头文件中定义了两种不同WNDCLASS,ASCII版WNDCLASSA和Unicode版的WNDCLASSW。

本书后面列出结构时,将只列出功用相同的结构定义,对WNDCLASS就像这样:

typedef struct {
   UINT          style ; //cs, CS_HREDRAW | CS_VREDRAW
   WNDPROC       lpfnWndProc ;//lpfn
   int           cbClsExtra ; //count of bytes 类附加内存
   int           cbWndExtra ; //窗口附加内存
   HINSTANCE     hInstance ;  //指定包含窗口过程函数的程序的实例句柄,WinMain参数
   HICON         hIcon ;
   HCURSOR       hCursor ;
   HBRUSH        hbrBackground ;//hbrush, HGDIOBJ GetStockObject(int fnObject)可获得系统标准画刷画笔字体调色板的句柄
   LPCTSTR       lpszMenuName ; //菜单资源名字,如果用资源号,要用MAKEINTRESOURCE宏转换,null为无菜单
   LPCTSTR       lpszClassName ; //指定窗口类名字
} WNDCLASS, * PWNDCLASS ;
typedef struct tagMSG {
    HWND   hwnd ; 
    UINT   message ; //消息标识符
    WPARAM wParam ; 
    LPARAM lParam ;//两个附加消息随消息不同而不同
    DWORD  time ;//消息投递时间
    POINT  pt ;   //鼠标当前位置
}MSG, * PMSG ;
while (GetMessage (&msg, NULL, 0, 0)){ //只要从消息队列中取出消息的message字段不为WM_QUIT(其值为0x0012),GetMessage就传回一个非零值。
    TranslateMessage (&msg) ;//将msg结构传给Windows,进行一些键盘转换?。(在第六章中深入讨论)
    DispatchMessage (&msg) ;// 又将msg结构回传给Windows。然后,Windows将该消息发送给适当的窗口消息处理程序.处理完消息之后,WndProc传回到Windows。  
}   //此时,Windows还停留在DispatchMessage呼叫中。在结束DispatchMessage呼叫的处理之后,Windows回到HELLOWIN,并从下一个GetMessage开始消息循环。
BOOL GetMessage(
  LPMSG lpMsg,         // message information
  HWND hWnd,           // handle to window
  UINT wMsgFilterMin,  // first message 获取的消息的最小值
  UINT wMsgFilterMax   // last message 获取的消息的最大值。都为0则接受全部消息
  );
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

第一个参数hwnd是接收消息的窗口的句柄,它与CreateWindow函数的传回值相同。
第二个参数与MSG结构中的message字段相同,它是标识消息的数值。最后两个参数都是32位的消息参数,提供关于消息的更多信息。
窗口消息处理程序通常由Windows本身呼叫。通过呼叫SendMessage函数,程序能够直接呼叫它自己的窗口消息处理程序。
窗口消息处理程序在处理消息时,必须传回0.

窗口消息处理程序不予处理的所有消息应该被传给名为DefWindowProc的Windows函数。从DefWindowProc传回的值必须由窗口消息处理程序传回

对WM_PAINT的处理几乎总是从一个BeginPaint呼叫开始:

hdc = BeginPaint (hwnd, &ps) ;
而以一个EndPaint呼叫结束:
EndPaint (hwnd, &ps) ;

在BeginPaint呼叫中,如果显示区域的背景还未被删除,则由Windows来删除。它使用注册窗口类别的WNDCLASS结构的hbrBackground字段中指定的画刷来删除背景
BeginPaint呼叫令整个显示区域有效,并传回一个「设备内容句柄」。设备内容是指实体输出设备(如视讯显示器)及其设备驱动程序。在窗口的显示区域显示文字和图形需要设备内容句柄。但是从BeginPaint传回的设备内容句柄不能在显示区域之外绘图。EndPaint释放设备内容句柄,使之不再有效。
如果窗口消息处理程序不处理WM_PAINT消息(这是很罕见的),它们必须被传送给DefWindowProc。DefWindowProc只是依次呼叫BeginPaint和EndPaint,以使显示区域有效。

HELLOWIN通过呼叫PostQuitMessage以标准方式响应WM_DESTROY消息:

PostQuitMessage (0) ;
该函数在程序的消息队列中插入一个WM_QUIT消息。

最好WinMain返回:

return msg.wParam ;
结构的wParam字段是传递给PostQuitMessage函数的值(通常是0)。然后return叙述将退出WinMain并终止程序。

前缀 类别
CS    窗口类别样式
CW   建立窗口
DT    绘制文字
IDI    图示ID
IDC   游标ID
MB   消息框
SND  声音
WM  窗口消息
WS    窗口样式
by     BYTE (无正负号字符)
n       short
w      WORD (无正负号短整数)
sz      以字节值0结尾的字符串

消息WM_SIZE的参数wParam的值是SIZE_RESTORED、SIZE_MINIMIZED、SIZE_MAXIMIZED、SIZE_MAXSHOW或SIZE_MAXHIDE (在WINUSER.H表头文件中分别定义为数字0到4)。也就是说,参数wParam表明窗口是非最小化还是非最大化,是最小化、最大化,还是隐藏。

lParam参数包含了新窗口的大小,新宽度和新高度均为16位值,合在一起成为32位的lParam。WINDEF.H中提供了帮助程序写作者从lParam中取出这两个值的宏。

有时候,DefWindowProc处理完消息后会产生其它的消息。例如,使用者最终单击了 Close按钮,或者假设用键盘或鼠标从系统菜单中选择了 Close, DefWindowProc处理这一键盘或者鼠标输入,在检测到使用者选择了Close选项之后,它给窗口消息处理程序发送一条WM_SYSCOMMAND消息。WndProc将这个消息传给DefWindowProc。DefWindowProc给窗口消息处理程序发送一条WM_CLOSE消息来响应之。WndProc再次将它传给DefWindowProc。DestroyWindow呼叫DestroyWindow来响应这条WM_CLOSE消息。DestroyWindow导致Windows给窗口消息处理程序发送一条WM_DESTROY消息。WndProc再呼叫PostQuitMessage,将一条WM_QUIT消息放入消息队列中,以此来响应此消息。这个消息导致WinMain中的消息循环终止,然后程序结束。

总结windows消息机制:

Windows为当前执行的每个Windows程序维护一个「消息队列」。在发生输入事件之后,Windows将事件转换为一个「消息」,消息分为入队消息和不入队消息。入队消息被放入程序的消息队列中。程序通过执行一块称之为「消息循环」的程序代码从消息队列中取出消息。TranslateMessage(&msg); 将msg结构传给Windows,进行一些键盘转换。DispatchMessage(&msg);又将msg结构回传给Windows。然后,Windows将该消息发送给适当的窗口消息处理程序,让它进行处理。处理完消息之后,WndProc传回到Windows。结束DispatchMessage呼叫的处理之后,Windows回到程序中,并且接着从下一个GetMessage呼叫开始消息循环。

非队列化消息则是其它消息。在许多情况下,非队列化消息来自呼叫特定的Windows函数。例如,当WinMain呼叫CreateWindow时的WM_CREATE消息。ShowWindow时的WM_SIZE和WM_SHOWWINDOW消息。UpdateWindow时的WM_PAINT消息。键盘或鼠标输入时发出的队列化消息信号,也能在非队列化消息中出现。例如,用键盘或鼠标选择了一个菜单项时,键盘或鼠标消息就是队列化的,而说明菜单项已选中的WM_COMMAND消息则可能就是非队列化的。

posted on 2011-04-28 13:20  Atela  阅读(504)  评论(0编辑  收藏  举报