65.9K
CodeProject 正在变化。 阅读更多。
Home

跨进程子类化

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (23投票s)

2003 年 10 月 20 日

4分钟阅读

viewsIcon

150931

downloadIcon

6133

使用下面的代码,我将解释如何使用挂钩和子类化技术来子类化记事本应用程序窗口。此技术可用于为任何应用程序构建自定义的基于 DLL 的引擎。

引言

使用下面的代码,我将解释如何使用挂钩和子类化技术来子类化记事本应用程序窗口。此技术可用于为任何应用程序构建自定义的基于 DLL 的引擎。在此示例中,我将子类化记事本应用程序,创建自定义菜单,并在菜单被点击时做出响应。

背景

  • API 挂钩揭秘 - 作者: Ivo Ivanov
  • Windows 消息处理 - 第 4 部分 - 作者: Daniel Kopitchinski

使用代码

在开始描述跨进程子类化之前,我假设您已经知道挂钩和窗口子类化这两个术语的含义。我也假设您在处理挂钩和子类化方面,尤其是在 Windows SDK 方面,已经有一些经验。我将只简要回顾这两种技术,并解释如何将这两种技术结合起来实现跨进程子类化。

Windows 挂钩

MSDN 关于挂钩的说法如下:

"在 Microsoft® Windows® 操作系统中,挂钩是一种机制,通过它可以让一个函数在事件(消息、鼠标操作、键盘输入)到达应用程序之前进行拦截。该函数可以对事件进行操作,并在某些情况下修改或丢弃它们。接收事件的函数称为过滤器函数,并根据其拦截的事件类型进行分类。"

您可以根据您的需求安装不同类型的挂钩,例如键盘挂钩、消息挂钩或鼠标挂钩。挂钩可以分为两种方式:

  1. 全局挂钩
  2. 线程特定挂钩

顾名思义,全局挂钩是全局的。全局挂钩安装在系统中运行的每一个线程中。不当使用全局挂钩可能会导致程序臃肿,并在许多情况下减慢系统速度。

线程特定挂钩仅安装在一个进程中。此挂钩可以安装在与调用函数相同的线程中(稍后将进行解释),或者安装在运行在另一个进程中的线程中。

挂钩使用 SetWindowsHookEx API 进行安装,并使用 UnhookWindowsHookEx API 进行卸载。

窗口子类化

子类化允许您通过插入一个消息映射来拦截窗口的消息,从而改变现有窗口(通常是控件)的行为。子类化是进程特定的,您不能子类化一个与您的应用程序不在同一进程中的窗口。

尽管 Windows 操作系统不允许我们子类化一个不在同一进程中的窗口,但我们可以通过使用挂钩并进入我们想要子类化的窗口所在的进程空间来绕过这个限制。

现在,让我们开始构建我们的应用程序。

我们的应用程序将包含两个模块:一个用于安装/卸载挂钩并子类化记事本应用程序的挂钩 DLL,以及一个加载挂钩 DLL 的 EXE。

挂钩 DLL

挂钩 DLL 将用于安装/卸载 Windows 挂钩,并子类化记事本应用程序窗口。在深入研究安装/卸载和子类化的代码之前,让我们先看一下下面的代码片段。

//Initialized Data to be shared with all instance of the dll
#pragma data_seg("Shared")
HWND hTarget=NULL;
HWND hApp = NULL;
int num=0 ;// Number of the subclassed window handle ,for use in the dll
bool done=FALSE;
HINSTANCE hInstance=NULL;
#pragma data_seg()
// Initialised data End of data share

#pragma data_seg 编译器指令要求编译器创建一个可以被 DLL 的所有实例共享的数据段。我们需要这个是因为我们将通过我们的 EXE 应用程序加载的 DLL 将在一个进程中,而最终将挂钩记事本应用程序的 DLL 将在记事本应用程序的进程中。因此,我们需要一个可以被 DLL 不同实例共享的公共数据段。

安装 Windows 挂钩的代码大致如下。

// Get the handles of the Targetwindow and of the Our application
// and set the hook
int WINAPI SetHandle(HWND HandleofTarget ,HWND HandleofApp)
{

    hTarget=HandleofTarget;
    hApp=HandleofApp;
    hWinHook=SetWindowsHookEx(WH_CBT,(HOOKPROC)CBTProc,hInstance,
         GetWindowThreadProcessId(hTarget,NULL));
    if(hWinHook==NULL)
        return 0;
    else
        return 1;

}//End this function

