当前位置: 首页 > news >正文

2507C++,窗口勾挂事件

原文
许多人熟悉SetWindowsHookEx,API,它提供了拦截与用户界面相关的某些操作的方法,如针对窗口的消息,这里.

可在特定线程上或在当前桌面的所有线程上,设置大多数这些勾挂.

或将DLL注入到内联调用的目标进程以处理相关事件.

它还提供了与UI相关的同样可通过回调处理的各种事件.可在特定线程进程附加它,也可在有连接到当前桌面的线程所有进程上附加它.

有问题的APISetWinEventHook,这里:

HWINEVENTHOOK SetWinEventHook(_In_ DWORD eventMin,_In_ DWORD eventMax,_In_opt_ HMODULE hmodWinEventProc,_In_ WINEVENTPROC pfnWinEventProc,_In_ DWORD idProcess,_In_ DWORD idThread,_In_ DWORD dwFlags);

该函数允许在触发事件时调用回调(pfnWinEventProc).eventMineventMax提供了一个过滤事件简单方法.如果需要所有事件,可用EVENT_MINEVENT_MAX覆盖一切事件.

如果函数在DLL内,则需要该模块,因此hmodWinEventProc是,加载进调用过程中的模块句柄.与SetWindowsHookEx类似,按需自动在目标进程中加载DLL.

如果两个ID均为零,idProcessidThread允许针对当前桌面中的特定线程,特定进程或所有进程.即使没有DLL,也可针对所有进程.

此时,事件信息将混杂回调用者的进程,并在那里调用.这确实需要传递WINEVENT_OUTOFCONTEXT标志来指示此要求.

以下示例显示如何为当前桌面中的所有进程/线程安装此类事件监听:

auto hHook = ::SetWinEventHook(EVENT_MIN, EVENT_MAX, nullptr,OnEvent, 0, 0,WINEVENT_OUTOFCONTEXT |WINEVENT_SKIPOWNPROCESS | WINEVENT_SKIPOWNTHREAD);
::GetMessage(nullptr, nullptr, 0, 0);

最后两个标志指示,不应报告调用者进程的事件.注意很奇怪取消息调用,它对要调用的事件处理器是必需的.奇怪的是,与需要非无效指针的函数的SAL相反,不需要消息(MSG)结构.这里

事件处理器自身可执行任何操作,但是,提供的信息与SetWindowsHookEx回调根本不同.如,无法"改变"任何东西–它只是通知已发生的事情.

这些事件与访问相关,与窗口消息没有直接关系.下面是事件处理器原型:

void CALLBACK OnEvent(HWINEVENTHOOK hWinEventHook, DWORD event,HWND hwnd, LONG idObject, LONG idChild, DWORD eventTid, DWORD time);

事件正在报告的事件.WinUser.h中定义了各种此类事件,且第三方和OEM可使用许多值.检查头文件是值得的,因为每个微软定义的事件都有有关何时触发此类事件的细节,及该事件的idObject,idChild窗柄的含义.

eventTid是事件发起的线程ID.hwnd一般是与事件(如果有)关联的窗口或控件的句柄,某些事件足够通用,因此没有提供窗柄.

可通过利用访问,API来取有关与事件关联的对象更多信息.访问对象至少实现IAccessibleCOM接口,但也可实现其他接口,这里.

要从事件处理器IAccesible指针,可用AccessibleObjectFromEvent,这里:

CComPtr<IAccessible> spAcc;
CComVariant child;
::AccessibleObjectFromEvent(hwnd, idObject, idChild, &spAcc, &child);

我包含了<atlbase.h>来取得ATL客户支持(灵针和COM类型包装器).在可引入IAccessible其他环境中的其他API,包括AccessibleObjectFromPointAccessibleObjectFromWindow.

注意,必须包含<oleacc.h>并链接进oleacc.lib.
IAccessible有很多方法和属性,其中最简单的是实现者必须提供的名字(Name):

CComBSTR name;
spAcc->get_accName(CComVariant(idChild), &name);

IAccessible其他成员的文档.还可通过窗口句柄或线程ID,来取与事件关联的进程的细节,并取可执行文件名.下面是一个窗口句柄的示例:

DWORD pid = 0;
WCHAR exeName[MAX_PATH];
PCWSTR pExeName = L"";
if (hwnd && ::GetWindowThreadProcessId(hwnd, &pid)) {auto hProcess = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);if (hProcess) {DWORD size = _countof(exeName);if (::QueryFullProcessImageName(hProcess, 0, exeName, &size))pExeName = wcsrchr(exeName, L'\\') + 1;::CloseHandle(hProcess);}
}

