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! :)
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's Tooltip documentation, which I used throughout.
Warning: 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.
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.
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 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:
WH_CBT hook to intercept window creation.
WH_CALLWNDPROC hook to intercept
TTM_ACTIVATE and, if the parameter is
TRUE, post another message with a parameter of
This system worked all right, but also suffered from performance problems.
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
BOOL CALLBACK KillTT_EnableTooltips(HWND hwnd,LPARAM lParam)
KillTT_Hook simply registers a system-wide
WH_CBT 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 is called. First, however, when the DLL is loaded,
extern "C" BOOL WINAPI _DllMainCRTStartup(
if(dwReason == DLL_PROCESS_ATTACH)
if(hInstance == NULL)
hInstance = (HINSTANCE)hDllHandle;
bDLLPresent = (BOOL*)
HeapAlloc(GetProcessHeap(), 0, sizeof(BOOL));
*bDLLPresent = TRUE;
HMODULE hmSelf = LoadLibrary("killtt_helper.dll");
CreateThread(NULL, 1024, &WaitForUnload,
hmSelf, 0, &dwThreadID);
if(dwReason == DLL_PROCESS_DETACH)
*bDLLPresent = FALSE;
Several important notes:
- 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.
- 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.
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_RELAYEVENT, 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.)
mov eax, 0xFFFFFFFF
cmp dword ptr[eax], 0
mov eax, 0x88888888
cmp dword ptr[eax], 0
mov ebp, esp
mov eax, dword ptr[ebp + 12]
cmp eax, TTM_POPUP
cmp eax, TTM_TRACKACTIVATE
cmp eax, TTM_TRACKPOSITION
cmp eax, TTM_RELAYEVENT
cmp eax, TTM_ACTIVATE
mov eax, 0xBBBBBBBB
xor eax, eax
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 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.
- 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.
- 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.
- Fix code for Win98.
- Optimize assembly.
- 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.
I've worked with C++ for about 9 years for personal work. Only recently have I begun to learn the true power of things such as templates and operator overloading.
I've also done work in a few other languages, and am proficient with Java, shell scripts and batch files, PHP, and HTML. I do CSS and .NET sometimes, but nothing advanced.
I am 18 years old and live in Ohio, USA. I enrolled in college when I was 16, and plan on finishing in September, 2006.