[译]KillTT:没有更多的工具提示!
By robot-v1.0
本文链接 https://www.kyfws.com/applications/killtt-no-more-tooltips-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 7 分钟阅读 - 3410 个词 阅读量 0[译]KillTT:没有更多的工具提示!
原文地址:https://www.codeproject.com/Articles/11781/KillTT-No-More-Tooltips
原文作者:Peregrine Falcon
译文由本站 robot-v1.0 翻译
前言
Disabling tooltips system-wide - efficiently!
全系统禁用工具提示-有效!
介绍(Introduction)
我前一段时间写了这个程序,最后决定在这里发布它,看看它是如何收到的.请轻柔,这是我的第一篇文章! :)(I wrote this program some time back, and finally decided to post it here and see how it is received. Please be gentle, this is my first article! :))
我通常在anyyances.org上帮助与Windows有关的问题的人.那里的几个人希望摆脱那些叫做工具提示的奇妙的黄色小盒子.虽然可以通过注册表修改来禁用其较大的表亲(即气球提示),但尚无已知方法来禁用所有工具提示.该程序解决了该问题.我将向您简要介绍其工作原理,并让您自己尝试一下.(I routinely help people with Windows-related problems on annoyances.org. Several people there wanted to be rid of those wonderful little yellow boxes called tooltips. While their larger cousins, balloon tips, can be disabled via a registry modification, no known way exists to disable all tooltips. This program solves that problem. I will give you a bare-bones overview of how it works and let you play around with it yourself.)
MSDN的(MSDN’s) 工具提示文档(Tooltip documentation) ,这是我自始至终使用的.(, which I used throughout.)
**警告:(Warning:)**这段代码在Windows 98上无法使用,我不知道为什么,现在也不在乎.当前,此代码仅在Windows XP上调试.(This code breaks on Windows 98, and I have no clue why, nor do I care at this point. Currently, this code is only debugged on Windows XP.)
背景(Background)
为了帮助说明如何实现当前的实现,请允许我解释一下初始实现.首先,我搜索了MSDN,找到了(To help show how I arrived at my current implementation, let me explain the initial implementation. First, I searched MSDN and found the) TTM_ACTIVATE
信息.这似乎正是我所需要的:(message. This appeared to be exactly what I needed:)
TTM_ACTIVATE
没有指针参数,因此可以跨进程发送.(has no pointer parameters, and thus may be sent cross-process.)- 在大多数情况下,应用程序将无需发送此消息.(In most cases, the application will have no need to send this message.)
- 此消息似乎没有任何副作用.禁用该控件后,应用程序会认为用户只是没有将鼠标悬停在正确的位置上.(This message doesn’t appear to have any side effects. When the control is disabled, the application thinks that the user just hasn’t hovered the mouse over the right spot.) 最后,此消息确实是我选择用来禁用工具提示控件的方式.(In the end, this message was indeed the means I chose to use to disable tooltip controls.)
问题(The problem)
枚举窗口并禁用所有工具提示控件只是一个开始.随着新应用程序的启动,将创建新的工具提示控件.此外,Windows资源管理器还会发送(Enumerating windows and disabling all tooltip controls is only the beginning. As new applications are started, new tooltip controls are created. In addition, Windows Explorer also sends a) TTM_ACTIVATE
每当将新的Windows按钮添加到任务栏时,就会向其控件发送消息.因此出现了问题:拦截窗口的创建和发送(message to its control whenever a new window’s button is added to the task bar. Hence the problem: Intercept the creation of windows and the sending of) TTM_ACTIVATE
消息.我立即拒绝民意测验是有效的,但过于耗费资源.系统具有数百个窗口,并且跨进程边界复制窗口的类名非常昂贵.因此,我决定采用这种方法:可以通过以下方式注册系统范围的挂钩函数(messages. I rejected polling immediately as effective, but too resource-hungry. A system has hundreds of windows, and copying the class name of a window across process boundaries is quite expensive. Thus I settled on this approach: A system-wide hook function can be registered via) SetWindowsHookEx
.该挂钩可以拦截许多事件,包括窗口创建以及每个已发送或已发布的消息.我设置了两个钩子:(. This hook can intercept a number of events, including window creation and every sent or posted message. I set up two hooks:)
- 一种(A)
WH_CBT
钩子拦截窗口创建.(hook to intercept window creation.) - 一种(A)
WH_CALLWNDPROC
钩子拦截(hook to intercept)TTM_ACTIVATE
并且,如果参数是(and, if the parameter is)TRUE
,发布另一个参数为的消息(, post another message with a parameter of)FALSE
.(.) 该系统可以正常工作,但也存在性能问题.(This system worked all right, but also suffered from performance problems.)
解决方案(The solution)
让我们从头开始介绍当前的实现.用户界面是系统"托盘"中的一个图标,单击该图标可以启用或禁用所有阻止.启动应用程序时,将枚举所有窗口,并发送工具提示窗口.(Let’s walk through the current implementation from the beginning. The user interface is an icon in the system “tray” which, when clicked, either enables or disables all blocking. When the application is started, all windows are enumerated, and tooltip windows are sent a) TTM_ACTIVATE
((() FALSE
).().)
BOOL CALLBACK KillTT_EnableTooltips(HWND hwnd,LPARAM lParam)
{
CHAR buf[256];
GetClassName(hwnd,buf,256);
if(lstrcmp(buf,TEXT("tooltips_class32"))==0)
{
PostMessage(hwnd,TTM_ACTIVATE,lParam,0);
}
EnumChildWindows(hwnd,&KillTT_EnableTooltips,lParam);
return TRUE;
}
// ...
KillTT_Hook();
// ...
KillTT_SetBlock(kill);
if(kill)
EnumWindows(&KillTT_EnableTooltips,0);
KillTT_Hook
只需在系统范围内注册(simply registers a system-wide) WH_CBT
钩.现在,在创建窗口时,将钩子DLL加载到正在创建该窗口的进程中,然后使用钩子函数(hook. Now, when a window is created, the hook DLL is loaded into the process that is creating the window, and the hook function) CBTHook
叫做.但是,首先,在加载DLL时,(is called. First, however, when the DLL is loaded,)
extern "C" BOOL WINAPI _DllMainCRTStartup(
HANDLE hDllHandle,
DWORD dwReason,
LPVOID lpreserved)
{
if(dwReason == DLL_PROCESS_ATTACH)
{
if(hInstance == NULL)
hInstance = (HINSTANCE)hDllHandle;
// Allocate a flag that determines if
// this DLL is still loaded
bDLLPresent = (BOOL*)
HeapAlloc(GetProcessHeap(), 0, sizeof(BOOL));
*bDLLPresent = TRUE;
DWORD dwThreadID;
// Load ourselves.
HMODULE hmSelf = LoadLibrary("killtt_helper.dll");
// Create a monitor thread to unload us.
CreateThread(NULL, 1024, &WaitForUnload,
hmSelf, 0, &dwThreadID);
}
if(dwReason == DLL_PROCESS_DETACH)
{
*bDLLPresent = FALSE;
}
return TRUE;
}
重要注意事项:(Several important notes:)
- 当一个钩子被调用时,DLL被加载,函数运行,并且DLL被再次卸载.您可能会想到,如果DLL中的代码当时正在运行,这可能会带来一些讨厌的后果!因此,我确保将DLL保留在内存中,直到准备好将其卸载为止.(When a hook is called, the DLL is loaded, the function run, and the DLL is unloaded again. As you might imagine, this can have some nasty consequences if code from the DLL was running at the time! Thus, I make sure the DLL remains in memory until I’m ready to unload it.)
- 必须有一个标志来告知DLL是否存在,但是它不能成为DLL数据段的一部分,否则它将随DLL一起卸载.因此,它在堆上分配.(There must be a flag to tell whether the DLL is present, but it cannot be part of the data segment of the DLL, or it will be unloaded with the DLL. Thus, it is allocated on the heap.)
当钩子函数被调用时,它将检查其参数.如果正在创建工具提示窗口,则它会使用标准的子类化技术:而不是使用DLL中的函数,而是在堆栈上创建一个最初以汇编形式编写的存根,并将窗口过程设置为该存根.存根确保DLL存在,然后启用阻止.如果两者都为真,则检查消息及其参数.如果消息是(When the hook function is called, it checks its parameters. If a tooltip window is being created, it uses standard subclassing techniques with a twist: Rather than using a function from the DLL, a stub, originally written in assembly, is created on the stack and the window procedure is set to that stub. The stub makes sure the DLL is present, and then that blocking is enabled. If both are true, the message and its parameters are checked. If the message is)
TTM_ACTIVATE
,(,)TTM_POPUP
,(,)TTM_TRACKACTIVATE
,(,)TTM_TRACKPOSITION
, 要么(, or)TTM_RELAYEVENT
,则将其丢弃.否则,它将传递给原始Windows过程. (请注意,在整个过程中,我使用占位符值作为地址.另外请注意,我是(, it is discarded. Otherwise, it is passed on to the original Windows procedure. (Note that throughout, I use placeholder values for addresses. Also note that I am)**不(not)**装配大师.我讨厌汇编,而且写得不好.)(an assembly guru. I hate assembly, and write it poorly.))
// lParam
// wParam
// uMsg
// hwnd
// Return Address
// Old EBP <-EBP
push ebp
mov eax, 0xFFFFFFFF // DllPresent flag
cmp dword ptr[eax], 0
je retold
mov eax, 0x88888888 // Hooking flag
cmp dword ptr[eax], 0
je retold
mov ebp, esp
mov eax, dword ptr[ebp + 12]
cmp eax, TTM_POPUP
je ret0
cmp eax, TTM_TRACKACTIVATE
je ret0
cmp eax, TTM_TRACKPOSITION
je ret0
cmp eax, TTM_RELAYEVENT
je ret0
cmp eax, TTM_ACTIVATE
je ret0
retold:
pop ebp
pop eax
push 0xAAAAAAAA // Old window proc
push eax
mov eax, 0xBBBBBBBB // CallWindowProc
jmp eax
ret0:
xor eax, eax
pop ebp
ret 16
我很快意识到一个小缺陷.在创建和销毁多个工具提示Windows时,堆上会留下一小部分内存.如果创建并销毁了许多工具提示Windows,则这种小的资源泄漏可能会成为一个大问题.因此,包含在(I quickly realized a small flaw. As multiple tooltip Windows are created and destroyed, a small chunk of memory is left behind on the heap. If many tooltip Windows are created and destroyed, this small resource leak could become a big problem. Thus, the code included in)ttproc.asm(ttproc.asm)显示了汇编器函数的完整源代码,其中包括一个分支,该分支在从处理中返回时删除该过程.(shows the full source of the assembler function, which includes a branch that deletes the procedure upon return from processing of) WM_NCDESTROY
,最后发送到窗口的消息.(, the last message sent to a window.)
我学到的是(What I learned)
-
挂钩设置(Hooks set via)
SetWindowsHooksEx
如果使用不正确,会对性能产生重大影响.(have a major performance impact if used incorrectly.) -
挂钩DLL仅在挂钩功能期间加载.不要再依赖他们的存在!(Hook DLL’s are loaded only for the duration of the hook function. Don’t rely on their presence at any other time!)
-
评论!我的代码中需要更多注释!(Comments! I need more comments in my code!)
-
如果要将代码打包到结构中:(If you’re packing code into a structure:)
- 测试一下!逐步进入调试器.字节顺序可能与您预期的不同,导致胡说八道.(Test it! Step through in a debugger. Byte order may be different than you expect, resulting in nonsense.)
- 不要忘记#pragma pack!操作码之间添加的填充确实使事情变得混乱.(Don’t forget #pragma pack! Padding added in between opcodes really messes things up.)
- 一律致电(Always call)
FlushInstructionCache
,否则可能会发生坏事!一台机器在没有此调用的情况下会偶发崩溃.这花了我几个小时来调试.(, or bad things may happen! One machine exhibited sporadic crashes without this call. This took me hours to debug.) - 第一次就把它弄对!结构中的代码很难重做.(Get it right the first time! Code in structures is hard to rework.)
去做(TODO)
- 修复Win98的代码.(Fix code for Win98.)
- 优化组装.(Optimize assembly.)
- 修复DLL卸载机制.(Fix DLL unloading mechanism.) 评论或问题表示赞赏!这是一个相当难以维护的项目,但是如果有必要进行更改,我将对其进行更新.(Comments or questions appreciated! This is a fairly un-maintained project, but I will update it if something warrants the change.)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C++ ASM VC6 Windows WinXP Visual-Studio Dev 新闻 翻译