WINDOWS钩子函数

来源:互联网 发布:平板软件下载 编辑:程序博客网 时间:2024/06/05 18:44

原文链接:http://blog.csdn.net/s_ongfei/article/details/3352729


本课中我们将要学习WINDOWS钩子函数的使用方法。WINDOWS钩子函数的功能非常强大,有了它您可以探测其它进程并且改变其它进程的行为。 

理论:
WINDOWS的钩子函数可以认为是WINDOWS的主要特性之一。利用它们,您可以捕捉您自己进程或其它进程发生的事件。通过“钩挂”,您可以给WINDOWS一个处理或过滤事件的回调函数,该函数也叫做“钩子函数”,当每次发生您感兴趣的事件时,WINDOWS都将调用该函数。一共有两种类型的钩子:局部的和远程的。 
局部钩子仅钩挂您自己进程的事件。 
远程的钩子还可以将钩挂其它进程发生的事件。远程的钩子又有两种: 
基于线程的 它将捕获其它进程中某一特定线程的事件。简言之,就是可以用来观察其它进程中的某一特定线程将发生的事件。 
系统范围的 将捕捉系统中所有进程将发生的事件消息。 
安装钩子函数将会影响系统的性能。监测“系统范围事件”的系统钩子特别明显。因为系统在处理所有的相关事件时都将调用您的钩子函数,这样您的系统将会明显的减慢。所以应谨慎使用,用完后立即卸载。还有,由于您可以预先截获其它进程的消息,所以一旦您的钩子函数出了问题的话必将影响其它的进程。记住:功能强大也意味着使用时要负责任。
在正确使用钩子函数前,我们先讲解钩子函数的工作原理。当您创建一个钩子时,WINDOWS会先在内存中创建一个数据结构,该数据结构包含了钩子的相关信息,然后把该结构体加到已经存在的钩子链表中去。新的钩子将加到老的前面。当一个事件发生时,如果您安装的是一个局部钩子,您进程中的钩子函数将被调用。如果是一个远程钩子,系统就必须把钩子函数插入到其它进程的地址空间,要做到这一点要求钩子函数必须在一个动态链接库中,所以如果您想要使用远程钩子,就必须把该钩子函数放到动态链接库中去。当然有两个例外:工作日志钩子和工作日志回放钩子。这两个钩子的钩子函数必须在安装钩子的线程中。原因是:这两个钩子是用来监控比较底层的硬件事件的,既然是记录和回放,所有的事件就当然都是有先后次序的。所以如果把回调函数放在DLL中,输入的事件被放在几个线程中记录,所以我们无法保证得到正确的次序。故解决的办法是:把钩子函数放到单个的线程中,譬如安装钩子的线程。
钩子一共有14种,以下是它们被调用的时机: 
WH_CALLWNDPROC 当调用SendMessage时 
WH_CALLWNDPROCRET 当SendMessage的调用返回时 
WH_GETMESSAGE 当调用GetMessage 或 PeekMessage时 
WH_KEYBOARD 当调用GetMessage 或 PeekMessage 来从消息队列中查询WM_KEYUP 或 WM_KEYDOWN 消息时 
WH_MOUSE 当调用GetMessage 或 PeekMessage 来从消息队列中查询鼠标事件消息时 
WH_HARDWARE 当调用GetMessage 或 PeekMessage 来从消息队列种查询非鼠标、键盘消息时 
WH_MSGFILTER 当对话框、菜单或滚动条要处理一个消息时。该钩子是局部的。它时为那些有自己的消息处理过程的控件对象设计的。 
WH_SYSMSGFILTER 和WH_MSGFILTER一样,只不过是系统范围的 
WH_JOURNALRECORD 当WINDOWS从硬件队列中获得消息时 
WH_JOURNALPLAYBACK 当一个事件从系统的硬件输入队列中被请求时 
WH_SHELL 当关于WINDOWS外壳事件发生时,譬如任务条需要重画它的按钮. 
WH_CBT 当基于计算机的训练(CBT)事件发生时 
WH_FOREGROUNDIDLE 由WINDOWS自己使用,一般的应用程序很少使用 
WH_DEBUG 用来给钩子函数除错 
现在我们知道了一些基本的理论,现在开始讲解如何安装/卸载一个钩子。 
要安装一个钩子,您可以调用SetWindowHookEx函数。该函数的原型如下: 
SetWindowsHookEx proto HookType:DWORD, pHookProc:DWORD, hInstance:DWORD, ThreadID:DWORD 
HookType 是我们上面列出的值之一,譬如: WH_MOUSE, WH_KEYBOARD 
pHookProc 是钩子函数的地址。如果使用的是远程的钩子,就必须放在一个DLL中,否则放在本身代码中 
hInstance 钩子函数所在DLL的实例句柄。如果是一个局部的钩子,该值为NULL 
ThreadID 是您安装该钩子函数后想监控的线程的ID号。该参数可以决定该钩子是局部的还是系统范围的。如果该值为NULL,那么该钩子将被解释成系统范围内的,那它就可以监控所有的进程及它们的线程。如果您指定了您自己进程中的某个线程ID 号,那该钩子是一个局部的钩子。如果该线程ID是另一个进程中某个线程的ID,那该钩子是一个全局的远程钩子。这里有两个特殊情况:WH_JOURNALRECORD 和 WH_JOURNALPLAYBACK总是代表局部的系统范围的钩子,之所以说是局部,是因为它们没有必要放到一个DLL中。WH_SYSMSGFILTER 总是一个系统范围内的远程钩子。其实它和WH_MSGFILTER钩子类似,如果把参数ThreadID设成0的话,它们就完全一样了。 
如果该函数调用成功的话,将在eax中返回钩子的句柄,否则返回NULL。您必须保存该句柄,因为后面我们还要它来卸载钩子。
要卸载一个钩子时调用UnhookWidowHookEx函数,该函数仅有一个参数,就是欲卸载的钩子的句柄。如果调用成功的话,在eax中返回非0值,否则返回NULL。
现在您知道了如何安装和卸载一个钩子了,接下来我们将看看钩子函数。. 
只要您安装的钩子的消息事件类型发生,WINDOWS就将调用钩子函数。譬如您安装的钩子是WH_MOUSE类型,那么只要有一个鼠标事件发生时,该钩子函数就会被调用。不管您安装的时那一类型钩子,钩子函数的原型都时是一样的: 
HookProc proto nCode:DWORD, wParam:DWORD, lParam:DWORD 

