第三章是基于对一个windows窗口的学习,来达到对windows程序运行机制的理解。

从语言的角度看消息机制,Windows给程序发消息的本质就是调用“窗口过程”函数。

Don't Call Me, I'll Call You!

主动激励,被动响应。

/*------------------------------------------------------------
HELLOWIN.C -- Displays "Hello, Windows 98!" in client area
(c) Charles Petzold, 1998
------------------------------------------------------------*/ #include <windows.h>
#include <string.h>
#pragma comment(lib, "winmm.lib")
#pragma warning(disable : 4996) LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;//LRESULT等价于LONG WPARAM等价于UINT LPARAM等价于LONG int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("HelloWin") ;
HWND hwnd ;//窗口句柄
MSG msg ;//消息结构
WNDCLASS 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.lpszMenuName = 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 ("阡陌"), // 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
//发送WM_CREATE消息
ShowWindow (hwnd, iCmdShow) ;
//在屏幕显示窗口
UpdateWindow (hwnd) ;
//指明窗口对其自身进行重绘 发送WM_PAINT消息
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 ;//矩形结构
wchar_t str[1000] = {0};
wcscpy(str, TEXT("Hello"));
switch (message)
{
case WM_CREATE:
PlaySound (TEXT ("ka.wav"), NULL, SND_FILENAME | SND_ASYNC | SND_LOOP) ;//播放声音文件
return 0;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;//标明窗口绘制开始
GetClientRect (hwnd, &rect) ;//获取窗口客户区的尺寸
DrawText (hdc, str, -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) ;//执行默认的消息处理
}

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

在Win32中,wParam lParam是用来传递消息数据的最常用的手段.

比如,对按键消息来说,鼠标的X和Y的坐标被压缩进lParam中

对MFC来说,消息可以用多样的类型参数来传递,对用户自定义消息

来说,只能用wParam和lParam来传递。

在Win32 SDK中消息本身是作为一个结构体记录传递给应用程序的,这个记录中包含了消息的类型以及其他信息。这个记录类型叫做MSG,它在window中是这样声明的:

typedef struct tagMSG {     // msg  

   HWND hwnd;    //窗口句柄

   UINT message;   //消息常量标识符

   WPARAM wParam;  //32位消息的特定附加信息,具体表示什么处决于message

   LPARAM lParam;  //32位消息的特定附加信息,具体表示什么处决于message

   DWORD time;    //消息创建时的时间

   POINT pt;       //消息创建时的鼠标位置

} MSG;





hwnd 接收消息的32位窗口句柄。窗口可以是任何类型的屏幕对象,

因为Win32能够维护大多数可视对象的句柄(窗口、对话框、按钮、编辑框等)。

message 用于区别其他消息的常量值,这些常量可以是Windows单元中预定义的常量,也可以是自定义的常量。

wParam 通常是一个与消息有关的常量值,也可能是窗口或控件的句柄。

lParam 通常是一个指向内存中数据的指针。

--------------------------------------------------------------------------------------------------------------------------

把 UpdateWindow (hwnd) ;注释掉,程序没有变化;

把消息循环和UpdateWindow (hwnd) 一起注释掉没有客户区的“Hello”;

只把消息循环注释掉,显示;

说明updatewindow发出的 WM_PAINT 是不进入消息队列而直接处理。

------------------------------------------------------------------------------------------------------------------------

PlaySound (TEXT ("ka.wav"), NULL, SND_FILENAME | SND_ASYNC | SND_LOOP) ;//播放声音文件

假如去掉SND_ASYNC | SND_LOOP会有什么后果呢?

SND_ASYNC

用异步方式播放声音,PlaySound函数在开始播放后立即返回。

SND_LOOP

重复播放声音,必须与SND_ASYNC标志一块使用。

无论是PlaySound(SND_ASYNC)还是MCI(MM_MCINOTIFY)都必须异步方式执行播放声音文件代码,否则只能等到声音播放完成,才开始处理其他消息。

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

队列消息与非队列消息:

1、消息能够被分为「队列化的」和「非队列化的」。队列化的消息是由Windows放入程序消息队列中的。在程序的消息循环中,重新传回并分配给窗口消息处理程序。非队列化的消息在Windows呼叫窗口时直接送给窗口消息处理程序。也就是说,队列化的消息被「发送」给消息队列,而非队列化的消息则「发送」给窗口消息处理程序。任何情况下,窗口消息处理程序都将获得窗口所有的消息--包括队列化的和非队列化的。窗口消息处理程序是窗口的「消息中心」。





   2、 队列化消息基本上是使用者输入的结果,以击键(如WM_KEYDOWN和WM_KEYUP消息)、击键产生的字符(WM_CHAR)、鼠标移动(WM_MOUSEMOVE)和鼠标按钮(WM_LBUTTONDOWN)的形式给出。队列化消息还包含时钟消息(WM_TIMER)、更新消息(WM_PAINT)和退出消息(WM_QUIT)。





   3、非队列化消息则是其它消息。在许多情况下,非队列化消息来自呼叫特定的Windows函数。例如,当WinMain呼叫CreateWindow时,Windows将建立窗口并在处理中给窗口消息处理程序发送一个WM_CREATE消息。当WinMain呼叫ShowWindow时,Windows将给窗口消息处理程序发送WM_SIZE和WM_SHOWWINDOW消息。当WinMain呼叫UpdateWindow时,Windows将给窗口消息处理程序发送WM_PAINT消息。键盘或鼠标输入时发出的队列化消息信号,也能在非队列化消息中出现。





   SendMessage()与PostMessage()之间的区别是什么?

它们两者是用于向应用程序发送消息的。PostMessagex()将消息直接加入到应用程序的消息队列中,不等程序返回就退出;而SendMessage()则刚好相反,应用程序处理完此消息后,它才返回。我想下图能够比较好的体现这两个函数的关系:

程序详解:

