如何HOOK桌面窗口消息

12 篇文章 0 订阅

本文通过一个DEMO来演示如何HOOK桌面消息

代码详见:http://download.csdn.net/source/3448133


需求:截获桌面窗口鼠标单击事件,解析所选中的桌面 Item,并将解析后的 item 信息发送给主调程序,并将信息显示在一个窗口上面。如下图:

 

思路:

1. 确定HOOK的类型。很明显,这一个进程外的HOOK,我们的应用程序DesktopCaptor2.exe 需要捕获 Explorer.exe 这个进程的桌面窗口所在的线程的消息。因此,需要将HOOK过程放在一个独立的DLL 中去,然后使用 SetWindowsHookEx HOOK过程安装到HOOK链中去。

2. 如何解析点击的桌面 Item 信息?这个其实也比较好做,由于桌面窗口本身是一个 listview 控件(不解释),因此,我们可以通过 listview 拿到桌面窗口的简单信息。

3. 如何将解析后的桌面 Item 信息发送给主调程序,让它弹出一个窗口,并显示桌面 Item 信息?我们这里采用WM_COPYDATA 将从桌面进程获取到信息发送到我们的应用程序。

工程目录如下:


在主调程序(DesktopCaptor2.exe)是一个简单Win32 Dialog的程序。在这个程序中,我们干了两件事情:

1. 调用 DekstopHook 工程中的 DesktopHook.h 中的两个导出函数,通过这两个函数,对 HOOK 过程安装和卸载。

2. 接收来自于Explorer.exe 进程发送的 WM_COPYDATA 消息,还原桌面 Item 数据,并将在点击桌面 Item 的位置弹出一个窗口出来,显示 Item 信息。至于这个窗口如何制作,我就不再描述了。

以下为部分代码: 

#include "CommonDef.h"
#include "DesktopHook.h"
#include "FloatWin.h"

const UINT WM_DESKTOP_CLICKED_ITEM = RegisterWindowMessage(L"WM_DESKTOP_CLICKED_ITEM");

BOOL g_isCaptured = FALSE;
CFloatWin* g_floatWin = NULL;

INT_PTR WINAPI DlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) ;

int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int)
{
    HWND hwnd = FindWindow(TEXT("#32770"), TEXT("DesktopCaptor2"));
    if (IsWindow(hwnd))
    {
        // An instance is already running, show a messagebox
        MessageBox(GetForegroundWindow(), L"An instance is already running", L"Error", MB_ICONERROR);
    } 
    else 
    {
        DialogBox(hinstExe, MAKEINTRESOURCE(IDD_DESKTOP_CAPTOR), NULL, DlgProc);
    }
    return(0);
}

INT_PTR CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        {
            // Set icon for the application
            SendMessage(hDlg, WM_SETICON, ICON_BIG,  (LPARAM) 
                LoadIcon((HINSTANCE) GetWindowLongPtr(hDlg, GWLP_HINSTANCE), 
                MAKEINTRESOURCE(IDI_DESKTOPCAPTOR2)));

            // Set dialog's position
            int nScreenWidth = ::GetSystemMetrics(SM_CXSCREEN);
            int nScreenHeight = ::GetSystemMetrics(SM_CYSCREEN);
            RECT rect = { 0 };
            GetWindowRect(hDlg, &rect);
            SetWindowPos(
                hDlg, 
                HWND_TOP, 
                nScreenWidth - (rect.right - rect.left), 
                0, 
                0, 0, 
                SWP_NOSIZE);

            g_floatWin = CFloatWin::getInstance();
        }
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        {
            UINT wmId = LOWORD(wParam);
            UINT wmEvent = HIWORD(wParam);

            switch (wmId)
            {
            case IDOK:
            case IDCANCEL:
                EndDialog(hDlg, LOWORD(wParam));
                return (INT_PTR)TRUE;
            case IDC_START_CAPTOR:
                if (FALSE == g_isCaptured)
                {
                    g_isCaptured = CreateDesktopEventCaptor(hDlg);
                }
                break;
            case IDC_STOP_CAPTOR:
                if (TRUE == g_isCaptured)
                {
                    CloseDesktopEventCaptor();
                    g_isCaptured = FALSE;
                }
                break;
            default:
                return DefWindowProc(hDlg, message, wParam, lParam);
            }
        }
        break;
    case WM_COPYDATA:
        {
            COPYDATASTRUCT* pCopyData = (COPYDATASTRUCT*)lParam;
            if (pCopyData->dwData == WM_DESKTOP_CLICKED_ITEM)
            {
                DesktopItemData itemData(*(DesktopItemData*)pCopyData->lpData);
                g_floatWin->ShowWindow(TRUE, &itemData);
            }
        }
        break;
    }
    return (INT_PTR)FALSE;
}


