Visual Studio 2010!

Read now >

View Now
DevSource RSS FEEDS
XML Want an easy way to keep up with breaking tech news? And the Get DevSource headlines delivered to your desktop with RSS.
ADVERTISEMENT
ADVERTISEMENT

 

DevSource.com: Your Source for Visual Studio on Facebook
ADVERTISEMENT
Globally Hooking Windows Messages in .NET
By John Mueller

Rate This Article: Add This Article To:

Globally Hooking Windows Messages in .NET - ' Creating the DLL '
( Page 3 of 3 )

It's All About Memory

The memory problems that developers have to overcome with global hooks can prove confusing, because the .NET Framework hides the defaults of memory management from view. This example has to consider three memory areas.

First, you must consider the client memory area. The DLL executes in this memory context, so it can access the messages that the client generates. The callback you provide in the DLL receives the message from Windows.

Second, you must consider DLL shared memory. When you use a global hook, the system actually creates multiple instances of the DLL, one for each application that generates the hook of interest. Consequently, you need a shared DLL memory area so that all of the DLL instances can access essential information, such as the handle of the server.

The shared memory area can also contain pointers to server memory, even though the DLL can't access this memory directly. If you ever want to see this effect, open a command prompt, type TaskList /m <Name of your DLL>, and press Enter.

Figure 1 shows an example of how the HookLib.DLL used in this example attaches itself to the applications on the system.

Third, the server, the application that sets and monitors the hooks, must also provide careful memory management. For example, if you want to obtain information about the messages that your hook is obtaining from other applications on the system, you must provide a memory area for it to use. However, the kind of memory you supply is important. You must provide unmanaged memory from the global heap. Failure to observe memory limits will cause your system to freeze and do other odd things that you won't like.

Creating the DLL

You create your global hook library as a C++ DLL. In fact, you can't even use a managed DLL; you must use the plain run of the mill DLL that developers have used since Windows began. The downloadable example is generic.

Click here to get the code used in this article.

With a few small tweaks, you can modify it to meet most application needs. However, it's important to know what this DLL is doing. The first thing you need to know is that the DLL must create a shared memory area that all instances can use to store information about the server. This information appears as:

#pragma data_seg(".JPM")
// Define the server handle.
HWND hWndServer = NULL;

// Contains the message information.
LPMSG PassedMsg = NULL;

#pragma data_seg()
// Include a section comment that the data segment is read/write/shared.
#pragma comment(linker, "/section:.JPM,rws")

The code begins by creating a named data segment. Make sure you use something unique; I used my initials for this example, but many developers rely on GUIDs for the memory segment to ensure there aren't any conflicts with other applications. The shared memory area contains two variables. The first is a pointer to the server application so the library knows where to send messages. The second is a pointer to a message data structure that contains information about the message the hook library receives from the client.

The data segment begins and ends with #pragma data_seg(). The #pragma comment() is essential. It tells the linker to name this section a certain way and to mark it as read-write-shared. Place any variables you need for communication between DLLs in this shared section.

The hook library has to have some way to communicate with the server application. It uses custom messages to perform this task. You have to register the special messages with Windows using the RegisterWindowMessage() function. All you need to supply is a unique value for the message, so that Windows can identify it from other messages on the system. Both the server and the hook library must register the messages. In this case, the hook library registers the messages whenever a client loads it, as shown here (see the downloadable code for details):

THIS_MSG = RegisterWindowMessage(INIT_THIS_MSG);
START_SVC = RegisterWindowMessage(INIT_SVC_START_MSG);
STOP_SVC = RegisterWindowMessage(INIT_SVC_STOP_MSG);

These three messages tell the server when the hook library has started hooking messages, stopped hooking messages, and is sending a message for processing. To begin hooking messages, the server calls the SetHook() function. Likewise, when the server wants to stop hooking messages, it calls the ClearHook() function. These functions work very much like the managed equivalents discussed in the previous article, "Hooking Windows Messages in .NET". Listing 1 shows what these two functions look like when written in C++ for a DLL.

Listing 1: Setting and Clearing Hooks Using a DLL

__declspec(dllexport) BOOL SetHook(
   int idHook, HWND hWnd, DWORD dwThreadId, LPMSG hMsg)
{
   // Exit if the server is already running.
   if (hWndServer != NULL)
      return FALSE;

   // Set the hook. The arguments include the message type,
   // the function that will handle the call, the current
   // instance, and a flag that defines this as a global
   // hook.
   hMsgHook = 
      SetWindowsHookEx(
         idHook, (HOOKPROC)ProcessMessage, hInst, dwThreadId);

   // Verify the hook.
   if (hMsgHook != NULL)
   {
      // The hook succeeded.
      hWndServer = hWnd;

      // Assign the memory handle to our local variable.
      PassedMsg = hMsg;

      // Tell the client the service is started.
      PostMessage(hWndServer, START_SVC, 0, 0);

      // Show success.
      return TRUE;
   }

   // The hook failed.
   return FALSE;
}

__declspec(dllexport) BOOL ClearHook(HWND hWnd)
{
   // Exit if the hook isn't in place or if someone other
   // than the original caller tries to execute this
   // function.
   if (hWnd != hWndServer || hWnd == NULL)
      return FALSE;

   // Clear the hook.
   BOOL lCleared = UnhookWindowsHookEx(hMsgHook);

   // Verify that the hook is clear.
   if (lCleared)
   {
      // Tell the client the service is stopped.
      PostMessage(hWndServer, STOP_SVC, 0, 0);

      // Reset the application handle.
      hWndServer = NULL;

      // Reset the message pointer.
      PassedMsg = NULL;
   }

   // Return the result of the call for error handling
   // when required.
   return lCleared;
}