/*------------------------------------------------------------
HELLOWIN.C -- Displays "Hello, Windows 98!" in client area
(c) Charles Petzold, 1998
------------------------------------------------------------*/ #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;//窗口回调函数声明 LRESULT是函数返回值类型被宏定义为LONG
//CALLBACK说明的是函数参数的进栈顺序(从左到右)!
//4个函数参数为message结构体中的前4个参数! int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow) //主函数,int为函数返回值类型,WINAPI和CALLBACK一样都函数参数的进栈顺序!
//HINSTANCE hInstance 定义一个实例句柄变量,所谓句柄的概念和指针,引用差不多,但又不太一样!自己体会一下
//其他参数看书44页!
{
static TCHAR szAppName[] = TEXT ("HelloWin") ; //定义一个字符串数组szAppName[]并赋初值!这个“HelloWin”是程序的名字
//是要进操作系统的注册表的!是告诉操作系统你这个程序的名字是什么! HWND hwnd ; //定义窗口句柄变量!
MSG msg ; //MSG是一个结构体类型,所以msg是一个结构体变量!见书54页
WNDCLASS wndclass ; //WNDCLASS是一个结构体类型,所以wndclass是一个结构体变量!见书47,48页 wndclass.style = CS_HREDRAW | CS_VREDRAW ; //为窗口风格赋值,CS_HREDRAW 为垂直重画 CS_VREDRAW为水平重画!意思就 //是如果你水平或竖直拖动窗口,他将重新显示窗口,即调用WM_PAINT消息!
wndclass.lpfnWndProc = WndProc ; //把窗口回调函数的首地址赋给wndclass.lpfnWndProc
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) ; //初始化窗口的背景画刷,如果把WHITE_BRUSH改为
//BLACK_BRUSH背景即为黑色!
wndclass.lpszMenuName = 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 窗口显示时左上角x坐标
CW_USEDEFAULT, // initial y position 窗口显示时左上角y坐标
CW_USEDEFAULT, // initial x size 窗口显示时右下角x坐标
CW_USEDEFAULT, // initial y size 窗口显示时右下角y坐标
NULL, // parent window handle 父窗口句柄,此程序没有
NULL, // window menu handle 菜单句柄
hInstance, // program instance handle程序实例句柄
NULL) ; // creation parameters 创建参数指针 ShowWindow (hwnd, iCmdShow) ; //第一次调用窗口回调函数Wndproc,注意Wndproc函数不是ShowWindow函数来调用,而是
//ShowWindow函数向操作系统发送消息,是操作系统根据发送的消息来调用Wndproc函数!
//操作系统调用Wndproc函数后并响应WM_CREATE消息! UpdateWindow (hwnd) ; //刷新窗口,操作系统调用Wndproc函数,并响应WM_PAINT消息! while (GetMessage (&msg, NULL, 0, 0))// 这是所有WINDOWS程序的核心,消息循环处理过程!这里是接受消息和发送消息的地方!
{ //GetMessage 函数从操作系统的消息队列中获得消息,一个一个的处理,来一个处理一个,
//直到获得退出消息,也就是点击应用程序右上角的叉子!退出消息循环,并响应WM_QUIT消息 TranslateMessage (&msg) ; //翻译键盘消息
DispatchMessage (&msg) ; //发送消息函数,先把msg发送给操作系统,然后由操作系统再调用Wndproc函数!
}
return msg.wParam ;
}//主函数结束 //窗口回调函数,此函数只有声明和定义,没有调用!这说明此函数确实是由操作系统调用的!
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)//函数的4个参数是消息结构体的前4个参数!
{
HDC hdc ; //设备环境描述表句柄
PAINTSTRUCT ps ; //绘图结构体变量
RECT rect ;//矩形变量 switch (message) //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) ;//处理一些闲杂以及一些不可预料的消息!
} /*注意 1:所有的消息都由操作系统负责管理,所有的消息先进入总的系统消息队列,再由系统消息队列向各个小应用程序队列发消息!
2:窗口回调函数Wndproc是由操作系统调用的,所有的消息进入消息循环,由消息循环把消息发到操作系统的队列中,在由操作系统
根据消息来调用Wndproc函数,而Wndproc函数根据message的不同而调用相应的处理过程!
3:上面这个程序你可以改造一下,在添加几个消息处理过程,比如鼠标左键按下WM_LBULLONDOWN,鼠标左键抬起WM_LBULLONUP
定时器消息WM_TIMER,绘图消息WM_PAINT消息等等,然后把PlaySound (TEXT ("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC)
这个函数添加到以上各个消息处理过程中,看看结果是不是进行了相应的操作!比如,点击一下鼠标左键看看是不是发出声音了!
可以自己是是!
4:其实,WINDOWS程序也可以自己去创建函数!自己去自定义消息处理过程!比如,你自己定义了一个函数,想在鼠标左键点击后
去调用这个函数,那就可以在WM_LBULLONDOWN这个消息处理过程中加上你自己定义的函数调用就可以了!
5:上面这个程序是个非常简单的程序,但是他足以说明WINDOWS程序的消息机制!几乎所有的程序都包括上面这段代码(除了PLAYSOUND函数和
DrawText函数),不管多么复杂的WINDOWS程序都必须包括上面这段代码,因为他是再简单不过的了,只是创建了一个窗口!但是,
再复杂的程序无非也就是在Wndproc函数中的switch多加几个case 吗!无非就是加上什么鼠标,键盘,定时器等等,但原里都是
一样的!他的消息处理过程都是一样的!主要是把他的消息机制弄懂,其他的都非常easy!
*/

《windows程序设计》第三章学习心得的更多相关文章

  1. js 高级程序设计 第三章学习笔记——Number数据类型需要注意的事项

    1.浮点数值 虽然小数点前面可以没有整数,但是并不推荐这种写法. 由于保存浮点数值需要的内存空间是保存整数值的两倍,因此ECMAScript会不失时机地将浮点数值转化为整数数值.显然,如果小数点后面没 ...

  2. 《ORACLE数据库管理与开发》第三章学习之常用函数记录

    <ORACLE数据库管理与开发>第三章学习之常用函数记录 注:文章中的*代表所要操作的列名 1.lower(*)/upper(*),将此列下的值转为小写/大写 2.initcap(*):把 ...

  3. Windows程序设计(第五版)学习:第三章 窗口与消息

        第三章 窗口与消息 1,windows窗口过程:应用程序所创建的每一个窗口都有一个与之关联的窗口过程,用于处理传递给窗口的消息. 2,窗口依据窗口类来创建.窗口类标识了用于处理传递给窗口的消息 ...

  4. Windows程序设计(第五版)学习:第一章 起步

    第一章 起步 1,windows主要的三个动态库: kernel32.dll负责操作系统的传统工作,包括内存管理.文件输入以及任务管理等. user32.dll负责用户界面的操作,即所有窗口的管理 g ...

  5. Windows Pe 第三章 PE头文件(上)

    第三章  PE头文件 本章是全书重点,所以要好好理解,概念比较多,但是非常重要. PE头文件记录了PE文件中所有的数据的组织方式,它类似于一本书的目录,通过目录我们可以快速定位到某个具体的章节:通过P ...

  6. 20165221 JAVA第三周学习心得

    知识点回顾 类与对象学习总结 类:java作为面向对象型语言具有三个特性:①封装性.②继承性.③多态性.java中类是基本要素,类声明的变量叫对象.在类中定义体的函数题叫方法. 类与程序的基本结构: ...

  7. 20165233 Java第二、三章学习总结

    2017-2018-2 <Java程序设计>第二周学习总结 教材学习内容总结 第二.三章 ch2 标识符与关键字 基本数据类型: 逻辑类型:boolean 整数类型:int.byte.sh ...

  8. 20135202闫佳歆--week6 课本第三章学习笔记

    第三章 进程管理 一.进程 1.进程 进程就是处于执行期的程序. 进程就是正在执行的程序代码的实时结果. 进程是处于执行期的程序以及相关的资源的总称. 进程包括代码段和其他资源. 2.线程 执行线程, ...

  9. 《Linux命令行与shell脚本编程大全》 第三章 学习笔记

    第三章:基本的bash shell命令 bash程序使用命令行参数来修改所启动shell的类型 参数 描述 -c string 从string中读取命令并处理他们 -r 启动限制性shell,限制用户 ...