我们重点说明一下DekstopHookDLL中的内容。

DesktopHook.h 中定义的是一些导出接口,代码如下:

#ifndef _DESKTOPHOOK_H_
#define _DESKTOPHOOK_H_

//#ifdef __cplusplus
//extern "C" {
//#endif

#ifdef DESKTOPHOOK_EXPORTS
	#define DESKTOPHOOK_API __declspec(dllexport)
#else
	#define DESKTOPHOOK_API __declspec(dllimport)
#endif


typedef struct DESKTOPHOOK_API _DesktopItemData 
{
    POINT point;
    WCHAR szName[MAX_PATH];  // The desktop item's name.
    int nIndex;              // The desktop item's index on desktop.

    _DesktopItemData()
    {
        ZeroMemory(szName, MAX_PATH);
        point.x = point.y = 0;
        nIndex = -1;
    }

    _DesktopItemData(POINT pt, WCHAR* pszName, int nIx)
    {
        if (NULL != pszName)
        {
            point.x = pt.x;
            point.y = pt.y;

            ZeroMemory(szName, MAX_PATH);
            _tcscpy_s(szName, MAX_PATH, pszName);

            nIndex = nIx;
        }
    }

    _DesktopItemData(const _DesktopItemData& itemDataRef)
    {
        point.x = itemDataRef.point.x;
        point.y = itemDataRef.point.y;

        ZeroMemory(szName, MAX_PATH);
        _tcscpy_s(szName, MAX_PATH, itemDataRef.szName);
        nIndex = itemDataRef.nIndex;
    }

    _DesktopItemData& operator = (const _DesktopItemData& itemDataRef)
    {
        point.x = itemDataRef.point.x;
        point.y = itemDataRef.point.y;

        ZeroMemory(szName, MAX_PATH);
        _tcscpy_s(szName, MAX_PATH, itemDataRef.szName);
        nIndex = itemDataRef.nIndex;

        return *this;
    }

} DesktopItemData, *LPDesktopItemData;

EXTERN_C DESKTOPHOOK_API BOOL CreateDesktopEventCaptor(HWND hNotifierhWnd);
EXTERN_C DESKTOPHOOK_API void CloseDesktopEventCaptor();

//#ifdef __cplusplus
//}
//#endif

#endif // _DESKTOPHOOK_H_

其中:

1. DesktopItemData 结构体是用来存放解析桌面 Item 的数据。

2. CreateDesktopEventCaptor 函数是用来安装 HOOK

3. CloseDesktopEventCaptor 函数是用来卸载 HOOK

以下是 DesktopHook.cpp 中的实现代码:

// DesktopHook.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"
#include "DesktopHook.h"
#include "DesktopItem.h"

#pragma data_seg("SHARED_DATA")
HWND  g_hNotifierWnd = NULL;
HHOOK g_hPostMsgHook = NULL;
WCHAR g_szBuf[MAX_PATH] = {0};
#pragma data_seg()
#pragma comment(linker, "/SECTION:SHARED_DATA,RWS")