nCode 指定是否需要处理该消息 
wParam 和 lParam 包含该消息的附加消息 
HookProc 可以看作是一个函数名的占位符。只要函数的原型一致,您可以给该函数取任何名字。至于以上的几个参数及返回值的具体含义各种类型的钩子都不相同。譬如: 
WH_CALLWNDPROC 
nCode 只能是HC_ACTION,它代表有一个消息发送给了一个窗口 
wParam 如果非0,代表正被发送的消息 
lParam 指向CWPSTRUCT型结构体变量的指针 
return value: 未使用,返回0 
WH_MOUSE 
nCode 为HC_ACTION 或 HC_NOREMOVE 
wParam 包含鼠标的事件消息 
lParam 指向MOUSEHOOKSTRUCT型结构体变量的指针 
return value: 如果不处理返回0,否则返回非0值 
所以您必须查询您的WIN32 API 指南来得到不同类型的钩子的参数的详细定义以及它们返回值的意义。这里还有一个问题需要注意:所有的钩子都串在一个链表上,最近加入的钩子放在链表的头部。当一个事件发生时,WINDOWS将按照从链表头到链表尾调用的顺序。所以您的钩子函数有责任把消息传到下一个链中的钩子函数。当然您可以不这样做,但是您最好明白这时这么做的原因。在大多数的情况下,最好把消息事件传递下去以便其它的钩子都有机会获得处理这一消息的机会。调用下一个钩子函数可以调用函数CallNextHookEx。该函数的原型如下: 
CallNextHookEx proto hHook:DWORD, nCode:DWORD, wParam:DWORD, lParam:DWORD 
hHook 时是您自己的钩子函数的句柄。利用该句柄可以遍历钩子链。 
nCode, wParam and lParam 您只要把传入的参数简单传给CallNextHookEx即可。 
请注意:对于远程钩子,钩子函数必须放到DLL中,它们将从DLL中映射到其它的进程空间中去。当WINDOWS映射DLL到其它的进程空间中去时,不会把数据段也进行映射。简言之,所有的进程仅共享DLL的代码,至于数据段,每一个进程都将有其单独的拷贝。这是一个很容易被忽视的问题。您可能想当然的以为,在DLL中保存的值可以在所有映射该DLL的进程之间共享。在通常情况下,由于每一个映射该DLL的进程都有自己的数据段,所以在大多数的情况下您的程序运行得都不错。但是钩子函数却不是如此。对于钩子函数来说,要求DLL的数据段对所有的进程也必须相同。这样您就必须把数据段设成共享的,这可以通过在链接开关中指定段的属性来实现。在MASM中您可以这么做: 
/SECTION:<section name>, S
已初期化的段名是.data,未初始化的段名是.bss。`加入您想要写一个包含钩子函数的DLL,而且想使它的未初始化的数据段在所有进程间共享,您必须这么做: 
link /section:.bss,S /DLL /SUBSYSTEM:WINDOWS ..........
S 代表该段是共享段。

例子:
一共有两个模块:一个是GUI部分,另一个是安装和卸载钩子的DLL。 
;--------------------------------------------- 主程序的源代码部分-------------------------------------- 
.386 
.model flat,stdcall 
option casemap:none 
include /masm32/include/windows.inc 
include /masm32/include/user32.inc 
include /masm32/include/kernel32.inc 
include mousehook.inc 
includelib mousehook.lib 
includelib /masm32/lib/user32.lib 
includelib /masm32/lib/kernel32.lib

wsprintfA proto C :DWORD,:DWORD,:VARARG 
wsprintf TEXTEQU <wsprintfA>

.const 
IDD_MAINDLG equ 101 
IDC_CLASSNAME equ 1000 
IDC_HANDLE equ 1001 
IDC_WNDPROC equ 1002 
IDC_HOOK equ 1004 
IDC_EXIT equ 1005 
WM_MOUSEHOOK equ WM_USER+6

DlgFunc PROTO :DWORD,:DWORD,:DWORD,:DWORD

.data 
HookFlag dd FALSE 
HookText db "&Hook",0 
UnhookText db "&Unhook",0 
template db "%lx",0

.data? 
hInstance dd ? 
hHook dd ? 
.code 
start: 
invoke GetModuleHandle,NULL 
mov hInstance,eax 
invoke DialogBoxParam,hInstance,IDD_MAINDLG,NULL,addr DlgFunc,NULL 
invoke ExitProcess,NULL

DlgFunc proc hDlg:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD 
LOCAL hLib:DWORD 
LOCAL buffer[128]:byte 
LOCAL buffer1[128]:byte 
LOCAL rect:RECT 
.if uMsg==WM_CLOSE 
.if HookFlag==TRUE 
invoke UninstallHook 
.endif 
invoke EndDialog,hDlg,NULL 
.elseif uMsg==WM_INITDIALOG 
invoke GetWindowRect,hDlg,addr rect 
invoke SetWindowPos, hDlg, HWND_TOPMOST, rect.left, rect.top, rect.right, rect.bottom, SWP_SHOWWINDOW 
.elseif uMsg==WM_MOUSEHOOK 
invoke GetDlgItemText,hDlg,IDC_HANDLE,addr buffer1,128 
invoke wsprintf,addr buffer,addr template,wParam 
invoke lstrcmpi,addr buffer,addr buffer1 
.if eax!=0 
invoke SetDlgItemText,hDlg,IDC_HANDLE,addr buffer 
.endif 
invoke GetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer1,128 
invoke GetClassName,wParam,addr buffer,128 
invoke lstrcmpi,addr buffer,addr buffer1 
.if eax!=0 
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer 
.endif 
invoke GetDlgItemText,hDlg,IDC_WNDPROC,addr buffer1,128 
invoke GetClassLong,wParam,GCL_WNDPROC 
invoke wsprintf,addr buffer,addr template,eax 
invoke lstrcmpi,addr buffer,addr buffer1 
.if eax!=0 
invoke SetDlgItemText,hDlg,IDC_WNDPROC,addr buffer 
.endif 
.elseif uMsg==WM_COMMAND 
.if lParam!=0 
mov eax,wParam 
mov edx,eax 
shr edx,16 
.if dx==BN_CLICKED 
.if ax==IDC_EXIT 
invoke SendMessage,hDlg,WM_CLOSE,0,0 
.else 
.if HookFlag==FALSE 
invoke InstallHook,hDlg 
.if eax!=NULL 
mov HookFlag,TRUE 
invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText 
.endif 
.else 
invoke UninstallHook 
invoke SetDlgItemText,hDlg,IDC_HOOK,addr HookText 
mov HookFlag,FALSE 
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,NULL 
invoke SetDlgItemText,hDlg,IDC_HANDLE,NULL 
invoke SetDlgItemText,hDlg,IDC_WNDPROC,NULL 
.endif 
.endif 
.endif 
.endif 
.else 
mov eax,FALSE 
ret 
.endif 
mov eax,TRUE 
ret 
DlgFunc endp

end start

;----------------------------------------------------- DLL的源代码部分 -------------------------------------- 
.386 
.model flat,stdcall 
option casemap:none 
include /masm32/include/windows.inc 
include /masm32/include/kernel32.inc 
includelib /masm32/lib/kernel32.lib 
include /masm32/include/user32.inc 
includelib /masm32/lib/user32.lib

.const 
WM_MOUSEHOOK equ WM_USER+6

.data 
hInstance dd 0

.data? 
hHook dd ? 
hWnd dd ?

.code 
DllEntry proc hInst:HINSTANCE, reason:DWORD, reserved1:DWORD 
.if reason==DLL_PROCESS_ATTACH 
push hInst 
pop hInstance 
.endif 
mov eax,TRUE 
ret 
DllEntry Endp

MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD 
invoke CallNextHookEx,hHook,nCode,wParam,lParam 
mov edx,lParam 
assume edx:PTR MOUSEHOOKSTRUCT 
invoke WindowFromPoint,[edx].pt.x,[edx].pt.y 
invoke PostMessage,hWnd,WM_MOUSEHOOK,eax,0 
assume edx:nothing 
xor eax,eax 
ret 
MouseProc endp

InstallHook proc hwnd:DWORD 
push hwnd 
pop hWnd 
invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL 
mov hHook,eax 
ret 
InstallHook endp

UninstallHook proc 
invoke UnhookWindowsHookEx,hHook 
ret 
UninstallHook endp

End DllEntry

;---------------------------------------------- DLL的Makefile文件 ----------------------------------------------

NAME=mousehook 
$(NAME).dll: $(NAME).obj 
Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS /LIBPATH:c:/masm/lib $(NAME).obj 
$(NAME).obj: $(NAME).asm 
ml /c /coff /Cp $(NAME).asm 

分析:
该应用程序的主窗口中包括三个编辑控件,它们将分别显示当前鼠标光标所在位置的窗口类名、窗口句柄和窗口过程的地址。还有两个按钮:“Hook”和“Eixt”。当您按下Hook时,应用程序将钩挂鼠标输入的事件消息,该按钮的文本将变成“Unhook”。当您把鼠标关标滑过一个窗口时,该窗口的有关消息将显示在主窗口中。当您按下“Unhook”时,应用程序将卸载钩子。 主窗口使用一个对话框来作为它的主窗口。它自定义了一个消息WM_MOUSEHOOK,用来在主窗口和DLL之间传递消息。当主窗口接收到该消息时,wParam中包含了光标所在位置的窗口的句柄。当然这是我们做的安排。我这么做只是为了方便。您可以使用您自己的方法在主应用程序和DLL之间进行通讯。 
.if HookFlag==FALSE 
invoke InstallHook,hDlg 
.if eax!=NULL 
mov HookFlag,TRUE 
invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText 
.endif

该应用程序有一个全局变量,HookFlag,它用来监视钩子的状态。如果安装来钩子它就是TRUE,否则是FALSE。 当用户按下Hook按钮时,应用程序检查钩子是否已经安装。如果还没有的话,它将调用DLL中引出的函数InstallHook来安装它。注意我们把主对话框的句柄传递给了DLL,这样这个钩子DLL就可以把WM_MOUSEHOOK消息传递给正确的窗口了。当应用程序加载时,钩子DLL也同时加载。时机上当主程序一旦加载到内存中后,DLL就立即加载。DLL的入口点函数载主程序的第一条语句执行前就前执行了。所以当主程序执行时,DLL已经初始化好了。我们载入口点处放入如下代码:

.if reason==DLL_PROCESS_ATTACH 
push hInst 
pop hInstance 
.endif

该段代码把DLL自己的实例句柄放到一个全局变量中保存。由于入口点函数是在所有函数调用前被执行的,所以hInstance总是有效的。我们把该变量放到.data中,使得每一个进程都有自己一个该变量的值。因为当鼠标光标停在一个窗口上时,钩子DLL被映射进进程的地址空间。加入在DLL缺省加载的地址处已经加载其它的DLL,那钩子DLL将要被映射到其他的地址。hInstance将被更新成其它的值。当用户按下Unhook再按下Hook时,SetWindowsHookEx将被再次调用。这一次,它将把新的地址作为实例句柄。而在例子中这是错误的,DLL装载的地址并没有变。这个钩子将变成一个局部的,您只能钩挂发生在您窗口中的鼠标事件,这是很难让人满意的 。

InstallHook proc hwnd:DWORD 
push hwnd 
pop hWnd 
invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL 
mov hHook,eax 
ret 
InstallHook endp

InstallHook 函数非常简单。它把传递过来的窗口句柄保存在hWnd中以备后用。接着调用SetWindowsHookEx函数来安装一个鼠标钩子。该函数的返回值放在全局变量hHook中,将来在UnhookWindowsHookEx中还要使用。在调用SetWindowsHookEx后,鼠标钩子就开始工作了。无论什么时候发生了鼠标事件,MouseProc函数都将被调用:

MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD 
invoke CallNextHookEx,hHook,nCode,wParam,lParam 
mov edx,lParam 
assume edx:PTR MOUSEHOOKSTRUCT 
invoke WindowFromPoint,[edx].pt.x,[edx].pt.y 
invoke PostMessage,hWnd,WM_MOUSEHOOK,eax,0 
assume edx:nothing 
xor eax,eax 
ret 
MouseProc endp

钩子函数首先调用CallNextHookEx函数让其它的钩子处理该鼠标事件。然后,调用WindowFromPoint函数来得到给定屏幕坐标位置处的窗口句柄。注意:我们用lParam指向的MOUSEHOOKSTRUCT型结构体变量中的POINT成员变量作为当前的鼠标位置。在我们调用PostMessage函数把WM_MOUSEHOOK消息发送到主程序。您必须记住的一件事是:在钩子函数中不要使用SendMessage函数,它会引起死锁。MOUSEHOOKSTRUCT的定义如下:

MOUSEHOOKSTRUCT STRUCT DWORD 
pt POINT <> 
hwnd DWORD ? 
wHitTestCode DWORD ? 
dwExtraInfo DWORD ? 
MOUSEHOOKSTRUCT ENDS 

pt 是当前鼠标所在的屏幕位置。 
hwnd 是将接收鼠标消息的窗口的句柄。通常它是鼠标所在处的窗口,但是如果窗口调用了SetCapture,鼠标的输入将到向到这个窗口。因我们不用该成员变量而是用WindowFromPoint函数。 
wHitTestCode 指定hit-test值,该值给出了更多的鼠标位置值。它指定了鼠标在窗口的那个部位。该值的完全列表,请参考WIN32 API 指南中的WM_NCHITTEST消息。 
dwExtraInfo 该值包含了相关的信息。一般该值由mouse_event函数设定,可以调用GetMessageExtraInfo来获得。 

当主窗口接收到WM_MOUSEHOOK 消息时,它用wParam参数中的窗口句柄来查询窗口的消息。

.elseif uMsg==WM_MOUSEHOOK 
invoke GetDlgItemText,hDlg,IDC_HANDLE,addr buffer1,128 
invoke wsprintf,addr buffer,addr template,wParam 
invoke lstrcmpi,addr buffer,addr buffer1 
.if eax!=0 
invoke SetDlgItemText,hDlg,IDC_HANDLE,addr buffer 
.endif 
invoke GetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer1,128 
invoke GetClassName,wParam,addr buffer,128 
invoke lstrcmpi,addr buffer,addr buffer1 
.if eax!=0 
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer 
.endif 
invoke GetDlgItemText,hDlg,IDC_WNDPROC,addr buffer1,128 
invoke GetClassLong,wParam,GCL_WNDPROC 
invoke wsprintf,addr buffer,addr template,eax 
invoke lstrcmpi,addr buffer,addr buffer1 
.if eax!=0 
invoke SetDlgItemText,hDlg,IDC_WNDPROC,addr buffer 
.endif

为了避免重绘文本时的抖动,我们把已经在编辑空间中线时的文本和我们将要显示的对比。如果相同,就可以忽略掉。得到类名调用GetClassName,得到窗口过程调用GetClassLong并传入GCL_WNDPROC标志,然后把它们格式化成文本串并放到相关的编辑空间中去。

invoke UninstallHook 
invoke SetDlgItemText,hDlg,IDC_HOOK,addr HookText 
mov HookFlag,FALSE 
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,NULL 
invoke SetDlgItemText,hDlg,IDC_HANDLE,NULL 
invoke SetDlgItemText,hDlg,IDC_WNDPROC,NULL

当用户按下Unhook后,主程序调用DLL中的UninstallHook函数。该函数调用UnhookWindowsHookEx函数。然后,它把按钮的文本换回“Hook”,HookFlag的值设成FALSE再清除掉编辑控件中的文本。
链接器的开关选项如下:

Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS

它指定.bss段作为一个共享段以便所有映射该DLL的进程共享未初始化的数据段。如果不用该开关,您DLL中的钩子就不能正常工作了。

句柄概念在WINDOWS编程中是一个很重要的概念,在许多地方都扮演着重要的角色。但由此而产生的句柄概念也大同小异,比如:<<Microsoft Windows 3 Developer's Workshop>>(Microsoft Press,by Richard Wilton)一书中句柄的概念是:在Windows环境中,句柄是用来标识项目的,这些项目包括:

  *.模块(module)

  *.任务(task)

  *.实例(instance)

  *.文件(file)

  *.内存块(block of memory)

  *.菜单(menu)

  *.控制(control)

  *.字体(font)

  *.资源(resource),包括图标(icon),光标(cursor),字符串(string)等

  *.GDI对象(GDI object),包括位图(bitmap),画刷(brush),元文件(metafile),调色板(palette),画笔(pen),区域(region),以及设备描述表(device context)。

  WINDOWS程序中并不是用物理地址来标识一个内存块,文件,任务或动态装入模块的,相反的,WINDOWS API给这些项目分配确定的句柄,并将句柄返回给应用程序,然后通过句柄来进行操作。

  在<<WINDOWS编程短平快>>(南京大学出版社)一书中是这么说的:句柄是WINDOWS用来标识被应用程序所建立或使用的对象的唯一整数,WINDOWS使用各种各样的句柄标识诸如应用程序实例,窗口,控制,位图,GDI对象等等。WINDOWS句柄有点象C语言中的文件句柄。

  从上面的2个定义中的我们可以看到,句柄是一个标识符,是拿来标识对象或者项目的,它就象我们的姓名一样,每个人都会有一个,不同的人的姓名不一样,但是,也可能有一个名字和你一样的人。从数据类型上来看它只是一个16位的无符号整数。应用程序几乎总是通过调用一个WINDOWS函数来获得一个句柄,之后其他的WINDOWS函数就可以使用该句柄,以引用相应的对象。在WINDOWS编程中会用到大量的句柄,比如:HINSTANCE(实例句柄),HBITMAP(位图句柄),HDC(设备描述表句柄),HICON(图标句柄)等等,这当中还有一个通用的句柄,就是HANDLE,比如下面的语句:

  HINSTANCE hInstance;

  可以改成:

  HANDLE hInstance;

  上面的2句语句都是对的。

  一个WINDOWS应用程序可以用不同的方法获得一个特定项的句柄。许多API函数,诸如CreateWindow,GlobalAlloc,OpenFile的返回值都是一个句柄值。另外,WINDOWS也能通过应用程序的引出函数将一个句柄作为参数传送给应用程序,应用程序一旦获得了一个确定项的句柄,便可在WINDOWS环境下的任何地方对这个句柄进行操作。其实句柄的大量使用已经影响到了每一个WINDOWS的程序设计。

  句柄只有当唯一的确定了一个项目的时候,它才开始有意义。句柄对应着项目表中的一项,而只有WINDOWS本身才能直接存取这个表,应用程序只能通过API函数来处理不同的句柄,举个例子来说吧!比如:我们可以为我们的应用程序申请一块内存块,通过调用API函数GlobalAlloc,来返回一个句柄值:

  hMem=GlobalAlloc(......);

  其实现在hMem的值只是一个索引值,不是物理地址,应用程序还不能直接存取这块内存。这儿还有一个话外题,就是,一般情况下我们在编程的时候,给应用程序分配的内存都是可以移动的或者是可以丢弃的,这样能使有限的内存资源充分利用,所以,在某一个时候我们分配的那块内存的地址是不确定的,因为他是可以移动的,所以得先锁定那块内存块,这儿应用程序需要调用API函数GlobalLock函数来锁定句柄。如下:

  lpMem=GlobalLock(hMem);

  这样应用程序才能存取这块内存。

  注意:

  内核对象句柄,是用来标识某个内核对象的一个id

  同一个对象的该id对于每个进程是不同的,具体如何实现是ms不公开的算法,以下是一个近似的,可能的算法:

  进程创建时,windows系统为进程构造了一个句柄表

  当该进程希望获得一个内核对象句柄或者创建一个内核对象从而获得该对象句柄时

  系统会将在句柄表中增加一个表项,表项的内容中存储了指向目标内核对象的指针

  同时,系统返回这个表项在句柄表中的索引作为句柄

  

  这样,进程就通过句柄查询句柄表得到对象指针,从而可以访问该对象。

  同时又由于有了句柄表的保护,可以防止对内核对象的非法操作。

  我想现在大家已经能对句柄概念有所了解了,我希望我的文章能对大家有所帮助。其实如果你学过SDK编程,那对句柄的概念理解会更好,更深。如果你是直接学VC6的MFC编程的,建议你看一下SDK编程,这会对你大有好处。

有关中断的概念
  什么是中断,我们从一个生活中的例子引入。你正在家中看书,突然电话铃响了,你放下书本,去接电话,和来电话的人交谈,然后放下电话,回来继续看你的书。这就是生活中的“中断”的现象,就是正常的工作过程被外部的事件打断了。

  仔细研究一下生活中的中断,对于我们学习单片机的中断也很有好处。第一、什么可经引起中断,生活中很多事件可以引起中断:有人按了门铃了,电话铃响了,你的闹钟闹响了,你烧的水开了….等等诸如此类的事件,我们把可以引起中断的称之为中断源,单片机中也有一些可以引起中断的事件,8031中一共有5个:两个外部中断,两个计数/定时器中断,一个串行口中断。

 第二、中断的嵌套与优先级处理:设想一下,我们正在看书,电话铃响了,同时又有人按了门铃,你该先做那样呢?如果你正是在等一个很重要的电话,你一般不会去理会门铃的,而反之,你正在等一个重要的客人,则可能就不会去理会电话了。如果不是这两者(即不等电话,也不是等人上门),你可能会按你通常的习惯去处理。总之这里存在一个优先级的问题,单片机中也是如此,也有优先级的问题。优先级的问题不仅仅发生在两个中断同时产生的情况,也发生在一个中断已产生,又有一个中断产生的情况,比如你正接电话,有人按门铃的情况,或你正开门与人交谈,又有电话响了情况。考虑一下我们会怎么办吧。

 第三、中断的响应过程:当有事件产生,进入中断之前我们必须先记住现在看书的第几页了,或拿一个书签放在当前页的位置,然后去处理不同的事情(因为处理完了,我们还要回来继续看书):电话铃响我们要到放电话的地方去,门铃响我们要到门那边去,也说是不同的中断,我们要在不同的地点处理,而这个地点通常还是固定的。计算机中也是采用的这种方法,五个中断源,每个中断产生后都到一个固定的地方去找处理这个中断的程序,当然在去之前首先要保存下面将执行的指令的地址,以便处理完中断后回到原来的地方继续往下执行程序。具体地说,中断响应可以分为以下几个步骤:1、保护断点,即保存下一将要执行的指令的地址,就是把这个地址送入堆栈。2、寻找中断入口,根据5个不同的中断源所产生的中断,查找5个不同的入口地址。以上工作是由计算机自动完成的,与编程者无关。在这5个入口地址处存放有中断处理程序(这是程序编写时放在那儿的,如果没把中断程序放在那儿,就错了,中断程序就不能被执行到)。3、执行中断处理程序。4、中断返回:执行完中断指令后,就从中断处返回到主程序,继续执行。

究竟单片机是怎么样找到中断程序所在位置,又怎么返回的呢?我们稍后再谈.

MCS-51中断系统的结构:
 

  由与中断有关的特殊功能寄存器、中断入口、顺序查询逻辑电路等组成,包括5个中断请求源,4个用于中断控制的寄存器IE、IP、ECON和SCON来控制中断 类弄、中断的开、关和各种中断源的优先级确定。


中断请求源:
 

  (1)外部中断请求源:即外中断0和1,经由外部引脚引入的,在单片机上有两个引脚,名称为INT0、INT1,也就是P3.2、P3.3这两个引脚。在内部的TCON中有四位是与外中断有关的。
IT0:INT0触发方式控制位,可由软件进和置位和复位,IT0=0,INT0为低电平触发方式,IT0=1,INT0为负跳变触发方式。这两种方式的差异将在以后再谈。
IE0:INT0中断请求标志位。当有外部的中断请求时,这位就会置1(这由硬件来完成),在CPU响应中断后,由硬件将IE0清0。
IT1、IE1的用途和IT0、IE0相同。

  (2)内部中断请求源
TF0:定时器T0的溢出中断标记,当T0计数产生溢出时,由硬件置位TF0
。当CPU响应中断后,再由硬件将TF0清0。

TF1:与TF0类似。

TI、RI:串行口发送、接收中断,在串口中再讲解。

2、中断允许寄存器IE

在MCS-51中断系统中,中断的允许或禁止是由片内可进行位寻址的8位
中断允许寄存器IE来控制的。见下表

 

EA
x
x
ES
ET1
EX1
ET0
EX0

 

其中EA是总开关,如果它等于0,则所有中断都不允许。

ES-串行口中断允许

ET1-定时器1中断允许

EX1-外中断1中断允许。

ET0-定时器0中断允许

EX0-外中断0中断允许。

如果我们要设置允许外中断1,定时器1中断允许,其它不允许,则IE可以是

EA
x
x
ES
ET1
EX1
ET0
EX0
1
0
0
0
1
1
0
0


即8CH,当然,我们也可以用位操作指令

SETB EA
SETB ET1

SETB EX1

来实现它。

3、五个中断源的自然优先级与中断服务入口地址

外中断0:0003H

定时器0:000BH

外中断1:0013H

定时器1:001BH

串口 :0023H

它们的自然优先级由高到低排列。

写到这里,大家应当明白,为什么前面有一些程序一始我们这样写:

ORG 0000H

LJMP START

ORG 0030H

START:


  这样写的目的,就是为了让出中断源所占用的向量地址。当然,在程序中没用中断时,直接从0000H开始写程序,在原理上并没有错,但在实际工作中最好不这样做。
 

  优先级:单片机采用了自然优先级和人工设置高、低优先级的策略,即可以由程序员设定那些中断是高优先级、哪些中断是低优先级,由于只有两级,必有一些中断处于同一级别,处于同一级别的,就由自然优先级确定。
开机时,每个中断都处于低优先级,我们可以用指令对优先级进行设置。看表2
中断优先级中由中断优先级寄存器IP来高置的,IP中某位设为1,相应
的中断就是高优先级,否则就是低优先级。

x
x
x
PS
PT1
PX1
PT0
PX0

 

例:设有如下要求,将T0、外中断1设为高优先级,其它为低优先级,求IP的值。

IP的首3位没用,可任意取值,设为000,后面根据要求写就可以了

x
x
x
PS
PT1
PX1
PT0
PX0
0
0
0
0
0
1
1
0

因此,最终,IP的值就是06H。
例:在上例中,如果5个中断请求同时发生,求中断响应的次序。
响应次序为:定时器0->外中断1->外中断0->实时器1->串行中断。
 

MCS-51的中断响应过程:
  1、中断响应的条件:讲到这儿,我们依然对于计算机响应中断感到神奇,我们人可以响应外界的事件,是因为我们有多种“传感器“――眼、耳可以接受不同的信息,计算机是如何做到这点的呢?其实说穿了,一点都不希奇,MCS51工作时,在每个机器周期中都会去查询一下各个中断标记,看他们是否是“1“,如果是1,就说明有中断请求了,所以所谓中断,其实也是查询,不过是每个周期都查一下而已。这要换成人来说,就相当于你在看书的时候,每一秒钟都会抬起头来看一看,查问一下,是不是有人按门铃,是否有电话。。。。很蠢,不是吗?可计算机本来就是这样,它根本没人聪明。了解了上述中断的过程,就不难解中断响应的条件了。在下列三种情况之一时,CPU将封锁对中断的响应:

CPU正在处理一个同级或更高级别的中断请求。
 

  现行的机器周期不是当前正执行指令的最后一个周期。我们知道,单片机有单周期、双周期、三周期指令,当前执行指令是单字节没有关系,如果是双字节或四字节的,就要等整条指令都执行完了,才能响应中断(因为中断查询是在每个机器周期都可能查到的)。当前正执行的指令是返回批令(RETI)或访问IP、IE寄存器的指令,则CPU至少再执行一条指令才应中断。这些都是与中断有关的,如果正访问IP、IE则可能会开、关中断或改变中断的优先级,而中断返回指令则说明本次中断还没有处理完,所以都要等本指令处理结束,再执行一条指令才可以响应中断。 2、中断响应过程
 

  CPU响应中断时,首先把当前指令的下一条指令(就是中断返回后将要执行的指令)的地址送入堆栈,然后根据中断标记,将相应的中断入口地址送入PC,PC是程序指针,CPU取指令就根据PC中的值,PC中是什么值,就会到什么地方去取指令,所以程序就会转到中断入口处继续执行。这些工作都是由硬件来完成的,不必我们去考虑。这里还有个问题,大家是否注意到,每个中断向量地址只间隔了8个单元,如0003-000B,在如此少的空间中如何完成中断程序呢?很简单,你在中断处安排一个LJMP指令,不就可以把中断程序跳转到任何地方了吗? 一个完整的主程序看起来应该是这样的:

ORG 0000H

LJMP START

ORG 0003H

LJMP INT0 ;转外中断0

ORG 000BH
。 
RETI ;没有用定时器0中断,在此放一条RETI,万一 “不小心“产生了中断,也不会有太大的后果。

  中断程序完成后,一定要执行一条RETI指令,执行这条指令后,CPU将会把堆栈中保存着的地址取出,送回PC,那么程序就会从主程序的中断处继续往下执行了。注意:CPU所做的保护工作是很有限的,只保护了一个地址,而其它的所有东西都不保护,所以如果你在主程序中用到了如A、PSW等,在中断程序中又要用它们,还要保证回到主程序后这里面数据还是没执行中断以前的数据,就得自己

原创粉丝点击