GetWindowThreadProcessId取与窗口句柄关联的进程ID(和线程ID).可用给定的线程ID,调用OpenThread,然后调用GetProcessIdOfThread,这里.这里

以下是转储所有使用printf的完整事件处理器:

void CALLBACK OnEvent(HWINEVENTHOOK hWinEventHook, DWORD event, HWND hwnd,LONG idObject, LONG idChild, DWORD idEventThread, DWORD time) {CComPtr<IAccessible> spAcc;CComVariant child;::AccessibleObjectFromEvent(hwnd, idObject, idChild, &spAcc, &child);CComBSTR name;if (spAcc)spAcc->get_accName(CComVariant(idChild), &name);DWORD pid = 0;WCHAR exeName[MAX_PATH];PCWSTR pExeName = L"";if (hwnd && ::GetWindowThreadProcessId(hwnd, &pid)) {auto hProcess = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);if (hProcess) {DWORD size = _countof(exeName);if (::QueryFullProcessImageName(hProcess, 0, exeName, &size))pExeName = wcsrchr(exeName, L'\\') + 1;::CloseHandle(hProcess);}}printf("Event: 0x%X (%s) HWND: 0x%p, ID: 0x%X Child: 0x%X TID: %u PID: %u (%ws) Time: %u Name: %ws\n",event, EventNameToString(event),hwnd, idObject, idChild, idEventThread,pid, pExeName,time, name.m_str);
}

EventNameToString是一个按名转换某些事件ID的小助手.如果运行此代码(SimpleWinEventHook项目),你将看到大量输出,因为其中一个报告的事件是当鼠标光标位置更改时触发(除其他原因外)的EVENT_OBJECT_LOCATIONCHANGE:

Event: 0x800C (Name Change) HWND: 0x00000000000216F6, ID: 0xFFFFFFFC Child: 0x1DC TID: 39060 PID: 64932 (Taskmgr.exe) Time: 78492375 Name: (null)
...
Event: 0x8004 () HWND: 0x0000000000010010, ID: 0xFFFFFFFC Child: 0x0 TID: 72172 PID: 1756 () Time: 78493000 Name: DesktopEvent: 0x8 (Capture Start) HWND: 0x0000000000271D5A, ID: 0x0 Child: 0x0 TID: 72172 PID: 67928 (WindowsTerminal.exe) Time: 78493000 Name: c:\Dev\Temp\WinEventHooks\x64\Debug\SimpleWinEventHook.exe
...Event: 0x9 (Capture End) HWND: 0x0000000000271D5A, ID: 0x0 Child: 0x0 TID: 72172 PID: 67928 (WindowsTerminal.exe) Time: 78493109 Name: c:\Dev\Temp\WinEventHooks\x64\Debug\SimpleWinEventHook.exe

注入DLL

不是在SetWinEventHook调用者的线程上取事件,相反,可注入DLL.此类DLL必须导出事件处理器,这样安装处理器的进程,可用GetProcAddress找到函数.

如,我创建了实现与前例类似(没有进程名)的事件处理器的简单DLL,如下:

extern "C" __declspec(dllexport)
void CALLBACK OnEvent(HWINEVENTHOOK hWinEventHook, DWORD event, HWND hwnd,LONG idObject, LONG idChild, DWORD idEventThread, DWORD time) {CComPtr<IAccessible> spAcc;CComVariant child;::AccessibleObjectFromEvent(hwnd, idObject, idChild, &spAcc, &child);CComBSTR name;if (spAcc)spAcc->get_accName(CComVariant(idChild), &name);printf("Event: 0x%X (%s) HWND: 0x%p, ID: 0x%X Child: 0x%X TID: %u Time: %u Name: %ws\n",event, EventNameToString(event),hwnd, idObject, idChild, idEventThread,time, name.m_str);
}

注意,已导出该函数.代码使用printf,但不能保证目标进程有要使用的控制台.DllMain函数创建此控制台,并给它附加了标准输出句柄(否则printf将没有输出句柄,因为该进程不是用控制台引导的):