// Global data
const UINT WM_DESKTOP_CLICKED_ITEM = RegisterWindowMessage(L"WM_DESKTOP_CLICKED_ITEM");

HMODULE g_hModule;
HWND  g_hDesktopWnd	 = NULL;

// The low-order word specifies the x-coordinate of the cursor. 
// The high-order word specifies the y-coordinate of the cursor.
BOOL g_bDoubleClick = FALSE;
UINT_PTR g_timerID = 0;
POINT g_clickPt;
CDesktopItem g_singleClickDesktopItem = CDesktopItem::Empty;
DesktopItemData g_desktopItemData;

// Declaration of methods.
static HWND FindShellWindow();
static LRESULT CALLBACK GetMsgProc(int code,WPARAM wParam,LPARAM lParam);
static VOID CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);

EXTERN_C DESKTOPHOOK_API BOOL CreateDesktopEventCaptor(HWND hNotifierhWnd)
{
    g_hDesktopWnd = FindShellWindow();
    if (NULL == g_hDesktopWnd)
    {
        OutputDebugString(L"Can not find desktop window handle");
        return FALSE;
    }

    g_hNotifierWnd = hNotifierhWnd;

    // Get desktop handle's thread id.
    DWORD targetThreadid = ::GetWindowThreadProcessId(g_hDesktopWnd, NULL);
    // Hook post message.
    g_hPostMsgHook = ::SetWindowsHookEx(WH_GETMESSAGE, &GetMsgProc, g_hModule, targetThreadid);

    if (g_hPostMsgHook != NULL)
    {
        return TRUE;
    }

    return FALSE;
}

EXTERN_C DESKTOPHOOK_API void CloseDesktopEventCaptor()
{
    if (g_hPostMsgHook != NULL)
    {
        ::UnhookWindowsHookEx(g_hPostMsgHook);
    }
}

HWND FindShellWindow()
{
    // Sometimes, we can't find the desktop window when we use this function, but we must 
    // find it's handle, so we do a loop to find it, but at most we find for 10 times.
    UINT uFindCount = 0;
    HWND hSysListView32Wnd = NULL;
    while (NULL == hSysListView32Wnd && uFindCount < 10)
    {
        HWND hParentWnd = ::GetShellWindow();
        HWND hSHELLDLL_DefViewWnd = ::FindWindowEx(hParentWnd, NULL, L"SHELLDLL_DefView", NULL); 
        hSysListView32Wnd = ::FindWindowEx(hSHELLDLL_DefViewWnd, NULL, L"SysListView32", L"FolderView");

        if (NULL == hSysListView32Wnd)
        {
            hParentWnd = ::FindWindowEx(NULL, NULL, L"WorkerW", L"");
            while((!hSHELLDLL_DefViewWnd) && hParentWnd)
            {
                hSHELLDLL_DefViewWnd = ::FindWindowEx(hParentWnd, NULL, L"SHELLDLL_DefView", NULL);
                hParentWnd = FindWindowEx(NULL, hParentWnd, L"WorkerW", L"");
            }
            hSysListView32Wnd = ::FindWindowEx(hSHELLDLL_DefViewWnd, 0, L"SysListView32", L"FolderView");
        }

        if (NULL == hSysListView32Wnd)
        {
            Sleep(1000);
            uFindCount++;
        }
        else
        {
            break;
        }
    }

    return hSysListView32Wnd;
}

