《Windows核心编程》---动态链接库和钩子基础

动态链接库是应用程序的一个模块,这个模块用于导出一些函数和数据以供其他程序中的其他模块调用:

1)动态链接库是应用程序的一部分,是作为模块被进程加载到自己的空间地址中;

2)动态链接库在程序编译时并不会被插入到可执行文件中,在程序运行时这个库的代码才会调入内存,即“动态链接”;

3)若有多个程序用到同一个动态链接库,Windows在物理内存中只保留一份库的代码,仅通过分页机制将这份代码映射到不同的进程中;

 

动态链接库的扩展名一般是dll,但也可以是某些exe、各种控件(*.ocx)等。

 

动态链接库的入口点函数是DllMain函数,它仅供操作系统使用,Windows在库装载、卸载、进程中线程创建和结束时调用入口函数,以便动态链接库采取相应的动作;DllMain函数的框架结构为:

         BOOL APIENTRY DllMain(HANDLE hModule,                  //本模块句柄

                                                                                    DWORD ul_reason_for_call,        //调用的原因

                                                                                    LPVOID lpReserved)     //没有被使用

         {

                   switch(ul_reason_for_call)

                   {

                   case DLL_PROCESS_ATTACH:     //动态链接库刚被映射到某个进程的地址空间

 

                   case DLL_THREAD_ATTACH:               //应用程序创建了一个新的线程

 

                   case DLL_THREAD_DETACH:               //应用程序某个线程正常终止

 

                   case DLL_PROCESS_DETACH:     //动态链接库将被卸载

 

                   default:

                            break;

                   }

                   return TRUE;

         }

hModule参数是本DLL模块的句柄,即本动态链接库模块的实例句柄,数值上是系统将DLL文件映象加载到进程的地址空间时使用的基地址。

注意:在动态链接库中,通过GetModuleHandle(NULL)语句得到的是主模块(即可执行文件映象)的基地址,而不是DLL文件映象的基地址。

 

DLL可以定义两种函数:导出函数和内部函数。导出函数可以被其他模块调用,也可以被定义这个函数的模块调用,而内部函数当然只能被定义这个函数的模块调用。

动态链接库的主要功能是向外导出函数,供进程中其他模块使用。一般会生成3个主要的文件:.dll文件就是动态链接库,.lib文件时供程序开发用的导入库,.h文件包含了导出函数的声明。

 

调用DLL中的导出函数有两种方法:

1)装载期间动态链接:必须使用.lib文件,它为系统提供了加载这个DLL和定位DLL中的导出函数所需的信息;(上述3个文件都要用到)

实例代码如下:

#include <windows.h>

#include "acedll.h"

//指明要链接到acedll.lib库,也可以不用#pragma

//直接将acedll.lib文件添加到工程中也是一样的

#pragma comment(lib, "acedll")

 

void main()

{

         //像调用本地函数一样调用acedll.lib库中的导出函数Export(..)

         Export("SEE YOU IN ANOTHER LIFE BROTHER!");

}

这种方法的缺点是:如果用户丢失了DLL文件,那么程序是永远也不能启动的了。因此,很多时候要采用运行期间动态链接的方法,以便决定DLL加载失败后如何处理。

 

2)运行期间动态加载:使用LoadLibraryLoadLibraryEx函数在运行期间加载这个DLLDLL被加载后,加载模块调用GetProcAddress函数取得DLL导出函数的地址,再通过该函数地址调用DLL中的函数。(只需要.dll文件就行)为了实现这个方法,一般需要在工程中建立一个DEF文件,用来指定要导出的函数:

EXPORTS

         Export

表示此DLL库要向外导出Export函数。

实例代码如下:

#include <windows.h>

//声明函数原型

typedef void(*PFNEXPORT)(LPCTSTR);

int main(int argc, char* argv[])

{

         //加载DLL

         HMODULE hModule = ::LoadLibrary("acedll.dll");

         if(hModule != NULL)

         {

                   //取得Export函数的地址

                   PFNEXPORT mExport = (PFNEXPORT)::GetProcAddress(hModule, "Export");

                   if(mExport != NULL)

                   {

                            mExport("SEE YOU IN ANOTHER LIFE BROTHER!");

                   }

                   //卸载DLL

                   ::FreeLibrary(hModule);

         }

         return 0;

}

