Modifying Popup Windows, Part 2

Detect the creation of third-party popup windows by looking at the window caption


December 09, 2002
URL:http://drdobbs.com/modifying-popup-windows-part-2/184416791

Modifying Popup Windows, Part 2

I need to know when a particular popup window appears from a third-party application so that I can modify its appearance. How can I do this?

In the last newsletter, we explored how to know when a window appears based on its window class. We explored the use of a window hook, installing it, removing it, and testing values provided by Windows during an event trap.

This issue, we’ll continue looking at hooking by examining how we might do this by looking at the window caption instead of the window class.

Currently, unique window classes are becoming much scarcer, and I haven’t run into too many cases where I’ve been able to identify third-party windows by their classes. Most often, I fire up Spy++, click on the window I want to look at, access the window properties and see the class name as “#32770”. Bummer. This is the class name used by an MFC window. MFC creates window classes for various default window styles as a convenience to the developer. One of the advantages of using a framework is just such assistance with hiding the grunge of Windows. But it makes it impossible to distinguish one MFC window from another on the basis of the window class.

So what to do? Another technique is to use the window caption. The window caption for a window can be easily obtained by calling GetWindowText() with the proper HWND. Again, the trick is to know when these windows come into existence so we can determine if the particular window is the one that we want.

Last time we explored use of SetWindowsHookEx() with the WH_CBT style to trap on window creation events. At first glance, this seems like it might be the way to go. We get the CREATESTRUCT values passed to a call to CreateWindow() just after the window was created. However, there is a drawback. There is no way to guarantee that the lpszName attribute of the structure will be set to the correct window caption. The application may override the caption by a later call to SetWindowText() during it’s initialization of the window. We’d like to trap on an event that is sent just before the window appears rather than just after it’s created to ensure we have the greatest chance of getting the right caption.

Fortunately, we can use another of SetWindowsHookEx’s styles called WH_CALLWNDPROC. This style allows us to handle messages just prior to the time they are dispatched to the window they are intended for. In our case, the message we want to look at is WM_ACTIVATE. This message is sent to a window as it is being activated and deactivated. At this point in the creation cycle we can be pretty sure that the window is ready for display and the underlying application has finished initializing it.

Let’s look at some code. First, we create the boilerplate methods we need to install the hook (for more info on why this needs to be in a DLL, see the prior issue of the newsletter):

static HINSTANCE g_hModule = NULL;


BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
                )
{
   switch (ul_reason_for_call)
   {
   case DLL_PROCESS_ATTACH:
      g_hModule = (HINSTANCE)hModule;
      break;
   case DLL_THREAD_ATTACH:
   case DLL_THREAD_DETACH:
   case DLL_PROCESS_DETACH:
      break;
   }
    return TRUE;
}


static HHOOK g_hHook = NULL;

__declspec(dllexport) void InitHook()
{
   g_hHook = SetWindowsHookEx( WH_CALLWNDPROC,(HOOKPROC)OnHookProc,g_hModule,0 );

   if ( g_hHook )
      MessageBox( NULL,"hook created","",MB_OK );
   else
      MessageBox( NULL,"hook was not created","",MB_OK );
}

__declspec(dllexport) void TermHook()
{
   if ( g_hHook )
      UnhookWindowsHookEx( g_hHook );
   g_hHook = NULL;
}

Pretty straightforward, especially if you read through the last issue of the newsletter. We install the hook in InitHook() and tear it down in TermHook(). Again, these messages would be called in your application's initialization and termination code, respectively.

The next step is to create the hook function itself called OnHookProc():

static LRESULT OnHookProc(int nCode,WPARAM w,LPARAM l)
{
   LRESULT lr = CallNextHookEx(g_hHook,nCode,w,l);

   if ( nCode == HC_ACTION )
   {
      CWPSTRUCT* p = (CWPSTRUCT*)l;
      
      if ( p && (p->message == WM_ACTIVATE && p->wParam==WA_ACTIVE) )
      {
          char caption[1024];
          ZeroMemory(caption,sizeof(caption));
          SendMessage( p->hwnd,WM_GETTEXT,sizeof(caption)-1,(LPARAM)caption );


         if ( !strcmpi(caption,"MyCoolWindowCaption") )
         {
      ::MessageBox( NULL,found it,,MB_OK );
         }
      }

   }

   return lr;
}

Notice that we call the function CallNextHookEx() here before we process the message. In our case, we don't care about changing the way the message is processed, we just want to know that it is being processed. So we give the default window proc a chance to do its thing before we do ours. This also ensures that if the underlying app messes with the caption in WM_ACTIVATE, we get the mod rather than the original value.

The code is fairly straightforward. We are looking for a window that becomes active by checking the message type and whether it is going active or inactive. Then, it's a simple call to SendMessage() with WM_GETTEXT to get the window caption for the window, and then finally our comparison test.

Some other words of advice about using hooks:

  1. Be sure you minimize the amount of code in your hook and do not use third-party frameworks such as MFC to save time writing the DLL. These items would have to be loaded into the process space of each application on the user’s desktop and could eat up unnecessary amounts of memory.
  2. Realize that once you terminate the hook, the DLL doesn't get dropped out of memory until the last application using it is terminated. This means that updating your hook DLL can be problematic if it's hooking widely used messages such as those we trapped above. You may not be able to update the DLL without a system reboot.
  3. Test, test, test, and then test again. A hook DLL is almost as tricky as working on a device driver. I've seen spectacular system crashes and lockups with hook DLL's that were improperly written even with newer operating systems like Windows 2000. Don't assume that just because it's a 32-bit OS, you can get away with minor failures. They could end up crashing or destablizing your user's system.
  4. If you're relying on a method like window captioning to determine identity, check to be sure that the text you are relying on is reliable and doesn't change from version to version of the underlying application. This could trip up a hook that needs to support multiple versions of applications.
  5. Another approach that I didn't explore but that may also be useful in some cases is to isolate the window by checking to see if it belongs to a particular EXE. To do this, you would call GetCurrentProcess() within the hook DLL (which will return the process handle of the application the hook DLL is running within) and then GetModuleFilenameEx() to get the filename of the process. You could then do a comparison to see if it's the one you want.

Feel free to send me questions you may have that might be of interest to other developers and we'll explore them together in a future issue of the Windows Q&A newsletter.


Mark M. Baker is the Chief of Research & Development at BNA Software located in Washington, D.C. Send your Windows development questions to [email protected].

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.