// The message which is "Post" type can be hook in this hook procedure.
LRESULT CALLBACK GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
{
    MSG* pMsg = (MSG*)lParam;
    if( NULL != pMsg && pMsg->hwnd != NULL  )
    {
        switch(pMsg->message)
        {
        case WM_MOUSEMOVE:
            {
                //OutputDebugStringW(L"Mouse Move");
            }
            break;
        case WM_LBUTTONUP:
            {
                //OutputDebugStringW(L"WM_LBUTTONUP");
                if (g_bDoubleClick)
                {
                    OutputDebugString(L"g_bDoubleClick == TRUE");
                    g_singleClickDesktopItem = CDesktopItem::Empty;
                    g_bDoubleClick = FALSE;
                    ::KillTimer(NULL, g_timerID);
                }
                else
                {
                    OutputDebugString(L"g_bDoubleClick == FALSE");
                    ::KillTimer(NULL, g_timerID);
                    g_clickPt.x = pMsg->pt.x;
                    g_clickPt.y = pMsg->pt.y;
                    g_singleClickDesktopItem = pMsg;
                    g_timerID = ::SetTimer(NULL, 1, ::GetDoubleClickTime(), TimerProc);
                }
            }
            break;
        case WM_LBUTTONDBLCLK:
            g_bDoubleClick = TRUE;
            break;
        }
    }

    return ::CallNextHookEx(g_hPostMsgHook, code, wParam, lParam);
}

VOID CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)
{
    UNREFERENCED_PARAMETER(hwnd);
    UNREFERENCED_PARAMETER(uMsg);
    UNREFERENCED_PARAMETER(idEvent);
    UNREFERENCED_PARAMETER(dwTime);
    if (g_timerID == idEvent &&
        g_bDoubleClick == FALSE && 
        g_singleClickDesktopItem != CDesktopItem::Empty)
    {
        OutputDebugString(L"SendMessageTimeout Process begin");

        wstring strName = g_singleClickDesktopItem.GetItemName();
        //_tcscpy_s(g_szBuf, MAX_PATH, strName.c_str());
        //::PostMessage(g_hNotifierWnd, WM_DESKTOP_CLICKED_ITEM, NULL, (LPARAM)g_szBuf);

        ZeroMemory(&g_desktopItemData, sizeof(DesktopItemData));
        g_desktopItemData.point.x = g_clickPt.x;
        g_desktopItemData.point.y = g_clickPt.y;
        _tcscpy_s(g_desktopItemData.szName, MAX_PATH, strName.c_str());
        g_desktopItemData.nIndex = g_singleClickDesktopItem.GetItemIndex();

        COPYDATASTRUCT copyData;
        copyData.dwData = (UINT_PTR)WM_DESKTOP_CLICKED_ITEM;
        copyData.cbData = sizeof(DesktopItemData);
        copyData.lpData = &g_desktopItemData;
        DWORD_PTR rt = -1;
        SetActiveWindow(g_hNotifierWnd);
        SetForegroundWindow(g_hNotifierWnd);
        //SendMessageTimeout(g_hNotifierWnd, WM_COPYDATA, (WPARAM)g_hDesktopWnd, (LPARAM)©Data, SMTO_NORMAL, 1000, &rt);
        SendMessage(g_hNotifierWnd, WM_COPYDATA, (WPARAM)g_hDesktopWnd, (LPARAM)©Data);

        OutputDebugString(L"SendMessageTimeout Process end");
    }
    ::KillTimer(NULL, g_timerID);
}

说明:在该实现中,CreateDesktopEventCaptor 函数与 CloseDesktopEventCaptor 函数被 DesktopCaptor2.exe 进程调用,此时,DesktopHook.dll 会被加载到 DesktopCaptor2.exe 所在进程,当使用 CreateDesktopEventCaptor 函数后,HOOK 过程函数 GetMsgProc 会被安装到 Explorer.exe 进程中去,此时,DesktopHook.dll 会被加载到 Explorer.exe 中去。当用鼠标单击桌面 Item 时,消息将被传递到 GetMsgProc 中去,然后,在这个函数中发送一个 WM_COPYDATA 消息给DesktopCaptor2 的窗口,实际上,这相当于是 Explorer.exe 进程发送的 WM_COPYDATA 消息到 DesktopCaptor2.exe 进程 。需要注意一点的是,在 GetMsgProc 中发送消息的时候,g_hNotifierWnd 必须要设置成为共享数据,因为它是DesktopCaptor2.exe 进程在调用 DesktopHook.dll CreateDesktopEventCaptor 函数的时候被设置的,要想在Explorer.exe进程中继续有效,需要将之设置为 DLL共享数据。

