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

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.xdnf.cn/news/16220.html

相关文章:

  • 我从农村来到了大城市
  • 绘图库 Matplotlib Search
  • C语言案例《猜拳游戏》
  • 【C++进阶】第7课—红黑树
  • ZYNQ芯片,SPI驱动开发自学全解析个人笔记【FPGA】【赛灵思】
  • 日语学习-日语知识点小记-构建基础-JLPT-N3阶段(10):ような复习
  • JAVA_FourTEEN_常见算法
  • 2025年7月区块链与稳定币最新发展动态深度解析
  • 基于讯飞星火AI的文学作品赏析系统开发实战:从通用聊天到专业文学分析的完整技术方案
  • Netty中future和promise用法和区别
  • 07 51单片机之定时器
  • 魔百和M401H_国科GK6323V100C_安卓9_不分地区免拆卡刷固件包
  • [RPA] Excel中的字典处理
  • 【C#学习Day12笔记】抽象类、密封类与子类构造(继承)
  • C语言————原码 补码 反码 (超绝详细解释)
  • 服务器安装虚拟机全步骤
  • KNN算法:从原理到实战全解析
  • selenium 元素定位
  • OpenCV(04)梯度处理,边缘检测,绘制轮廓,凸包特征检测,轮廓特征查找
  • 医疗器械:DFEMA和PFEMA
  • 零基础也能创作专属歌曲:文心一言+蘑兔AI协同教程
  • 前端学习日记(十三)
  • 在 Ansys CFX Pre 中配置 RGP 表的分步指南
  • ERNIE-4.5-0.3B 实战指南:文心一言 4.5 开源模型的轻量化部署与效能跃升
  • cocos creator 3.8.6 websocke的一直报错WebSocket is not a constructor
  • 武汉烽火民生汇,盛大启航
  • Nginx 安装与 HTTPS 配置指南:使用 OpenSSL 搭建安全 Web 服务器
  • 无印 v1.6 视频解析去水印工具,支持多个平台
  • C++ : list的模拟
  • Qwen-MT:翻得快,译得巧