您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
   
 
 
     
   
 订阅
  捐助
如何HOOK桌面窗口消息
 
作者:woyaowenzi 来自于:CSDN 发布于 2015-10-15
  2503  次浏览      15
 

需求:截获桌面窗口鼠标单击事件,解析所选中的桌面 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)
{
<strong> </strong>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次去查找桌面窗口句柄。

2、WH_GETMESSAGE 类型的消息 HOOK 只能钩住使用 PostMessage 方式发送的消息,这点很重要,否则,如果是以SendMessage发送的方式,则需要使用 WH_CALLWNDPROC 或者 WH_CALLWNDPROCRET 的 HOOK 方式。

3、如何解析点击桌面的 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,但涉及到相互发消息,有人就会打开2个Visual Studio,同时进行调试,这样的确可以做到,但总感觉不太方便。实际上,在同一个Visual Studio中,是可以同时调试多个程序的。对于刚刚这种情况,只需要在每个Project A 和 ProjectB 上面分别右击->Debug->Start new instance 即可。

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

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

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

3、将 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是会出现问题的,因为不能向高权限进程发送消息。

   
2503 次浏览       15
 
相关文章

手机软件测试用例设计实践
手机客户端UI测试分析
iPhone消息推送机制实现与探讨
Android手机开发(一)
 
相关文档

Android_UI官方设计教程
手机开发平台介绍
android拍照及上传功能
Android讲义智能手机开发
相关课程

Android高级移动应用程序
Android系统开发
Android应用开发
手机软件测试
最新课程计划
信息架构建模(基于UML+EA)3-21[北京]
软件架构设计师 3-21[北京]
图数据库与知识图谱 3-25[北京]
业务架构设计 4-11[北京]
SysML和EA系统设计与建模 4-22[北京]
DoDAF规范、模型与实例 5-23[北京]

android人机界面指南
Android手机开发(一)
Android手机开发(二)
Android手机开发(三)
Android手机开发(四)
iPhone消息推送机制实现探讨
手机软件测试用例设计实践
手机客户端UI测试分析
手机软件自动化测试研究报告
更多...   

Android高级移动应用程序
Android应用开发
Android系统开发
手机软件测试
嵌入式软件测试
Android软、硬、云整合

领先IT公司 android开发平台最佳实践
北京 Android开发技术进阶
某新能源领域企业 Android开发技术
某航天公司 Android、IOS应用软件开发
阿尔卡特 Linux内核驱动
艾默生 嵌入式软件架构设计
西门子 嵌入式架构设计
更多...