Windows中有一个系统消息队列,对于每一个正在执行的Windows应用程序,系统为其建立一个“消息队列”,即应用程
序消息队列,用来存放该程序可能创建的各种窗口的消息。应用程序中含有一段称作“消息循环”的代码,用来从消息队列中
检索这些消息并把它们分发到相应的窗口函数中。
在windows中,“一切皆为窗口”。虽然不是很贴切,但是说明了窗口的普遍性和其重要性。比如,你现在看到的QQ聊天窗口,就是由多个窗口组成的!
Windows程序是由一系列的窗口构成的,每个窗口都有自己的窗口过程。
消息就是一组数据包(结构体),用于传递信息。消息可以在操作系统和一个进程之间传递,也可以在两个不同的进程间传递,也可以在同一个进程的不同线程间传递或同一个进程的不同窗口间传递。 比如,你在QQ聊天窗口中点一下鼠标,打字等,都会产生消息。在代码中消息是以消息结构体MSG来保存的,里面的成员包括处理该消息的窗口句柄,消息代码,消息产生的时间和光标的位置等。
<1>.队列消息和非队列消息:从消息的发送途径上看,消息分两种:队列消息和非队列消息。 队列消息送到系统消息队列,然后到线程消息队列;非队列消息直接送给目的窗口过程。这里,对消息队列阐述如下
Windows维护一个系统消息队列(System message queue),每个GUI线程有一个线程消息队列(Thread message queue)。鼠标、键盘事件由鼠标或键盘驱动程序转换成输入消息并把消息放进系统消息队列,例如WM_MOUSEMOVE、WM_LBUTTONUP、WM_KEYDOWN、WM_CHAR等等。Windows每次从系统消息队列移走一个消息,确定它是送给哪个窗口的和这个窗口是由哪个线程创建的,然后,把它放进窗口创建线程的线程消息队列。线程消息队列接收送给该线程所创建窗口的消息。线程从消息队列取出消息,通过Windows把它送给适当的窗口过程来处理。
除了键盘、鼠标消息以外,队列消息还有WM_PAINT、WM_TIMER和WM_QUIT。这些队列消息以外的绝大多数消息是非队列消息。
<2>统消息和应用程序消息: 从消息的来源来看,可以分为:系统定义的消息和应用程序定义的消息。(控件改变时自己也会向系统发送消息
系统消息ID的范围是从0到WM_USER-1,或0X80000到0XBFFFF;应用程序消息从WM_USER(0X0400)到0X7FFF,或0XC000到0XFFFF;WM_USER到0X7FFF范围的消息由应用程序自己使用;0XC000到0XFFFF范围的消息用来和其他应用程序通信,为了ID的唯一性,使用::RegisterWindowMessage来得到该范围的消息ID。
<3>窗口消息,命令消息,控件通知消息:根据处理过程的不同,可以分为三类:窗口消息,命令消息,控件通知消息
(1).窗口消息
一般以WM_开头,如WM_CREATE, WM_SIZE, WM_MOUSEMOVE等标准的Windows消息, 用于窗口相关的事件通知,窗口消息将由系统分配到该窗口的窗口过程处理。
(2).命令消息 (WM_COMMAND)
一种特殊的窗口消息,它从一个窗口发送到另一个窗口以处理来自用户的请求,通常是从子窗口发送到父窗口,例如,点击按钮时,按钮的父窗口会收到WM_COMMAND消息,用以通知父窗口按钮被点击,经测试:子窗口向父窗口发送WM_COMMAND消息,或者称为父窗口会收到WM_COMMAND消息,操作系统并不是通过将WM_COMMAND消息放入到父窗口的消息队列中去,而是直接调用了父窗口的窗口过程,以 WM_COMMAND 为消息标识参数(UINT uMsg),实现这个功能的API函数正是: LRESULT DispatchMessage(const MSG *lpmsg);
(3).控件通知消息
WM_NOTIFY消息,当用户与控件交互(Edit, Button)时,通知消息会从控件窗口发送到父窗口,这种消息的目的不是为了处理用户命令,而是为了让父窗 口能够适时的改变控件。
MSG 结构体
typedef struct tagMSG {
HWND hwnd;
UINT message; //消息ID
WPARAM wParam;
LPARAM lParam;
DWORD time; //时间(消息投递到消息队列的时间)
POINT pt; //鼠标的当前位置
#ifdef _MAC
DWORD lPrivate;
#endif
} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;
从消息队列中获得消息,并移除,无消息时,等待,,如果得到WM_QUIT消息返回的0,结束循环
原型:
BOOL
WINAPI
GetMessageW(
_Out_ LPMSG lpMsg,
_In_opt_ HWND hWnd,
_In_ UINT wMsgFilterMin,
_In_ UINT wMsgFilterMax);
大小写键盘翻译
例子:
TranslateMessage(&msg);
把消息发送到指定窗口,直接给消息处理函数,直到消息处理函数返回结束,堵塞函数(必须等消息处理完成)
原型:
LRESULT
WINAPI
SendMessageW(
_In_ HWND hWnd,
_In_ UINT Msg, //消息
_Pre_maybenull_ _Post_valid_ WPARAM wParam,
_Pre_maybenull_ _Post_valid_ LPARAM lParam);
例子:
SendMessage(hWnd, WM_CLOSE, 0, 0); //WM_DESTROY 是关闭程序的 M_CLOSE 是关闭窗口的 WM_QUIT 是关闭消息环的
SendMessage为同步,PostMessage为异步,GetMessage只处理第一个链表即SentMessagesListHead里面的消息
当一个程序利用SendMessage向另外一个程序发送消息时,另外一个程序会用GetMessage接收,这个过程GetMessage会在0环的SentMessagesListHead链表里面搜索是否存在SendMessage,如果存在SendMessage,GetMessage就会在两个程序的共享内存里面向发送消息的程序发送一个结果,在这个过程中,发送消息的程序是一直处于等待状态的,只有接收到返回的消息才会结束,这称为同步
如果利用PostMessage发送消息,处于第二个链表里面,GetMessage不会处理,而程序发完消息之后也会立即结束,不会有等待的过程,这成为异步,如果要处理,使用DispatchMessage()处理
MSG msg;
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
取得的消息将交由窗口处理函数进行处理,对于每个窗口类Windows 为我们预备了一个
默认的窗口过程处理函数DefWindowProc(),这样做的好处是,我们可以着眼于我们感兴趣
的消息,把其他不感兴趣的消息传递给默认窗口过程函数进行处理。每一个窗口类都有一个
窗口过程函数,此函数是一个回调函数,它是由Windows 操作系统负责调用的,而应用程
序本身不能调用它。以switch 语句开始,对于每条感兴趣的消息都以一个case 引出。
LRESULT CALLBACK WndProc
(
HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam
)
{
…
switch(uMsgId){
case WM_TIMER://对WM_TIMER 定时器消息的处理过程
return 0;
case WM_LBUTTONDOWN://对鼠标左键单击消息的处理过程
reurn 0;
. …
default:
return DefWindowProc(hwnd,uMsgId,wParam,lParam);
}
}
对于每条已经处理过的消息都必须返回0,否则消息将不停的重试下去;对于不感兴趣
的消息,交给DefWindowProc()函数进行处理,并需要返回其处理值。
消息队列的结构
<1> SentMessagesListHead //接到SendMessage发来的消息
<2> PostedMessagesListHead //接到PostMessage发来的消息
<3> HardwareMessagesListHead //接到鼠标、键盘的消息
如果要取所有队列的消息,则第二个参数设置为NULL,后两个参数全部设置为0
GetMessage的主要功能:循环判断是否有该窗口的消息,如果有,将消息存储到MSG指定的结构,并将消息从列表中删除
GetMessage( LPMSG lpMsg, //返回从队列中摘下来的消息
HWND hWnd, //过滤条件一:发个这个窗口的消息
UNIT wMsgFilterMin, //过滤条件
UNIT wMsgFilterMax //过滤条件
);
使用vs2010创建的一个windows窗口程序。
代码如下:
#include
#include
//包含应用程序中数据类型和数据结构的定义
long CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
//窗口说明
// WinMain函数是所有Windows应用程序的入口,类似c语言中的main函数 其功能是完成//一系列的定义和初始化,并产生消息循环
// WinMain函数实现以下功能:注册窗口类,建立窗口及执行其他必要的初始化工作;进入消息循环,根据从应用程序消息队列接受的消息,调用相应的处理过程;当消息循环检
//测到WM_QUIT消息时终止程序运行
// WinMain函数有三个基本部分组成:函数说明、初始化和消息循环*/
//函数说明
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
HWND hwnd;
MSG Msg;
WNDCLASS wndclass;
char lpszClassName[] = "窗口"; //窗口类名
char lpszTitle[] = "test"; //窗口标题名
//窗口类定义
//窗口类定义了窗口的形式与功能 窗口类定义通过给窗口类数据结构WNDCLASS赋值完成
//该数据结构中包含窗口类的各种属性
wndclass.style = 0; // 窗口类型为缺省类型
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 = lpszClassName; //窗口类名为“窗口”
if (!RegisterClass(&wndclass))
{
MessageBeep(0);
return FALSE;
}
///创建窗口 创建一个窗口的实例由函数CreateWindow()实现
hwnd = CreateWindow(lpszClassName, lpszTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL,
hInstance, NULL);
//显示窗口
ShowWindow(hwnd, nCmdShow);
//绘制用户区
UpdateWindow(hwnd);
//消息循环
while (GetMessage(&Msg, NULL, 0, 0)) {
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam; //消息循环结束 即程序结束时 将信息返回系统
}
//窗口函数
//窗口函数定义了应用程序对接收到的不同消息的响应,其中包含了应用程序对各种可能接受到的消息的
//处理过程,是消息处理分支控制语句的集合
long CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WM_DESTROY:
PostQuitMessage(0);
break;
case 256: //WM_KEYUP = 256按下一个键
exit(0);
break;
default:
return DefWindowProc(hwnd, message, wParam, lParam);
}
return (0);
}
和其它消息通信的区别:
WM_* 只用于windows预定义的消息,都有特定的含义;
WM_USER+* 只能用于窗口过程(即通信目标必须是窗口类注册的窗口),否则无效;
WM_COPYDATA 是一个特殊的WM_*消息,但只用于同步通信,必须使用SendMessage。
注册消息:比WM_USER+*更灵活,常用于多个进程同时处理统一个消息(注意是进程而不只是窗口)。
处于同一台电脑的进程A,要给进程B发数据。但是进程B的主窗口在创建时,没有指定类名,也没有指定窗口名,所以无法通过::FindWindow()来获得窗口句柄,从而发消息。后来解决办法是通过HWND_BROADCAST广播消息。
发送一个窗口关闭消息
const UINT WM_WSW_ShowQRCode = ::RegisterWindowMessage(_T("WSW_ShowQRCode_Function"));
PostMessage(HWND_BROADCAST, WM_CLOSE, 0 , 0);
//首先需要注册一个与之对应的自定义消息
const UINT WM_WSW_ShowQRCode = ::RegisterWindowMessage(_T("WSW_ShowQRCode"));
//消息响应
ON_REGISTERED_MESSAGE(WM_WSW_ShowQRCode, OnShowQRCode_WSW)
总结:HWND_BROADCAST,是对所有通过 RegisterWindowMessage方法注册了同一个消息的窗口,都发送广播(此文中为,对所有注册了WSW_ShowQRCode消息的窗口,都广播)。对于无法获得窗口的句柄时,才采用此方法。
特点:
基于操作系统才能运行;
GUI应用程序提供的功能必须由用户触发;
用户操作界面时操作系统是第一个感知的 ;
系统内核的消息通过事件处理转变成QT的信号。
在Qt中,事件被封装成一个个对象,所有的事件均继承自抽象类QEvent. 事件处理的核心包括事件产生、分发、接受和处理。
问题:谁来产生事件?
最容易想到的是我们的输入设备,比如键盘、鼠标产生的keyPressEvent,keyReleaseEvent,mousePressEvent,mouseReleaseEvent事件(他们被封装成QMouseEvent和QKeyEvent)。
问题:谁来负责分发事件?
对于non-GUI的Qt程序,是由QCoreApplication负责将QEvent分发给QObject的子类-Receiver.;对于Qt GUI程序,由QApplication来负责。
问题:谁来接受和处理事件?
答案是QObject。类是整个Qt对象模型的心脏,事件处理机制是QObject三大职责(内存管理、内省(intropection)与事件处理制)之一。任何一个想要接受并处理事件的对象均须继承自QObject,可以选择重载QObject::event()函数或事件的处理权转给父类。
A. Qt事件是一个QEvent(或子类)的对象;
B. 有时一个事件包含多个事件类型,比如鼠标事件又可以分为鼠标按下、双击、和移动多种操作事件类型由QEvent类的枚举型QEvent::Type来表示,可由帮助文档进行查询;
C. Qt事件用于描述程序内部或外部发生的对应动作(描述的是操作系统发生来的消息,一个系统消息对应着一个消息事件);
D. 任意QObject对象都具备时间处理的能力.
Qt 程序需要在main()函数创建一个QApplication对象,然后调用它的exec()函数。这个函数就是开始 Qt 的事件循环。在执行exec()函数之后,程序将进入事件循环来监听应用程序的事件。当事件发生时,Qt 将创建一个事件对象。Qt 中所有事件类都继承于QEvent。在事件对象创建完毕后,Qt 将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是将这些事件对象按照它们不同的类型,分发给不同的事件处理器(event handler)。如上所述,event()函数主要用于事件的分发。
方法1:
// 通过继承QWidget的类中重新实现winEvent接口,以接收在消息参数中传递的本机Windows事件。
bool QWidget::winEvent(MSG *message, long *result)
方法2:
// 通过继承QCoreApplication的类中重新实现winEventFilter接口,以接收在消息参数中传递的本机Windows事件。
bool QCoreApplication::winEventFilter(MSG *msg, long *result)
方法1:
// 通过继承QWidget的类中重新实现winEvent接口,以接收在消息参数中传递的eventType标识的本机平台事件。
bool QWidget::nativeEvent(const QByteArray &eventType, void *message, long *result)
方法2:
// 通过继承QAbstractNativeEventFilter的类中重新实现nativeEventFilter接口:
bool QAbstractNativeEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, long *result)
并安装到中:
void QCoreApplication::installNativeEventFilter(QAbstractNativeEventFilter *filterObj)
或安装到:
void QAbstractEventDispatcher::installNativeEventFilter(QAbstractNativeEventFilter *filterObj)
特别地:不同平台对应的eventType类型有:
平台 事件类型(eventType) 消息类型(message) 结果类型(result)
Windows “windows_generic_MSG” MSG * LRESULT
macOs “NSEvent” NSEvent * 无
XCB(Linux) “xcb_generic_event_t” xcb_generic_event_t * 无
// 继承Qt的基类QAbstractNativeEventFilter
class HHNativeEventFilter : public QAbstractNativeEventFilter
{
protected:
bool nativeEventFilter(const QByteArray &eventType, void *message, long *)
{
if (eventType == "windows_generic_MSG"
|| eventType == "windows_dispatcher_MSG")
{
PMSG msg = static_cast(message);
if(msg->message == WM_CLOSE )
{
qApp->exit();
}
}
return false;
}
};
// 利用QApplication注册类对象
app.installNativeEventFilter(new NativeEventFilter);
在运行程序时要对某些程序进程进行终止操作,可以使用kill命令和对应的pid号进行处理,这种方法对于后台运行的程序特别有用:
ps -a 列出所有进程:
PID TTY TIME CMD
3946 pts/20 00:23:11 python
3523 pts/27 00:10:00 ps
或者使用管道来获取对应应用程序的进程号:
ps | grep python
3946 pts/20 00:13:55 python
随后就可以使用kill来关闭这一程序了:
kill -9 2946
但是,kill命令除了-9外还有很多的用途:
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]
在终端中输入kill -l会看到除了-9外还有很多其他的信号:
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
其中9是立即结束进程的信号不能被阻塞处理,而安全结束进程的信号可以使用SIGTERM(15),这个信号可以被阻塞处理。
需要注意的是:
默认情况下, kill或pkill命令发送的信号是SIGTERM(15) 。
kill -9或pkill -9将发送SIGKILL信号。 不能捕获或忽略 SIGKILL或SIGSTOP信号。
常用的Ctrl+C 发出的是SIGKILL信号。
signal 函数比较老了,功能有一些限制,现在常用的是 sigaction, 仅在信号处理程序中使用异步信号安全的函数。
// 第一个参数为目标信号,第二个参数为sigaction结构,内有处理机制,信号掩码,和标志
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
#include
#include
#include
#include
#include
#define SIGTERM_MSG "SIGTERM received.\n"
void sig_term_handler(int signum, siginfo_t *info, void *ptr)
{
write(STDERR_FILENO, SIGTERM_MSG, sizeof(SIGTERM_MSG));
}
void catch_sigterm()
{
static struct sigaction _sigact;
memset(&_sigact, 0, sizeof(_sigact));
_sigact.sa_sigaction = sig_term_handler;
_sigact.sa_flags = SA_SIGINFO;
sigaction(SIGTERM, &_sigact, NULL);
}
void main()
{
catch_sigterm();
printf("I am sleeping...\n");
sleep(3000);
}