zoukankan      html  css  js  c++  java
  • 如何HOOK桌面窗口消息

    代码详见:http://download.csdn.net/detail/swanabin/6771465


    需求:截获桌面窗口鼠标单击事件,解析所选中的桌面 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 信息。至于这个窗口如何制作,我就不再描述了。

    以下为部分代码: 

    1. #include "CommonDef.h"  
    2. #include "DesktopHook.h"  
    3. #include "FloatWin.h"  
    4.   
    5. const UINT WM_DESKTOP_CLICKED_ITEM = RegisterWindowMessage(L"WM_DESKTOP_CLICKED_ITEM");  
    6.   
    7. BOOL g_isCaptured = FALSE;  
    8. CFloatWin* g_floatWin = NULL;  
    9.   
    10. INT_PTR WINAPI DlgProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) ;  
    11.   
    12. int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCEPTSTR pszCmdLine, int)  
    13. {  
    14.     HWND hwnd = FindWindow(TEXT("#32770"), TEXT("DesktopCaptor2"));  
    15.     if (IsWindow(hwnd))  
    16.     {  
    17.         // An instance is already running, show a messagebox  
    18.         MessageBox(GetForegroundWindow(), L"An instance is already running", L"Error", MB_ICONERROR);  
    19.     }   
    20.     else   
    21.     {  
    22.         DialogBox(hinstExe, MAKEINTRESOURCE(IDD_DESKTOP_CAPTOR), NULL, DlgProc);  
    23.     }  
    24.     return(0);  
    25. }  
    26.   
    27. INT_PTR CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)  
    28. {  
    29.     UNREFERENCED_PARAMETER(lParam);  
    30.     switch (message)  
    31.     {  
    32.     case WM_INITDIALOG:  
    33.         {  
    34.             // Set icon for the application  
    35.             SendMessage(hDlg, WM_SETICON, ICON_BIG,  (LPARAM)   
    36.                 LoadIcon((HINSTANCE) GetWindowLongPtr(hDlg, GWLP_HINSTANCE),   
    37.                 MAKEINTRESOURCE(IDI_DESKTOPCAPTOR2)));  
    38.   
    39.             // Set dialog's position  
    40.             int nScreenWidth = ::GetSystemMetrics(SM_CXSCREEN);  
    41.             int nScreenHeight = ::GetSystemMetrics(SM_CYSCREEN);  
    42.             RECT rect = { 0 };  
    43.             GetWindowRect(hDlg, &rect);  
    44.             SetWindowPos(  
    45.                 hDlg,   
    46.                 HWND_TOP,   
    47.                 nScreenWidth - (rect.right - rect.left),   
    48.                 0,   
    49.                 0, 0,   
    50.                 SWP_NOSIZE);  
    51.   
    52.             g_floatWin = CFloatWin::getInstance();  
    53.         }  
    54.         return (INT_PTR)TRUE;  
    55.   
    56.     case WM_COMMAND:  
    57.         {  
    58.             UINT wmId = LOWORD(wParam);  
    59.             UINT wmEvent = HIWORD(wParam);  
    60.   
    61.             switch (wmId)  
    62.             {  
    63.             case IDOK:  
    64.             case IDCANCEL:  
    65.                 EndDialog(hDlg, LOWORD(wParam));  
    66.                 return (INT_PTR)TRUE;  
    67.             case IDC_START_CAPTOR:  
    68.                 if (FALSE == g_isCaptured)  
    69.                 {  
    70.                    <strong> </strong>g_isCaptured = CreateDesktopEventCaptor(hDlg);  
    71.                 }  
    72.                 break;  
    73.             case IDC_STOP_CAPTOR:  
    74.                 if (TRUE == g_isCaptured)  
    75.                 {  
    76.                     CloseDesktopEventCaptor();  
    77.                     g_isCaptured = FALSE;  
    78.                 }  
    79.                 break;  
    80.             default:  
    81.                 return DefWindowProc(hDlg, message, wParam, lParam);  
    82.             }  
    83.         }  
    84.         break;  
    85.     case WM_COPYDATA:  
    86.         {  
    87.             COPYDATASTRUCT* pCopyData = (COPYDATASTRUCT*)lParam;  
    88.             if (pCopyData->dwData == WM_DESKTOP_CLICKED_ITEM)  
    89.             {  
    90.                 DesktopItemData itemData(*(DesktopItemData*)pCopyData->lpData);  
    91.                 g_floatWin->ShowWindow(TRUE, &itemData);  
    92.             }  
    93.         }  
    94.         break;  
    95.     }  
    96.     return (INT_PTR)FALSE;  
    97. }  


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

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

    1. #ifndef _DESKTOPHOOK_H_  
    2. #define _DESKTOPHOOK_H_  
    3.   
    4. //#ifdef __cplusplus  
    5. //extern "C" {  
    6. //#endif  
    7.   
    8. #ifdef DESKTOPHOOK_EXPORTS  
    9.     #define DESKTOPHOOK_API __declspec(dllexport)  
    10. #else  
    11.     #define DESKTOPHOOK_API __declspec(dllimport)  
    12. #endif  
    13.   
    14.   
    15. typedef struct DESKTOPHOOK_API _DesktopItemData   
    16. {  
    17.     POINT point;  
    18.     WCHAR szName[MAX_PATH];  // The desktop item's name.  
    19.     int nIndex;              // The desktop item's index on desktop.  
    20.   
    21.     _DesktopItemData()  
    22.     {  
    23.         ZeroMemory(szName, MAX_PATH);  
    24.         point.x = point.y = 0;  
    25.         nIndex = -1;  
    26.     }  
    27.   
    28.     _DesktopItemData(POINT pt, WCHAR* pszName, int nIx)  
    29.     {  
    30.         if (NULL != pszName)  
    31.         {  
    32.             point.x = pt.x;  
    33.             point.y = pt.y;  
    34.   
    35.             ZeroMemory(szName, MAX_PATH);  
    36.             _tcscpy_s(szName, MAX_PATH, pszName);  
    37.   
    38.             nIndex = nIx;  
    39.         }  
    40.     }  
    41.   
    42.     _DesktopItemData(const _DesktopItemData& itemDataRef)  
    43.     {  
    44.         point.x = itemDataRef.point.x;  
    45.         point.y = itemDataRef.point.y;  
    46.   
    47.         ZeroMemory(szName, MAX_PATH);  
    48.         _tcscpy_s(szName, MAX_PATH, itemDataRef.szName);  
    49.         nIndex = itemDataRef.nIndex;  
    50.     }  
    51.   
    52.     _DesktopItemData& operator = (const _DesktopItemData& itemDataRef)  
    53.     {  
    54.         point.x = itemDataRef.point.x;  
    55.         point.y = itemDataRef.point.y;  
    56.   
    57.         ZeroMemory(szName, MAX_PATH);  
    58.         _tcscpy_s(szName, MAX_PATH, itemDataRef.szName);  
    59.         nIndex = itemDataRef.nIndex;  
    60.   
    61.         return *this;  
    62.     }  
    63.   
    64. } DesktopItemData, *LPDesktopItemData;  
    65.   
    66. EXTERN_C DESKTOPHOOK_API BOOL CreateDesktopEventCaptor(HWND hNotifierhWnd);  
    67. EXTERN_C DESKTOPHOOK_API void CloseDesktopEventCaptor();  
    68.   
    69. //#ifdef __cplusplus  
    70. //}  
    71. //#endif  
    72.   
    73. #endif // _DESKTOPHOOK_H_  

    其中:

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

    2. CreateDesktopEventCaptor 函数是用来安装 HOOK

    3. CloseDesktopEventCaptor 函数是用来卸载 HOOK

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

    1. // DesktopHook.cpp : Defines the exported functions for the DLL application.  
    2. //  
    3.   
    4. #include "stdafx.h"  
    5. #include "DesktopHook.h"  
    6. #include "DesktopItem.h"  
    7.   
    8. #pragma data_seg("SHARED_DATA")  
    9. HWND  g_hNotifierWnd = NULL;  
    10. HHOOK g_hPostMsgHook = NULL;  
    11. WCHAR g_szBuf[MAX_PATH] = {0};  
    12. #pragma data_seg()  
    13. #pragma comment(linker, "/SECTION:SHARED_DATA,RWS")  
    14.   
    15. // Global data  
    16. const UINT WM_DESKTOP_CLICKED_ITEM = RegisterWindowMessage(L"WM_DESKTOP_CLICKED_ITEM");  
    17.   
    18. HMODULE g_hModule;  
    19. HWND  g_hDesktopWnd  = NULL;  
    20.   
    21. // The low-order word specifies the x-coordinate of the cursor.   
    22. // The high-order word specifies the y-coordinate of the cursor.  
    23. BOOL g_bDoubleClick = FALSE;  
    24. UINT_PTR g_timerID = 0;  
    25. POINT g_clickPt;  
    26. CDesktopItem g_singleClickDesktopItem = CDesktopItem::Empty;  
    27. DesktopItemData g_desktopItemData;  
    28.   
    29. // Declaration of methods.  
    30. static HWND FindShellWindow();  
    31. static LRESULT CALLBACK GetMsgProc(int code,WPARAM wParam,LPARAM lParam);  
    32. static VOID CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime);  
    33.   
    34. EXTERN_C DESKTOPHOOK_API BOOL CreateDesktopEventCaptor(HWND hNotifierhWnd)  
    35. {  
    36.     g_hDesktopWnd = FindShellWindow();  
    37.     if (NULL == g_hDesktopWnd)  
    38.     {  
    39.         OutputDebugString(L"Can not find desktop window handle");  
    40.         return FALSE;  
    41.     }  
    42.   
    43.     g_hNotifierWnd = hNotifierhWnd;  
    44.   
    45.     // Get desktop handle's thread id.  
    46.     DWORD targetThreadid = ::GetWindowThreadProcessId(g_hDesktopWnd, NULL);  
    47.     // Hook post message.  
    48.     g_hPostMsgHook = ::SetWindowsHookEx(WH_GETMESSAGE, &GetMsgProc, g_hModule, targetThreadid);  
    49.   
    50.     if (g_hPostMsgHook != NULL)  
    51.     {  
    52.         return TRUE;  
    53.     }  
    54.   
    55.     return FALSE;  
    56. }  
    57.   
    58. EXTERN_C DESKTOPHOOK_API void CloseDesktopEventCaptor()  
    59. {  
    60.     if (g_hPostMsgHook != NULL)  
    61.     {  
    62.         ::UnhookWindowsHookEx(g_hPostMsgHook);  
    63.     }  
    64. }  
    65.   
    66. HWND FindShellWindow()  
    67. {  
    68.     // Sometimes, we can't find the desktop window when we use this function, but we must   
    69.     // find it's handle, so we do a loop to find it, but at most we find for 10 times.  
    70.     UINT uFindCount = 0;  
    71.     HWND hSysListView32Wnd = NULL;  
    72.     while (NULL == hSysListView32Wnd && uFindCount < 10)  
    73.     {  
    74.         HWND hParentWnd = ::GetShellWindow();  
    75.         HWND hSHELLDLL_DefViewWnd = ::FindWindowEx(hParentWnd, NULL, L"SHELLDLL_DefView", NULL);   
    76.         hSysListView32Wnd = ::FindWindowEx(hSHELLDLL_DefViewWnd, NULL, L"SysListView32", L"FolderView");  
    77.   
    78.         if (NULL == hSysListView32Wnd)  
    79.         {  
    80.             hParentWnd = ::FindWindowEx(NULL, NULL, L"WorkerW", L"");  
    81.             while((!hSHELLDLL_DefViewWnd) && hParentWnd)  
    82.             {  
    83.                 hSHELLDLL_DefViewWnd = ::FindWindowEx(hParentWnd, NULL, L"SHELLDLL_DefView", NULL);  
    84.                 hParentWnd = FindWindowEx(NULL, hParentWnd, L"WorkerW", L"");  
    85.             }  
    86.             hSysListView32Wnd = ::FindWindowEx(hSHELLDLL_DefViewWnd, 0, L"SysListView32", L"FolderView");  
    87.         }  
    88.   
    89.         if (NULL == hSysListView32Wnd)  
    90.         {  
    91.             Sleep(1000);  
    92.             uFindCount++;  
    93.         }  
    94.         else  
    95.         {  
    96.             break;  
    97.         }  
    98.     }  
    99.   
    100.     return hSysListView32Wnd;  
    101. }  
    102.   
    103. // The message which is "Post" type can be hook in this hook procedure.  
    104. LRESULT CALLBACK GetMsgProc(int code, WPARAM wParam, LPARAM lParam)  
    105. {  
    106.     MSG* pMsg = (MSG*)lParam;  
    107.     if( NULL != pMsg && pMsg->hwnd != NULL  )  
    108.     {  
    109.         switch(pMsg->message)  
    110.         {  
    111.         case WM_MOUSEMOVE:  
    112.             {  
    113.                 //OutputDebugStringW(L"Mouse Move");  
    114.             }  
    115.             break;  
    116.         case WM_LBUTTONUP:  
    117.             {  
    118.                 //OutputDebugStringW(L"WM_LBUTTONUP");  
    119.                 if (g_bDoubleClick)  
    120.                 {  
    121.                     OutputDebugString(L"g_bDoubleClick == TRUE");  
    122.                     g_singleClickDesktopItem = CDesktopItem::Empty;  
    123.                     g_bDoubleClick = FALSE;  
    124.                     ::KillTimer(NULL, g_timerID);  
    125.                 }  
    126.                 else  
    127.                 {  
    128.                     OutputDebugString(L"g_bDoubleClick == FALSE");  
    129.                     ::KillTimer(NULL, g_timerID);  
    130.                     g_clickPt.x = pMsg->pt.x;  
    131.                     g_clickPt.y = pMsg->pt.y;  
    132.                     g_singleClickDesktopItem = pMsg;  
    133.                     g_timerID = ::SetTimer(NULL, 1, ::GetDoubleClickTime(), TimerProc);  
    134.                 }  
    135.             }  
    136.             break;  
    137.         case WM_LBUTTONDBLCLK:  
    138.             g_bDoubleClick = TRUE;  
    139.             break;  
    140.         }  
    141.     }  
    142.   
    143.     return ::CallNextHookEx(g_hPostMsgHook, code, wParam, lParam);  
    144. }  
    145.   
    146. VOID CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime)  
    147. {  
    148.     UNREFERENCED_PARAMETER(hwnd);  
    149.     UNREFERENCED_PARAMETER(uMsg);  
    150.     UNREFERENCED_PARAMETER(idEvent);  
    151.     UNREFERENCED_PARAMETER(dwTime);  
    152.     if (g_timerID == idEvent &&  
    153.         g_bDoubleClick == FALSE &&   
    154.         g_singleClickDesktopItem != CDesktopItem::Empty)  
    155.     {  
    156.         OutputDebugString(L"SendMessageTimeout Process begin");  
    157.   
    158.         wstring strName = g_singleClickDesktopItem.GetItemName();  
    159.         //_tcscpy_s(g_szBuf, MAX_PATH, strName.c_str());  
    160.         //::PostMessage(g_hNotifierWnd, WM_DESKTOP_CLICKED_ITEM, NULL, (LPARAM)g_szBuf);  
    161.   
    162.         ZeroMemory(&g_desktopItemData, sizeof(DesktopItemData));  
    163.         g_desktopItemData.point.x = g_clickPt.x;  
    164.         g_desktopItemData.point.y = g_clickPt.y;  
    165.         _tcscpy_s(g_desktopItemData.szName, MAX_PATH, strName.c_str());  
    166.         g_desktopItemData.nIndex = g_singleClickDesktopItem.GetItemIndex();  
    167.   
    168.         COPYDATASTRUCT copyData;  
    169.         copyData.dwData = (UINT_PTR)WM_DESKTOP_CLICKED_ITEM;  
    170.         copyData.cbData = sizeof(DesktopItemData);  
    171.         copyData.lpData = &g_desktopItemData;  
    172.         DWORD_PTR rt = -1;  
    173.         SetActiveWindow(g_hNotifierWnd);  
    174.         SetForegroundWindow(g_hNotifierWnd);  
    175.         //SendMessageTimeout(g_hNotifierWnd, WM_COPYDATA, (WPARAM)g_hDesktopWnd, (LPARAM)©Data, SMTO_NORMAL, 1000, &rt);  
    176.         SendMessage(g_hNotifierWnd, WM_COPYDATA, (WPARAM)g_hDesktopWnd, (LPARAM)©Data);  
    177.   
    178.         OutputDebugString(L"SendMessageTimeout Process end");  
    179.     }  
    180.     ::KillTimer(NULL, g_timerID);  
    181. }  

    说明:在该实现中,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...

  • 相关阅读:
    显示器的分类和主要性能指标
    关闭Win 10 自动更新功能
    MySQL下载安装教程
    经济学十大原理
    西方经济学概述(经济学原理 1 )
    工作表基本操作
    输入和编辑工作表
    因特网概述
    摩尔定律(Moore's Law)
    C 语言编程机制
  • 原文地址:https://www.cnblogs.com/vcerror/p/4289108.html
Copyright © 2011-2022 走看看