HANDLE hConsole;
BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, PVOID lpReserved) {switch (reason) {case DLL_PROCESS_DETACH:if (hConsole)//很好::CloseHandle(hConsole);break;case DLL_PROCESS_ATTACH:if (::AllocConsole()) {auto hConsole = ::CreateFile(L"CONOUT$", GENERIC_WRITE,0, nullptr, OPEN_EXISTING, 0, nullptr);if (hConsole == INVALID_HANDLE_VALUE)return FALSE;::SetStdHandle(STD_OUTPUT_HANDLE, hConsole);}break;}return TRUE;
}

注入器进程(WinHookInject项目),如果有,首先取目标进程ID:

int main(int argc, const char* argv[]) {DWORD pid = argc < 2 ? 0 : atoi(argv[1]);if (pid == 0) {printf("Warning: injecting to potentially processes with threads connected to the current desktop.\n");printf("Continue ?(y/n) ");char ans[3];gets_s(ans);if (tolower(ans[0]) != 'y')return 0;}

显示未提供PID的警告,因为为某些创建进程控制台可能会造成严重析构.如果确实想将DLL注入进桌面上的所有进程,请避免创建控制台.

一旦有了目标进程,需要加载DLL(为了简单而硬编码),并取导出的事件处理器函数:

auto hLib = ::LoadLibrary(L"Injected.Dll");
if (!hLib) {printf("DLL not found!\n");return 1;
}
auto OnEvent = (WINEVENTPROC)::GetProcAddress(hLib, "OnEvent");
if (!OnEvent) {printf("Event handler not found!\n");//没找到return 1;
}

最后一步是注册处理器.如果针对所有进程,最好限制你感兴趣的事件,尤其是嘈杂的事件.
如果只想注入DLL而不关心任何事件,请选择一个没有事件的范围,然后调用相关函数以强制在目标进程中加载DLL.剩下交给你.

auto hHook = ::SetWinEventHook(EVENT_MIN, EVENT_MAX,hLib, OnEvent, pid, 0, WINEVENT_INCONTEXT);
::GetMessage(nullptr, nullptr, 0, 0);

注意,参数包括DLL模块,处理器地址和WINEVENT_INCONTEXT标志.
以下是在记事本实例上使用此DLL时的一些输出.记事本触发事件时,首次创建控制台:

Event: 0x800B (Name Change) HWND: 0x0000000000000000, ID: 0xFFFFFFF7 Child: 0x0 TID: 34756 Time: 70717718 Name: Edit
......
Event: 0x8004 () HWND: 0x0000000000010010, ID: 0xFFFFFFFC Child: 0x0 TID: 29516 Time: 70717859 Name: Desktop 1
Event: 0x800B (Name Change) HWND: 0x00000000000A1D50, ID: 0x0 Child: 0x0 TID: 34756 Time: 70717859 Name: Untitled - Notepad
...

完整代码在zodiacon/WinEventHooks:SetWinEventHook示例,这里.

http://www.dtcms.com/a/298846.html

相关文章:

  • IPv6,你开始使用了吗?
  • MATLAB 设置默认启动路径为上次关闭路径的方法
  • Linux C : 指针
  • ZYNQ芯片,SPI驱动开发自学全解析个人笔记【FPGA】【赛灵思】
  • 您的需求已被采纳
  • 【51单片机简易红绿灯计数延时】2022-9-23
  • AIStarter平台亮点解析:从ComfyUI项目上架到一键运行的完整指南
  • I/O多路复用机制中触发机制详细解析
  • 数字化转型-AI落地金字塔法则
  • 【补题】Codeforces Round 735 (Div. 2) B. Cobb
  • 卡尔曼滤波器噪声方差设置对性能影响的仿真研究
  • 【LeetCode刷题指南】--有效的括号
  • K-近邻算法中的近似误差和估计误差
  • Win11批量部署神器winget
  • SQL基础⑯ | MySQL8新特性篇
  • JDK8保姆级安装教程
  • 新房装修是中央空调还是壁挂空调好?
  • 安卓上的迷之K_1171477665
  • 扒网站工具 HTTrack Website Copier
  • Netty中future和promise用法和区别
  • HashMap的线程安全性 vs ConcurrentHashMap
  • 从零开发Java坦克大战:架构设计与难点突破 (下)
  • Java测试题(上)
  • Effective C++ 条款02:尽量以 const, enum, inline 替换 #define
  • I/O 控制方式
  • docker部署的ragflow服务迁移 数据卷迁移
  • 内存优化:从堆分配到零拷贝的终极重构
  • Web前端:JavaScript 随机点名系统案例详解
  • 肺癌预测模型实战案例
  • C51:用DS1302时钟读取和设置时间