|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
IntroductionThere is a lot of confusion about how to set up and use global hook functions. This essay attempts to clear up some of these issues. It may be worth pointing out that Flounders disapprove of hooks in general, but these hooks are felt to be acceptable. Note that none of the problems described below occur if you are simply hooking operations from your own process. This only happens if you want to get events system-wide. The key problem here is address space. When a global DLL executes, it executes in the context of the process whose event it is hooking. This means that the addresses it sees even for its own variables are addresses in the context of the target process. Since this is a DLL, it has a private copy of its data for every process that is using it, which means that any values you set in variables global in the DLL (such as those declared at file-level) are private variables, and will not inherit anything from the original DLL's context. They are going to be initialized anew, meaning, typically, they will be zero. A recent post even suggested the notion of storing a callback address in the DLL. This is impossible. Well, it is not impossible to store it, but it is impossible to use it. What you've stored is a bunch of bits. Even if you follow the instructions below to create a shared memory variable that is visible to all instances of the DLL, the bunch of bits (which you think is an address) is actually meaningful as an address only in the context of the process that stored it. For all other processes, this is merely a bunch of bits, and if you try to use it as an address, you will call some address in the process whose event is being intercepted, which is completely useless. It will most likely just cause the app to crash. This concept of separate address spaces is a hard concept to grasp. Let me use a picture to illustrate it.
But here's the kicker: the same address is a coincidence. It is absolutely, positively, not guaranteed. Take a look at Process B. When the event is hooked in Process B, the DLL is mapped in. But the addresses it occupied in Your Process and Process A are not available in Process B's address space. So all that happens is the code is relocated into a different address in Process B. The code is happy; it actually doesn't care what address it is executing at. The data addresses are adjusted to refer to the new position of the data, and even the shared data is mapped into a different set of addresses, so it is referenced differently. If you were running the debugger in Process B and looked at While I've referred to the similar placement as a coincidence, the "coincidence" is a bit contrived; Windows attempts whenever possible to map DLLs into the same virtual location as other instances of the same DLL. It tries. It may not be able to succeed. If you know a little bit (enough to be dangerous), you can say, Aha! I can rebase my DLL so that it loads at an address that does not conflict, and I'll be able to ignore this feature. This is a prime example of a little knowledge being a dangerous thing. You cannot guarantee this will work in every possible executable that can ever run on your computer! Because this is a global hook DLL, it can be invoked for Word, Excel, Visio, VC++, and six thousand applications you've never heard of, but you might run someday or your customers might run. So forget it. Don't try rebasing. You will lose, eventually. Usually at the worst possible time, with your most important customer (for example, the magazine reviewer of your product, or your very best dollar-amount customer who is already nervous about other bugs you may have had...) Assume that the shared data segment is "moveable". If you didn't understand this paragraph, you don't know enough to be dangerous, and you can ignore it. There are other implications to this relocation. In the DLL, if you had stored a pointer to a callback function in Your Process, it is meaningless for the DLL to execute it in Process A or Process B. The address will cause a control transfer to the location it designates, all right, but that transfer will happen into Process A or Process B's address space, which is pretty useless, not to mention almost certainly fatal. It also means you can't use any MFC in your DLL. It can't be an MFC DLL, or an MFC Extension DLL. Why? Because it would call MFC functions. Where are they? Well, they're in your address space. Not in the address space of Process A, which is written in Visual Basic, or Process B, which is written in Java. So you have to write a straight-C DLL, and I also recommend ignoring the entire C runtime library. You should only use the API. Use There are many solutions to how your DLL communicates to its controlling server. One solution is to use You can also use queues of information in the shared memory area, but I'm going to consider that topic outside the scope of this essay. In the I strongly suggest using Registered Window Messages for this purpose (see my essay on Message Management). You can use the Getting the The first thing you have to do is create the shared data segment. This is done by using the #pragma data_seg(".JOE") HANDLE hWnd = NULL; #pragma dta_seg() #pragma comment(linker, "/section:.JOE,rws") Any variables you declare in the scope of the It appears at the moment that this precludes using arrays of C++ objects in the shared data segment, because you cannot initialize a C++ array of user-defined objects (their default constructors are supposed to do this). This appears to be a fundamental limitation, an interaction between formal C++ requirements and the Microsoft extensions that require initializers be present. The You typically provide some mechanism to set the window handle, for example void SetWindow(HWND w)
{
hWnd = w;
}
although this is often combined with setting the hook itself as I will show below. Sample: A Mouse Hookheader file (myhook.h)The functions #define UWM_MOUSEHOOK_MSG \ _T("UMW_MOUSEHOOK-" \ "{B30856F0-D3DD-11d4-A00B-006067718D04}") source file (myhook.cpp)#include "stdafx.h" #include "myhook.h" #pragma data_seg(".JOE") HWND hWndServer = NULL; #pragma data_seg() #pragma comment("linker, /section:.JOE,rws") HINSTANCE hInstance; UINT HWM_MOUSEHOOK; HHOOK hook; // Forward declaration static LRESULT CALLBACK msghook(int nCode, WPARAM wParam, LPARAM lParam); /**************************************************************** * DllMain * Inputs: * HINSTANCE hInst: Instance handle for the DLL * DWORD Reason: Reason for call * LPVOID reserved: ignored * Result: BOOL * TRUE if successful * FALSE if there was an error (never returned) * Effect: * Initializes the DLL. ****************************************************************/ BOOL DllMain(HINSTANCE hInst, DWORD Reason, LPVOID reserved) { switch(Reason) { /* reason */ //********************************************** // PROCESS_ATTACH //********************************************** case DLL_PROCESS_ATTACH: // Save the instance handle because we need it to set the hook later hInstance = hInst; // This code initializes the hook notification message UWM_MOUSEHOOK = RegisterWindowMessage(UWM_MOUSEHOOK_MSG); return TRUE; //********************************************** // PROCESS_DETACH //********************************************** case DLL_PROCESS_DETACH: // If the server has not unhooked the hook, unhook it as we unload if(hWndServer != NULL) clearMyHook(hWndServer); return TRUE; } /* reason */ /**************************************************************** * setMyHook * Inputs: * HWND hWnd: Window whose hook is to be set * Result: BOOL * TRUE if the hook is properly set * FALSE if there was an error, such as the hook already * being set * Effect: * Sets the hook for the specified window. * This sets a message-intercept hook (WH_GETMESSAGE) * If the setting is successful, the hWnd is set as the * server window. ****************************************************************/ __declspec(dllexport) BOOL WINAPI setMyHook(HWND hWnd) { if(hWndServer != NULL) return FALSE; hook = SetWindowsHookEx( WH_GETMESSAGE, (HOOKPROC)msghook, hInstance, 0); if(hook != NULL) { /* success */ hWndServer = hWnd; return TRUE; } /* success */ return FALSE; } // SetMyHook /**************************************************************** * clearMyHook * Inputs: * HWND hWnd: Window whose hook is to be cleared * Result: BOOL * TRUE if the hook is properly unhooked * FALSE if you gave the wrong parameter * Effect: * Removes the hook that has been set. ****************************************************************/ __declspec(dllexport) BOOL clearMyHook(HWND hWnd) { if(hWnd != hWndServer) return FALSE; BOOL unhooked = UnhookWindowsHookEx(hook); if(unhooked) hWndServer = NULL; return unhooked; } /**************************************************************** * msghook * Inputs: * int nCode: Code value * WPARAM wParam: parameter * LPARAM lParam: parameter * Result: LRESULT * * Effect: * If the message is a mouse-move message, posts it back to * the server window with the mouse coordinates * Notes: * This must be a CALLBACK function or it will not work! ****************************************************************/ static LRESULT CALLBACK msghook(int nCode, WPARAM wParam, LPARAM lParam) { // If the value of nCode is < 0, just pass it on and return 0 // this is required by the specification of hook handlers if(nCode < 0) { /* pass it on */ CallNextHookEx(hook, nCode, wParam, lParam); return 0; } /* pass it on */ // Read the documentation to discover what WPARAM and LPARAM // mean. For a WH_MESSAGE hook, LPARAM is specified as being // a pointer to a MSG structure, so the code below makes that // structure available LPMSG msg = (LPMSG)lParam; // If it is a mouse-move message, either in the client area or // the non-client area, we want to notify the parent that it has // occurred. Note the use of PostMessage instead of SendMessage if(msg->message == WM_MOUSEMOVE || msg->message == WM_NCMOUSEMOVE) PostMessage(hWndServer, UWM_MOUSEMOVE, 0, 0); // Pass the message on to the next hook return CallNextHookEx(hook, nCode, wParam, lParam); } // msghook The server applicationIn the header file, add this to the protected section of the class: afx_msg LRESULT OnMyMouseMove(WPARAM,LPARAM); In the application file, add this at the front of the file somewhere: UINT UWM_MOUSEMOVE = ::RegisterWindowMessage(UWM_MOUSEMOVE_MSG); In the ON_REGISTERED_MESSAGE(UWM_MOUSEMOVE, OnMyMouseMove) In your application file, add the following function: LRESULT CMyClass::OnMyMouseMove(WPARAM, LPARAM)
{
// ...do stuff here
return 0;
}
You can download this project and build it. The real key is the DLL subproject; the rest is decorative fluff that uses it. There are several other techniques shown in this example, including various drawing techniques, the use of The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft. Send mail to newcomer@flounder.com with questions or comments about this article.
| ||||||||||||||||||||