As you can see, the structure of these functions does come very close to the managed functions. The biggest difference is that the SetWindowsHookEx() function receives an instance handle in this case. If you don't supply an instance handle, then Windows won't know which instance of the DLL should receive a particular message and you'll run into yet more memory problems.

These functions also signal the starting and stopping of the service by posting a message. The managed application receives the message and processes it as an event.

See Working with Windows Messages in .NET portion of this series for more information on working with messages.

The callback function, ProcessMessage(), works exactly the same as the managed equivalent. However, you need some way to pass the information it receives from the client memory space to the server memory space. Even if you pass the pointer to the memory from the client to the server, the server can't access the memory. The client memory doesn't exist for the server; both memory spaces begin with pointers set at 0 and extend only as far as that application requires. Consequently, you use the CopyMemory() function to copy the information from the client memory area to the server memory area, like this:

CopyMemory(
   (void*)PassedMsg, (void*)ThisMsg, sizeof(MSG));

The ProcessMessage() function then posts a THIS_MSG message with the lParam and wParam values set at 0. Generally, you can pass values using lParam and wParam within a hook library, but you can't pass anything else, because the hook library is also part of the client memory space, which is inaccessible to the server.

Developing a Global Server

The managed portion of this example performs precisely the same task as the example in the "Hooking Windows Messages in .NET" article, with the exception that we're using a global hook, not a thread hook. The fact that this example works at a global level means you have to perform additional work. The starting point is starting and stopping the hooking process, as shown in Listing 2.

Listing 2: Starting and Stopping the Hook Library

private void btnStartStop_Click(object sender, EventArgs e)
{
   if (btnStartStop.Text == "&Start")
   {
      // Allocate memory from the global heap.
      hMsg = Marshal.AllocHGlobal(Marshal.SizeOf(ThisMsg));

      // Set the hook.
      if (!SetHook(
         SWH_ID.WH_GETMESSAGE, this.Handle, 0, hMsg))
      {
         // When the hook fails, display a message and free
         // the memory from the global heap. If you don't
         // free this memory, you'll cause a memory leak.
         MessageBox.Show("Didn't Set Hook");
         Marshal.FreeHGlobal(hMsg);
      }
   }
   else
   {
      // Clear the hook when you're doing using it.
      if (!ClearHook(this.Handle))
         MessageBox.Show("Didn't Clear Hook");

      // Whatever happens, always free the global memory.
      Marshal.FreeHGlobal(hMsg);
   }
}

The example defines an IntPtr named hMsg, which points to the memory that the hook library should use to store the message. You can't pass a pointer to managed memory. Notice how the code allocates memory from the global heap using the Marshal.AllocHGlobal() method. The amount of memory matches the size of the MSG structure, which is precisely the same for thread and global hooks. Unfortunately, the MSG structure contains IntPtr entries, so you can't measure it directly using the sizeof() function. You must use the Marshal.SizeOf() method instead, to ensure that the size of the data structure is correct.

The call to SetHook() comes next. This function lets you provide the hook type, a handle to the server, a thread identifier, and a pointer to the message memory. Notice the thread identifier is 0, which signifies that this is a global hook. Only thread hooks have a thread identifier value. If this call fails, you must perform cleanup, or the application creates a memory leak (at a minimum). Consequently, the application calls the Marshal.FreeHGlobal() method to free the memory it previously allocated.

Clearing the hook requires that you provide the handle to the server. As before, you must free any memory that the application allocated by calling the Marshal.FreeHGlobal() method.

At this point, you're ready to process messages. The example defines a method named ProcessMessage() to perform this task. It looks similar and acts almost the same as the thread hook equivalent. Listing 3 shows how this method differs.

Listing 3: Processing the Message

private void ProcessMessage(Object Sender, HookEventArgs e)
{
   // Marshal the MSG structure from unmanaged memory to
   // the managed structure we created.
   ThisMsg = (MSG)Marshal.PtrToStructure(hMsg, typeof(MSG));

   // Test the message for a particular type.
   if ((ThisMsg.message == WM_MOUSEMOVE) ||
       (ThisMsg.message == WM_NCMOUSEMOVE))
   {
      // Get the Window text.
      StringBuilder WindowText = new StringBuilder(256);
      GetWindowText(
         ThisMsg.hwnd, WindowText, WindowText.Capacity);

      // When the system sends the correct message, display the
      // coordinates on screen.
      txtPosition.Text =
         "X: " + ThisMsg.pt.x.ToString() +
         "\r\nY: " + ThisMsg.pt.y.ToString() +
         "\r\nWindow Text: " + WindowText.ToString();
   }
}

Notice that this example still uses the Marshal.PtrToStructure() method to convert the pointer containing the message to a MSG structure. However, notice that this call points to the memory allocated earlier, hMsg, rather than the e.lParam value. Once you make this simple change, you can process the messages as you do for a thread hook.

The Bottom Line

Global hooks provide you with powerful access to all of the applications currently executing in Windows. However, you must use them with extreme care. Setting up a global hook isn't nearly as easy as using a thread hook. The biggest issue you must consider is managing memory correctly. Because of the way that these global hooks work, you'll find that it's actually easier to debug your hook library by building a simple C++ application first, and then move the hook library to your managed application.

Yes, this example points out a place where the .NET Framework fails to provide the required functionality in a big way, and demonstrates that we'll have the unmanaged environment around for a while longer. Make sure you check out the next segment of this series, which shows how to work with Office applications and global hooks.



 
 
>>> More Using Microsoft Visual Studio Articles          >>> More By John Mueller