另外,这里还有另外两个问题:

  1. 如何找到 Desktop 窗口句柄? 通过Spy++,我们得到:

其中,蓝色部分就是我们的桌面窗口句柄。我们的 FindShellWindow 函数就是为了干这个事情,但是有时候,这个函数会失败,因此,我们最多循环10次去查找桌面窗口句柄。

  1. WH_GETMESSAGE 类型的消息 HOOK 只能钩住使用 PostMessage 方式发送的消息,这点很重要,否则,如果是以SendMessage发送的方式,则需要使用 WH_CALLWNDPROC 或者 WH_CALLWNDPROCRET HOOK 方式。
  1. 如何解析点击桌面的 Item 信息?

实际上,由于桌面窗口本身是一个 ListView 控件,通过ListView 的控件的API,我们便能够拿到相关的信息(当然这里只是一个简单的信息,深层次的信息还需要深掘)。

首先,通过下面的函数,能够拿到选中的 Item ID,其中 hwnd 就是桌面窗口句柄。

UINT ListView_GetSelectedCount(

    HWND hwnd

);

其次,通过下面的函数,传入选中的 Item ID,我们就能拿到 Item 对应的文本。

void ListView_GetItemText(

    HWND hwnd,
    int 
iItem,
    int 
iSubItem,
    LPTSTR 
pszText,
    int 
cchTextMax
);

在这里,我使用了一个 CDesktopItem 类来专门干这个事情。

具体请详见附录代码。

 

至此,简单的代码讲解就结束了。


下面说一下如何调试。

因为本例子是进程外的HOOK,因此调试起来有很多不方便的地方。调试的难处在于如何调试HOOK过程函数。先说一个简单的例子,可能大家经常会遇到这种情况:假如一个Solution下有ProjectA Project B,它们都是exe,但涉及到相互发消息,有人就会打开2Visual Studio,同时进行调试,这样的确可以做到,但总感觉不太方便。实际上,在同一个Visual Studio中,是可以同时调试多个程序的。对于刚刚这种情况,只需要在每个Project A ProjectB 上面分别右击->Debug->Start new instance 即可。

然而,对于本例,这样做是不行的,因为 DesktopHook 是一个DLL工程,本身是无法进行独立调试的。因此,要想同时调试 DesktopCaptor2 工程和 DesktopHook 工程,需要按如下操作:

  1. 将DesktopHook工程设置为默认启动的工程。如图:

  1. DesktopCaptor2 工程通过 右击->Debug->Start new instance 启动起来,点击 Start Desktop Captor 按钮启动HOOK,将DesktopHook .dll 注入到进程Explorer.exe中去。

  1. DesktopHook  工程到附加(Attach)到Explorer.exe进程中去。如图:

点击 Tools->Attachto Process...

然后找到Explorer.exe,点击Attach按钮即可。


通过上面的这种方式,我们就能够很简单的在一个工程中,调试两个不同的进程的程序。这时,我们将断点打在DesktopHook 工程的函数 GetMsgProc中,并在DesktopCaptor2 工程 WM_COPYDATA 内部打上断点,发现点击桌面图标时,断点会走到GetMsgProc内部,当发送完消息后,就能走到DesktopCaptor2 工程 WM_COPYDATA 内部。

另外,你调试的时候,要注意一下当前用户的权限以及Visual Studio的权限。如果当前用户是管理员组用户,而你的VisualStudio是以管理员启动进来的,那么DesktopCaptor2.exe将也是管理员权限,但此时,Explorer.exe却是普通权限,此时,在GetMsgProc内部发送WM_COPYDATA是会出现问题的,因为不能向高权限进程发送消息。

 

The End...


  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值