一、问题背景:
1、某设计软件AD,可以打开/编辑二维电路设计界面,其交互方式如下:
(1)鼠标右键按下拖动
(2)鼠标滚轮上下移动
(3)鼠标滚轮按下+鼠标移动 缩放
(4)ctrl + 鼠标滚轮 缩放
该软件的交互方式可以通过软件设置修改,但是基本的使用习惯就是如此,用户的一个场景是通过每台机器mstsc到另一台机器S上,而软件AD是部署运行在S上,在S上进行以上操作是没有问题的,而使用机器远程到S上进行操作的时候就会出现(3)的交互有问题,表现出来是缩放过程中设计图会突然变小,这个就很不好用,所以需要解决此问题。
二、方案设想
(1)研究windows远程桌面rdp协议,通过了解协议细节去解决
大概看了一下rdp协议,比较繁琐,没有仔细研究,pass
(2)尝试在S上安装一台显示器,推测是否为两边显示器分辨率不一致问题
因为开始的时候S上是没有连接显示器的,尝试以后发现问题仍然存在,pass
(3)通过HOOK MOUSE操作,将以上交互(3)修改为交互(4)
即打算把 MButtonDown + MouseMove 修改为 Ctrl + MouseWheel
在使用MOUSE HOOK的过程中,发现以下结论:
①Mouse Low Level Hook 可以不试用dll实现,直接在一个宿主exe中实现即可,因为不管是否使用dll,钩子过程函数的执行上下文都是安装钩子的线程。
②同时,局部钩子能hook的线程只能是安装钩子的进程所在的线程,无法跨进程安装局部钩子;
③又,鼠标钩子无法添加键盘按键属性,即无法通过给鼠标钩子添加CTRL按下这个键值,遂pass。
(4)通过 hook WH_GETMESSAGE 来解决
以下以这个方案来叙述。
三、解决过程
首先明确一点:WH_GETMESSAGE 全局钩子通过dll实现。
思路:
(1)设置目标进程名字xxx.exe
后面会根据xxx.exe查找进程id
(2)在钩子过程函数GetMsgProc中判断当前窗口对应的进程是否为目标进程
GetMsgProc原型如下
LRESULT CALLBACK GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
参数意义如下:(from https://learn.microsoft.com/zh-cn/windows/win32/winmsg/getmsgproc )
代码 [in]
类型:int
指定钩子过程是否必须处理消息。 如果代码是 HC_ACTION,则钩子过程必须处理消息。 如果代码小于零,则钩子过程必须将消息传递给 CallNextHookEx 函数,而无需进一步处理,并且应返回 CallNextHookEx 返回的值。
wParam [in]
类型:WPARAM
指定是否已从队列中删除消息。 此参数的取值可为下列值之一。
值 含义 PM_NOREMOVE 0x0000 消息尚未从队列中删除。 (一个应用程序调用了 PeekMessage 函数,且指定 PM_NOREMOVE 标志。) PM_REMOVE 0x0001 消息已从队列中删除。 (一个应用程序调用了 GetMessage,或者它调用了 PeekMessage 函数,且指定 PM_REMOVE 标志。) lParam [in]
类型:LPARAM
指向包含消息详细信息的 MSG 结构的指针。
-returns
如果代码小于零,则钩子过程必须返回 CallNextHookEx 返回的值。
如果代码大于或等于零,则强烈建议调用 CallNextHookEx 并返回它返回的值;否则,安装有 WH_GETMESSAGE 钩子的其他应用程序不会收到钩子通知,可能会因此出现错误行为。 如果钩子过程不调用 CallNextHookEx,则返回值应为零。
因此主要是处理lParam参数,其为MSG结构 (https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/ns-winuser-msg)
typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; DWORD lPrivate; } MSG, *PMSG, *NPMSG, *LPMSG;
其结构体成员如下
成员
hwnd
类型:HWND
其窗口过程接收消息的窗口的句柄。 当消息是线程消息时,此成员为 NULL 。
message
类型: UINT
消息的标识符。 应用程序只能使用低字;高字由系统保留。
wParam
类型:WPARAM
关于消息的附加信息。 确切含义取决于 消息 成员的值。
lParam
类型:LPARAM
关于消息的附加信息。 确切含义取决于 消息 成员的值。
time
类型:DWORD
消息的发布时间。
pt
类型: POINT
发布消息时的光标位置(以屏幕坐标表示)。
lPrivate
(3)通过hwnd成员获取起所在进程id,通过与xxx.exe获取的进程id对比,如果一致则进行处理,否则不需要处理。
(4)几个关键点
①windows窗口过程函数如下,需要知道GetMsgProc到WndProc,他们的参数如何对应。
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
②对于消息循环来说,msg.lParam包含了鼠标位置,对mousemove来说是客户区坐标,msg.pt包含的是桌面坐标,对于mousewheel来说,两个坐标值都是桌面坐标。
1 while (auto ret = GetMessage(&msg, nullptr, 0, 0)) 2 { 3 if (-1 == ret) 4 { 5 exit(1); 6 } 7 TCHAR szBuf[200] = { 0 }; 8 if (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST) 9 { 10 wsprintf(szBuf, TEXT("message=%0x, lParam.pt(%d, %d), msg.pt(%d, %d)\r\n"), 11 msg.message, GET_X_LPARAM(msg.lParam), GET_Y_LPARAM(msg.lParam), msg.pt.x, msg.pt.y); 12 OutputDebugString(szBuf); 13 } 14 15 if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) 16 { 17 TranslateMessage(&msg); 18 DispatchMessage(&msg); 19 } 20 }
③GetMsgProc中的MSG结构与下游消息循环如何对应
结论就是MSG结构即为消息循环的GetMessage中的第一个参数!(字段值是一样的!!!)
因此转换过程就是 改鼠标消息值、添加CTRL消息,修改鼠标坐标值、根据鼠标移动方向计算为滚轮向上还是向下滚动。
1 #include "pch.h" 2 #include "CHook.h" 3 #include "utility.h" 4 #include <string> 5 #include <windowsx.h> 6 7 #pragma data_seg("hookrws") 8 HHOOK g_hookLLKeyboard = NULL; 9 HHOOK g_hookLLMouse = NULL; 10 HHOOK g_hookGetMsg = NULL; 11 bool g_bMouseMBtnDown = false; 12 HMODULE g_hDll = NULL; 13 wchar_t g_szExeName[MAX_PATH] = { 0 }; 14 15 #pragma data_seg() 16 #pragma comment(linker,"/SECTION:hookrws,RWS") 17 18 int g_nLastMousePosY = 0; 19 char g_nLastMouseDirectionFlag = 1; 20 int g_nLastPosX = 0; 21 int g_nLastPosY = 0; 22 23 CHook& CHook::Instance() 24 { 25 static CHook hook; 26 return hook; 27 } 28 29 bool CHook::Install(const wchar_t* exe) 30 { 31 OutputDebugString(__FUNCTIONW__); 32 if (exe) 33 { 34 wsprintf(g_szExeName, TEXT("%s"), exe); 35 } 36 37 HWND hwnd = GetWindowHandlerByName(exe); 38 DWORD dwThread = 0, dwProcessId = 0; 39 dwThread = GetWindowThreadProcessId(hwnd, &dwProcessId); 40 TCHAR szBuf[200] = { 0 }; 41 wsprintf(szBuf, TEXT("[CHook::Install]thread=%u, exe=%s, hwnd=%0x, dwProcessId=%0x, dwProcessId=%d, dwThread=%0x, \r\n"), 42 GetCurrentThreadId(), exe, hwnd, dwProcessId, dwProcessId, dwThread); 43 OutputDebugString(szBuf); 44 45 return InstallGetMsgHook(0); 46 } 47 48 void CHook::UnInstall() 49 { 50 OutputDebugString(__FUNCTIONW__); 51 UnInstallLowLevelKeyboardHook(); 52 UnInstallLowLevelMouseHook(); 53 UnInstallGetMsgHook(); 54 } 55 56 57 58 bool CHook::InstallLowLevelKeyboardHook(DWORD dwThread) // 此案例不需要关注 59 { 60 if (!g_hookLLKeyboard) 61 { 62 g_hookLLKeyboard = SetWindowsHookEx(WH_KEYBOARD, LowLevelKeyboardProc, NULL, dwThread); 63 } 64 return (NULL != g_hookLLKeyboard); 65 } 66 67 bool CHook::InstallLowLevelMouseHook(DWORD dwThread) // 此案例无需关注 68 { 69 DWORD dwErr = ERROR_SUCCESS; 70 if (!g_hookLLMouse) 71 { 72 g_hookLLMouse = SetWindowsHookEx(WH_MOUSE_LL, LowLevelMouseProc, NULL, dwThread); 73 dwErr = GetLastError(); 74 } 75 return (NULL != g_hookLLMouse); 76 } 77 78 bool CHook::InstallGetMsgHook(DWORD dwThread) 79 { 80 DWORD dwErr = ERROR_SUCCESS; 81 if (!g_hookGetMsg) 82 { 83 g_hookGetMsg = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, g_hDll, dwThread); 84 dwErr = GetLastError(); 85 } 86 return (NULL != g_hookGetMsg); 87 } 88 89 void CHook::UnInstallLowLevelKeyboardHook() 90 { 91 if (g_hookLLKeyboard) 92 { 93 UnhookWindowsHookEx(g_hookLLKeyboard); 94 g_hookLLKeyboard = NULL; 95 } 96 } 97 98 void CHook::UnInstallLowLevelMouseHook() 99 { 100 if (g_hookLLMouse) 101 { 102 UnhookWindowsHookEx(g_hookLLMouse); 103 g_hookLLMouse = NULL; 104 } 105 } 106 107 void CHook::UnInstallGetMsgHook() 108 { 109 if (g_hookGetMsg) 110 { 111 UnhookWindowsHookEx(g_hookGetMsg); 112 g_hookGetMsg = NULL; 113 } 114 } 115 116 117 118 119 LRESULT CALLBACK LowLevelKeyboardProc(int code, WPARAM wParam, LPARAM lParam) 120 { 121 if (HC_ACTION == code) 122 { 123 TCHAR szBuf[100] = { 0 }; 124 wsprintf(szBuf, TEXT("[Keyboard]thread=%u, code=%d, w=%u, l=%ld\r\n"), GetCurrentThreadId(), code, wParam, lParam); 125 OutputDebugString(szBuf); 126 } 127 128 return CallNextHookEx(g_hookLLKeyboard, code, wParam, lParam); 129 } 130 131 LRESULT CALLBACK LowLevelMouseProc(int code, WPARAM wParam, LPARAM lParam) 132 { 133 if (HC_ACTION == code) 134 { 135 TCHAR szBuf[200] = { 0 }; 136 std::wstring data; 137 MSLLHOOKSTRUCT* st = (MSLLHOOKSTRUCT*)(lParam); 138 short delta = 0; 139 switch (wParam) 140 { 141 case WM_MOUSEWHEEL: 142 delta = GET_WHEEL_DELTA_WPARAM(st->mouseData); 143 if (delta > 0) 144 { 145 data = L" mouse middle key forward"; 146 } 147 else 148 { 149 data = L" mouse middle key backward"; 150 } 151 152 wsprintf(szBuf, TEXT("[Mouse]pt=[%d, %d], delta=%d, thread=%u, code=%d, w=%u, l=%ld, %s\r\n"), 153 st->pt.x, st->pt.y, delta, GetCurrentThreadId(), code, wParam, lParam, data.c_str()); 154 OutputDebugString(szBuf); 155 break; 156 157 default: 158 wsprintf(szBuf, TEXT("[Mouse]thread=%u, code=%d, w=%u, l=%ld\r\n"), GetCurrentThreadId(), code, wParam, lParam); 159 OutputDebugString(szBuf); 160 break; 161 } 162 } 163 164 return CallNextHookEx(g_hookLLMouse, code, wParam, lParam); 165 }
166 /////////////////////////////////////////////////////////// 重点看这里 ///////////////////////////////////////////////////////////////////////////// 167 LRESULT CALLBACK GetMsgProc(int code, WPARAM wParam, LPARAM lParam) 168 { 169 MSG* pMsg = (MSG*)(lParam); 170 if (HC_ACTION == code) 171 { 172 HWND hWnd = pMsg->hwnd; 173 TCHAR szBuf[200] = { 0 }; 174 175 if (WM_MBUTTONDOWN == pMsg->message && IsSameProcess(g_szExeName, hWnd)) 176 { 177 g_nLastPosX = pMsg->pt.x; 178 g_nLastPosY = pMsg->pt.y; 179 wsprintf(szBuf, TEXT("[WM_MBUTTONDOWN] xy(%d, %d), exe(%s)"), g_nLastPosX, g_nLastPosY, g_szExeName); 180 OutputDebugString(szBuf); 181 } 182 else if (WM_MOUSEMOVE == pMsg->message && IsSameProcess(g_szExeName, hWnd)) 183 { 184 // 默认鼠标向上滑动 185 char flag = 1; 186 int temp = g_nLastMousePosY; 187 if (pMsg->pt.y > temp) 188 { 189 // 鼠标向下 190 flag = -1; 191 } 192 // 如果y坐标与上次相同则使用上次的方向标志 193 else if (pMsg->pt.y == temp) 194 { 195 flag = g_nLastMouseDirectionFlag; 196 } 197 g_nLastMouseDirectionFlag = flag; 198 199 // 按下了中键 200 if (pMsg->wParam & MK_MBUTTON) 201 { 202 // 转换为ctrl + movewheel 203 pMsg->message = WM_MOUSEWHEEL; 204 #if 0 205 LPARAM ptParam = pMsg->pt.y; 206 ptParam = (ptParam << (sizeof(DWORD) / 2 * 8)) | pMsg->pt.x; 207 #else 208 LPARAM ptParam = g_nLastPosY; 209 ptParam = (ptParam << (sizeof(DWORD) / 2 * 8)) | g_nLastPosX; 210 pMsg->pt.x = g_nLastPosX; 211 pMsg->pt.y = g_nLastPosY; 212 #endif 213 214 pMsg->lParam = ptParam; 215 216 DWORD dwParam = WHEEL_DELTA * flag; 217 dwParam = (dwParam << (sizeof(DWORD) / 2 * 8)) | (MK_CONTROL - 0); 218 pMsg->wParam = dwParam; 219 220 } 221 } 222 else if (WM_MOUSEWHEEL == pMsg->message && IsSameProcess(g_szExeName, hWnd)) 223 { 224 wsprintf(szBuf, TEXT("[MouseWheel] pt(%d, %d), pMsg.wParam.hi(delta)=%d, pMsg.wParam.lo(mk)=%d\r\n"), 225 pMsg->pt.x, pMsg->pt.y, GET_WHEEL_DELTA_WPARAM(pMsg->wParam), GET_KEYSTATE_WPARAM(pMsg->wParam)); 226 OutputDebugString(szBuf); 227 } 228 229 } // if (HC_ACTION == code) 230 231 g_nLastMousePosY = pMsg->pt.y; 232 233 return CallNextHookEx(g_hookGetMsg, code, wParam, lParam); 234 }
IsSameProcess函数实现
1 bool IsSameProcess(const wchar_t* exe, HWND hWnd) 2 { 3 if (!exe || !IsWindow(hWnd)) 4 { 5 TCHAR szBuf[80] = { 0 }; 6 // wsprintf(szBuf, TEXT("hwnd=%0x, exe=%s, NOT!!!!"), hWnd, exe); 7 //OutputDebugString(szBuf); 8 return false; 9 } 10 // 根据exe获取PID 11 DWORD dwPid = GetProcessIDByName(exe); 12 // 根据窗口句柄获取PID 13 DWORD dwPid2 = 0; 14 GetWindowThreadProcessId(hWnd, &dwPid2); 15 TCHAR szBuf2[80] = { 0 }; 16 //wsprintf(szBuf2, TEXT("hwnd=%0x, exe=%s, pid1=%u, pid2=%u"), hWnd, exe, dwPid, dwPid2); 17 //OutputDebugString(szBuf2); 18 return ((dwPid == dwPid2) && (dwPid != 0)); 19 }
1 DWORD GetProcessIDByName(const wchar_t* pName) 2 { 3 HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 4 if (INVALID_HANDLE_VALUE == hSnapshot) { 5 return NULL; 6 } 7 PROCESSENTRY32 pe = { sizeof(pe) }; 8 for (BOOL ret = Process32First(hSnapshot, &pe); ret; ret = Process32Next(hSnapshot, &pe)) 9 { 10 if (wcscmp(pe.szExeFile, pName) == 0) 11 { 12 CloseHandle(hSnapshot); 13 return pe.th32ProcessID; 14 } 15 } 16 CloseHandle(hSnapshot); 17 return 0; 18 }
标签:wParam,lParam,exe,pt,pMsg,HOOK,GETMESSAGE,WH,szBuf From: https://www.cnblogs.com/cuish/p/18127771