LoadLibrary函数的作用是加载指定目录下的DLL库到进程的虚拟地址空间,函数执行成功返回此DLL模块的句柄,否则返回NULL。事实上,载入器也是调用这个函数加载DLL的。

 

 

=====================JIN的分割线===================

Windows应用程序的运行模式是基于消息驱动的,任何线程只要注册了窗口类,都会有一个消息队列来接收用户的输入消息和系统消息。而为了取得特定线程接收或发送的消息,就要用到Windows提供的钩子。

钩子是Windows消息处理机制的一个监视点,应用程序可以在这里安装一个钩子函数以监视指定窗口某种类型的消息,所监视的窗口可以是其他进程创建的。钩子函数通过调用相关的API函数,把它挂入系统,每当特定消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,这时钩子函数既可以加工处理该消息,也可以不作处理而继续传递该消息。


关于Windows钩子需要知道以下几点:

1)钩子是用来截获系统中的消息流的;

2)截获消息后,用来处理消息的子程序叫做钩子函数,它是应用程序自定义的一个函数,在安装钩子时要把这个函数的地址告知Windows

3)系统中同一时间可能有多个进程安装了钩子,多个钩子函数在一起组成钩子链;因此,在处理截获到的消息后,应该把消息事件传递下去。

 

系统为每一种钩子建立一个钩子链(HOOK CHAIN),一个钩子链实际上就是一个指针列表,其指针指向钩子的各个处理函数,这些函数是一种特殊的回调函数。像堆栈一样,在钩子链中最后安装的钩子函数放在钩子链的最顶层,最先安装的钩子则放在钩子链的最底层,Windows系统负责记录和管理这条函数链。系统的各种消息首先被送到最顶层的钩子函数,即最后加入的钩子优先获得控制权。


钩子按事件分类主要有以下几种类型:

1)键盘钩子和低级键盘钩子:监视各种键盘消息;

2)外壳钩子:监视各种Shell事件消息,如启动和关闭应用程序等;

3)鼠标钩子和低级鼠标钩子:监视各种鼠标消息;

4)窗口过程钩子:监视所有从系统消息队列发往目标窗口的消息;

5)日志钩子:即如从系统消息队列中取出的各种事件消息。

 

安装钩子:

SetWindowsHookEx函数可以把应用程序定义的钩子函数安装到系统中:

HHOOK SetWindowsHookEx(

         int idHook,                                          //指定钩子的类型ª

         HOOKPROC lpfn,                            //钩子函数的地址。如果使º用的是远程钩子,钩子函数必须放在一个DLL

         HINSTANCE hMod,                //钩子函数所在DLL的实例句柄。如果是一个局部的钩子,该参数的值为NULL

         DWORD dwThreadId);         //指定要为哪个线程安装钩子,如果该值为0,那么该钩子将被解释为系统范围的

各个参数解释如下:

idHook参数取值:

WH_CALLWNDPROC:当目标线程调用SendMessage函数发送消息时,钩子函数被调用

WH_CALLWNDPROCRET:当SendMessage发送的消息返回时,钩子函数被调用

WH_GETMESSAGE:当目标线程调用GetMessagePeekMessage时,钩子函数被调用

WH_KEYBOARD:当从消息队列中查询WM_KEYUPWM_KEYDOWN消息时

WH_MOUSE:当调用从消息队列中查询鼠标事件消息时

WH_MSGFILTER:当对话框、菜单或滚动条要处理一个消息时,钩子函数被调用。该钩子是局部的,它是为那些有自己消息处理过程的控件对象设计的

WH_SYSMSGFILTER:和WH_MSGFILTER一样,只不过是系统范围的

WH_JOURNALRECORD:当windows从硬件队列中获得消息时

WH_JOURNALPLAYBACK:当一个事件从系统的硬件队列中被请求时

WH_SHELL:当关于Windows外壳事件发生时,譬如任务条需要重画它的按钮

WH_CBT:当基于计算机的训练(CBT)事件发生时

WH_FOREGROUNDIDLEWindows自己使用

WH_DEBUG:用来给钩子函数除错

 