我们将安装一个 CBT 挂钩。当我们需要在窗口创建/销毁/激活/最小化等事件发生时收到通知时,就会使用 CBT(计算机基础培训)挂钩。挂钩回调过程大致如下。

//The CBT hook Proc(Computer Based Training Hook)
LRESULT CALLBACK CBTProc(int nCode,WPARAM wParam,LPARAM lParam)
{


    if (nCode==HCBT_ACTIVATE)  //Called when the application window 
                               //is activated
    {
    
        if((HWND)(wParam)==hTarget)  //check if the window activated 
                                      //is Our Targer App
        {   
                    
            int count;
            for (count=0;count<num;count++)
            {
                if (blnsubclassed[count]==FALSE)
                {    
                    if(((int)hndll[count])>1)
                    {
                        OldWndHndl[count]=SetWindowLong(hndll[count],
                           GWL_WNDPROC,(long)WindowProc);  //Subclass !!!!
                    }
                                        
                    blnsubclassed[count]=TRUE;    // Set state as subclassed
                }
            }

        }        
    }
    if (nCode==HCBT_DESTROYWND) //Called when the application 
                                 //window is destroyed
    {

        if((HWND)wParam==hTarget)
            SendNotifyMessage(hApp,WM_APP +1024,(WPARAM)wParam,
               (LPARAM)lParam);// Send the message to our app
    }
    return CallNextHookEx(NULL, nCode, wParam, lParam);
}//End of the hook procedure

我们在记事本应用程序首次激活后立即对其进行子类化。SetWindowLong API 用于子类化记事本应用程序。被子类化的窗口过程如下:

//Window Procedures of the subclassed windows
LRESULT CALLBACK WindowProc(
  HWND hwnd,
  UINT uMsg,
  WPARAM wParam,
  LPARAM lParam
)
{    
    long val;
    int count;
    for(count=0;count<num;count++)
    {
        if(hndll[count]==hwnd)
        {
            val=count;   // this gets us the exact position of 
                          // this window procedure in the array
        }
    }
    
    long result;
    if(uMsg==273) //Message Implying Menu Clicks
        if(HIWORD(wParam)==0)
                result=SendNotifyMessage(hApp,WM_APP +1024,
                  (WPARAM)(LOWORD(wParam)),
                  (LPARAM)uMsg);// Send the message  to the vb app

    return CallWindowProc((WNDPROC)OldWndHndl[val],hwnd,uMsg,wParam,lParam);
}//End Procedure

每当用户点击记事本中的菜单时,我们的挂钩 EXE 都会收到通知,并且我们的应用程序会根据被点击的菜单做出响应。

挂钩 EXE

挂钩 EXE 将创建菜单并加载挂钩 DLL。执行此操作的代码如下:

hHookedWindow=FindWindow(NULL,"Untitled - Notepad");
if(hHookedWindow==NULL)
{
    MessageBox(0,
          "Could Not find a running instance of Notepad.\r\n"
          "Please Start Notepad and try again","Error",0);
    break;
}
hAppMenu=GetMenu(hHookedWindow);
hAppendMenu=CreateMenu(); //Create the menu
AppendMenu(hAppMenu,MF_STRING + MF_POPUP,
    (unsigned int)hAppendMenu,"HTML");  //add new menu
AppendMenu(hAppendMenu,MF_STRING,125,"Make HTML");     
AppendMenu(hAppendMenu,MF_STRING,126,"Add Line Break");
hLib = LoadLibrary("hooks.dll"); //load the hooking dll
hMenuWnd = GetWindow(hHookedWindow, 5); //get the menu window
hThread=GetWindowThreadProcessId(hHookedWindow,NULL);
SetHandle = (sthndl)GetProcAddress(hLib, "SetHandle");
UnSubClass = (unsub)GetProcAddress(hLib, "UnSubclass");
SetHandle(hHookedWindow,hwnd);
FillHandleArray = (filhndl)GetProcAddress(hLib, "FillHandleArray");
FillHandleArray(hHookedWindow,1);
FillHandleArray(hMenuWnd,1);
ShowWindow(hHookedWindow, SW_MINIMIZE);

因此,通过使用挂钩,我们可以子类化运行在任何进程中的窗口。跨进程子类化应非常小心地使用,不安全的挂钩/子类化通常会导致被子类化/挂钩的应用程序崩溃。

历史

  • 2003 年 10 月 22 日 - 文章添加
© . All rights reserved.