随机推荐

  1. 10_时间戳timeStamp 和 时间 time 转换, 根据时间节点倒计时

    1: 时间戳 timeStamp 获取的几种方法及其优劣, 第一种只能精确到秒, 故不推荐使用, 最最常用的也是最官方的是第三种, 通过原型方法进行调用获取精确到毫秒数 : var timestamp ...

  2. 傅立叶变换—FFT(cuda实现)

    背景: 无意间看到cuda解决FFT有一个cufft函数库,大体查看了有关cufft有关知识,写了一个解决一维情况的cuda代码,据调查知道cufft在解决1D,2D,3D的情况时间复杂度都为O(nl ...

  3. React Native 性能优化指南【全网最全,值得收藏】

    2020 年谈 React Native,在日新月异的前端圈,可能算比较另类了.文章动笔之前我也犹豫过,但是想到写技术文章又不是赶时髦,啥新潮写啥,所以还是动笔写了这篇 React Native 性能 ...

  4. 关于i++的底层原理分析

    首先看一道典型题 public class Test { static int x, y, z; static { int x = 5;//局部变量 x--; } static { x--; } pu ...

  5. bash的默认组合键

    组合键 组合按键 执行结果 Ctrl+C 终止目前的命令 Ctrl+D 输入结束(EOF),例如邮件结束的时候 Ctrl+M 就是Enter啦! Ctrl+S 暂停屏幕输出 Ctrl+Q 恢复屏幕输出 ...

  6. Frogger POJ - 2253(求两个石头之间”所有通路中最长边中“的最小边)

    题意 ​ 题目主要说的是,有两只青蛙,在两个石头上,他们之间也有一些石头,一只青蛙要想到达另一只青蛙所在地方,必须跳在石头上.题目中给出了两只青蛙的初始位置,以及剩余石头的位置,问一只青蛙到达另一只青 ...

  7. invalid expression: missing ) after argument list in xxx 或者 console.error(("[Vue warn]: " + msg + trace));

    效果图:   此处错误原因   中文输入法的 逗号 导致    :   解决方案: 改为 英文输入法的 逗号

  8. SpringBoot + Apache Shiro权限管理

    之前配置过Spring + SpringMVC + JPA + Shiro后台权限管理 + VUE前台登录页面的框架,手动配置各种.xml,比较繁琐,前几天写了个SpringBootShiro的Dem ...

  9. Scrapy定制起始请求

    Scrapy引擎来爬虫中取起始的URL 1.调用start_requests方法(父类),并获取返回值 2.将放回值变成迭代器,通过iter() 3.执行__next__()方法取值 4.把返回值全部 ...

  10. 针对MySQL的MVCC多版本并发控制的一些总结

    MVCC MVCC细节太多,我直接备忘一下总结: MVCC就是通过事务的ID与行数据的版本(修改事务的ID)进行比较(通过redo log可以回溯版本)得出哪些版本的行数据可见和不可见而实现的事务隔离 ...