如果dwThreadId参数是0,或者指定一个有其他进程创建的线程ID,则lpfn参数指向的钩子函数必须位于一个DLL中,因为进程的地址空间是相互隔离的,发生事件的进程不能调用其他进程地址空间的钩子函数。如果钩子函数的实现代码在DLL中,在相关事件发生时,系统会把这个DLL插入到发生事件的进程的地址空间,使它能够调用钩子函数。这种需要将钩子函数写进DLL以便挂钩其他进程事件的钩子称为远程钩子。

如果dwThreadId参数指定一个由自身进程创建的线程IDlpfn参数指向的钩子函数只要在当前进程中即可,不必非要写入DLL。这种仅挂钩属于自身进程事件的钩子称为局部钩子。

 

钩子函数的一般形式如下:

LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam)

{

       //处理该消息的代码

       .......

       return ::CallNextHookEx(hHook, nCode, wParam, lParam);

}

其中,nCode参数是Hook代码,钩子函数用这个参数来确定任务,它的值依赖于Hook的类型。wParamlParam参数的值依赖于Hook代码,但是它们典型的值是一些关于发送或者接收消息的信息。

hHook参数是安装钩子时得到的钩子句柄(SetWindowsHookEx的返回值)。

 

卸载钩子:

BOOL UnhookWindowsHookEx(HHOOK hhk);            //hhk是要卸载的钩子的句柄

 

 

下面实现右键双击关闭当前窗口的功能,为了使用方便,我们设计成没有界面的,因此,我们先用VC生成基于对话框的应用程序,并且在OnInitDialog()函数中添加如下实现程序无界面运行的代码:

SetWindowPos(&CWnd::wndNoTopMost, 0, 0, 0, 0, WSP_HIDEWINDOW);

ModifyStyleEx(WS_EX_APPWINDOW, WS_EX_TOOLWINDOW);

 

其中SetWindowPos函数完成了设置窗口处于最底层、大小为0并且隐藏的功能;ModifyStyleEx函数用来更改窗口的扩展样式。

 

为了完成全局钩子的安装,我们需要动态链接库ACEHook.dll,在SetHook()函数的实现中调用SetWindowsHookEx(WH_MOUSE, AceMouseProc, GetModuleHandle(ACEHook), 0)安装全局钩子,钩子函数AceMouseProc实现对鼠标右键双击事件的响应:

 

LRESULT CALLBACK AceMouseProc(

              int nCode, WPARAM wParam, LPARAM lParam)

{

       if(nCode >= 0)

       {

              if(wParam == WM_RBUTTONDBLCLK)

              {

                     hWnd = GetForegroundWindow();

                     GetWindowRect(hWnd, &rt);

                     ptNew.x = rt.right - 13;

                     ptNew.y = rt.top + 13;   

                     GetCursorPos(&ptOld);

                     SetCursorPos(ptNew.x, ptNew.y);

                    

                     mouse_event(MOUSEEVENTF_LEFTDOWN, ptNew.x, ptNew.y, 0, 0);

                     mouse_event(MOUSEEVENTF_LEFTUP, PTNew.x, ptNew.y, 0, 0);

                    

                     SetCursorPos(ptOld.x, ptOld.y);

                     return 1;

              }     

       }

       return CallNextHookEx(g_hMouse, nCode, wParam, lParam);   

}

 

如果是右键双击事件,系统首先调用GetForegroundWindow()函数获得用户当前正在工作的窗口句柄,通过此窗口句柄可调用GetWindowRect()函数来获得当前工作窗口在屏幕上的坐标。ptNewPOINT类型变量,通过计算出的当前窗口的关闭按钮在屏幕上的坐标对其赋值。当前窗口的关闭按钮能响应鼠标事件的屏幕坐标范围是:[rt.right-4, rt.right-21],在此我们选用rt.right-13

窗口的关闭我们通过调用mouse_event()函数来完成,mouse_event()函数可以模拟鼠标按键及滚轮等动作,因此,通过在窗口关闭按钮位置合成鼠标的左键单击事件可以实现关闭窗口的功能。鼠标的左键单击事件通过组合左键按下(MOUSEEVENTF_LEFTDOWN)和左键放开(MOUSEEVENTF_LEFTUP)事件获得。

 

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值