PRB: RegNotifyChangeKeyValue() Sets Long-Term Association with Key

ID: Q236570


The information in this article applies to:
  • Microsoft Win32 Application Programming Interface (API), included with:
    • Microsoft Windows NT
    • Microsoft Windows 2000
    • Microsoft Windows 98


SYMPTOMS

The RegNotifyChangeKeyValue() API allows an application to receive event notifications for changes within a specified registry key and its subkeys.

On Windows NT and Windows 2000 calling RegNotifyChangeKeyValue() for a particular key handle causes change notifications to continue to occur for as long as the key handle is valid. This causes a second call to RegNotifyChangeKeyValue() to return immediately, if any changes have occurred in the interim period between the first and second calls. If the API is being used asynchronously, the passed event handle will be signaled immediately if any interim changes have occurred.

Windows 98 does not keep track of interim changes. Calling RegNotifyChangeKeyValue only notifies you of changes occurring after the call.


RESOLUTION

You may not care about interim changes. Instead, you want to only receive notifications for future changes that occur after the calls RegNotifyChangeKeyValue().

Either of the following approaches can be used to make sure that notifications are not received for interim changes:

  • Close and reopen the key.


  • This returns a new key handle that is not registered for change notifications.
  • Use RegNotifyChangeKeyValue() asynchronously and wait on the event using WaitForSingleObject() with a zero timeout.


  • This clears a possible interim notification. If no changes have occurred, no harm is done, as the wait immediately returns with WAIT_TIMEOUT. The API can then be called again immediately to catch future changes.

    Multithreaded applications should use the second approach, since it avoids the possibility of closing the registry handle out from under another thread that may be using it.

    For a sample implementation of this second approach, refer to the code at the end of this article.


STATUS

This behavior is by design.


MORE INFORMATION

There are a couple of things to keep in mind when dealing with registry change notifications:

As with the file change notification APIs, RegNotifyChangeKeyValue() does not return any indication as to what has changed in the registry key or its subkeys or values. The program must know the state of the keys or values prior to the notification, so that it can determine what has changed.

There is not a one-to-one relationship between changes and notifications. When a change notification is received, there actually may have been numerous registry changes.

Sample Code

Those who have worked with file change notifications, know that FindFirstChangeNotification() can be used to receive only notifications for future changes in a directory. Interim file changes can then be received using FindNextChangeNotification(). This ensures that notifications are not missed. Unfortunately, there are no built-in analogous functions for registry change notifications. The following sample code demonstrates how to implement these functions using the second approach listed in the "Resolution" section. The following functions are implemented:
  • RegFindFirstChange()
  • RegFindNextChange()
  • RegFindCloseChange()

//**********************************************************************
// 
//  This program demonstrates how to work with registry change
//  notifications. In particular, it shows how to either receive
//  or ignore interim registry changes (for example, those that occur in
//  the period between receiving a notification and the next call
//  to RegNotifyChangeKeyValue().
// 
//  THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
//  ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
//  TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
// 
//  Copyright (C) 1999 Microsoft Corporation. All rights reserved.
// 
//**********************************************************************

#include <windows.h>
#include <conio.h>
#include <stdio.h>
#include <tchar.h>


// defined constants
#define MONITOR_TOPKEY  HKEY_LOCAL_MACHINE
#define MONITOR_SUBKEY  TEXT("Software\\Temp")


// structure used internally by RegFindFirst/NextChange()
typedef struct _REG_CHANGE_DATA {
   HKEY   hKey;
   BOOL   bWatchSubtree;
   DWORD  dwNotifyFilter;
} REG_CHANGE_DATA, *LPREG_CHANGE_DATA;


// function prototypes
HANDLE RegFindFirstChange( HKEY hKey, BOOL bWatchSubtree, 
      DWORD dwNotifyFilter, LPREG_CHANGE_DATA lprcd );

BOOL RegFindNextChange( HANDLE hChange, LPREG_CHANGE_DATA lprcd );

BOOL RegFindCloseChange( HANDLE hChange );

void DisplayError( LPTSTR szAPI, DWORD dwError );


//**********************************************************************
// 
//  _tmain() -- becomes main() for ANSI or wmain() for Unicode
// 
//  PURPOSE :     This is the entry point for the program. This function
//                contains sample code demonstrating how to use the
//                RegFind*() functions implemented below.
// 
//  PARAMETERS:   None
// 
//  RETURN VALUE: None
// 
//**********************************************************************

void _tmain( void ) {

   DWORD  dwFilter;
   LONG   lResult;
   HKEY   hKey;
   char   ch;
   HANDLE hChange    = NULL;
   BOOL   bContinue  = TRUE;
   REG_CHANGE_DATA   rcd;

   // open the key to be monitored
   lResult = RegOpenKeyEx( MONITOR_TOPKEY, MONITOR_SUBKEY, 
         0, KEY_READ, &hKey );

   if ( lResult != ERROR_SUCCESS )
      DisplayError( "RegOpenKeyEx()", lResult );

   __try {

      // filter for all registry changes
      dwFilter = REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_ATTRIBUTES
            | REG_NOTIFY_CHANGE_LAST_SET | REG_NOTIFY_CHANGE_SECURITY;

      while ( bContinue ) {

         _tprintf( TEXT("'f' FindFirst; 'n' FindNext; 'q' Quit --> ") );
      
         ch = _getche();
         _tprintf( TEXT("\n") );

         switch ( ch ) {

            
            case 'f': case 'F':  // FindFirst
            
               // close old handle, if it exists
               if ( hChange )
                  RegFindCloseChange( hChange );

               hChange = 
                     RegFindFirstChange( hKey, TRUE, dwFilter, &rcd );
               
               if ( hChange == NULL )
                  DisplayError( TEXT("RegFindFirstChange()"), 
                        GetLastError() );
               break;


            case 'n': case 'N':  // FindNext
            
               if ( !RegFindNextChange( hChange, &rcd ) )
                  DisplayError( TEXT("RegFindNextChange()"), 
                        GetLastError() );
               break;


            case 'q': case 'Q':  // Quit

               __leave;
               break;

         }

         // wait on the handle, if it exists
         if ( hChange ) {
            _tprintf( TEXT("Waiting...\n") );
            WaitForSingleObject( hChange, INFINITE );
            _tprintf( TEXT("Registry change notification received\n") );
         }

      }

   } __finally {

      // close change handle, if it exists
      if ( hChange )
         RegFindCloseChange( hChange );

      // close key handle
      RegCloseKey( hKey );

   }

}


//**********************************************************************
// 
//  RegFindFirstChange()
// 
//  PURPOSE:      This function begins a registry change notification 
//                cycle. It will never return a handle that is already 
//                signaled due to interim change notifications.
// 
//                After a call to RegFindFirstChange(), a call to
//                RegFindNextChange() ensures that interim 
//                notifications are not missed.
// 
//                Since this routine allocates resources, it is 
//                important that each call is matched up with a 
//                corresponding RegFindCloseChange() call to free the 
//                resources.
// 
//  PARAMETERS:   hKey - handle to the key to watch
// 
//                bWatchSubtree - indicates whether or not to receive
//                notifications for changes in subkeys
// 
//                dwNotifyFilter - identifies which changes should cause
//                notifications to occur
// 
//                lprcd - pointer to a REG_CHANGE_DATA structure used
//                to cycle through the changes
// 
//  RETURN VALUE: If the function succeeds, a wait able handle is 
//                returned. If it fails, NULL is returned and a call to
//                GetLastError() identifies why it failed.
// 
//**********************************************************************

HANDLE RegFindFirstChange( HKEY hKey, BOOL bWatchSubtree, 
      DWORD dwNotifyFilter, LPREG_CHANGE_DATA lprcd ) {

   LONG lResult;
   HANDLE hChange;

   lprcd->hKey = hKey;
   lprcd->bWatchSubtree = bWatchSubtree;
   lprcd->dwNotifyFilter = dwNotifyFilter;

   // create event to be signaled when changes occur
   hChange = CreateEvent( NULL, TRUE, FALSE, NULL );

   // request registry change notifications
   lResult = RegNotifyChangeKeyValue( lprcd->hKey,
         lprcd->bWatchSubtree, lprcd->dwNotifyFilter, 
         hChange, TRUE );

   if ( lResult != ERROR_SUCCESS ) {
      SetLastError( lResult );
      return NULL;
   }

   // It is possible that this key handle has been used to receive
   // registry notifications already. Thus, you will wait with a timeout
   // of zero to clear interim notifications that might have occurred
   if ( WaitForSingleObject( hChange, 0 ) == WAIT_OBJECT_0 ) {

      // There were some interim changes; they are cleared now, but
      // you must call the API again to request future notifications
      lResult = RegNotifyChangeKeyValue( lprcd->hKey, 
            lprcd->bWatchSubtree, lprcd->dwNotifyFilter, 
            hChange, TRUE );

      if ( lResult != ERROR_SUCCESS ) {
         SetLastError( lResult );
         return NULL;
      }
   }

   return hChange;
}


//**********************************************************************
// 
//  RegFindNextChange()
// 
//  PURPOSE:      This function queues the next registry change 
//                notification in a cycle. It must be preceded by a 
//                call to RegFindFirstChange().
// 
//                After calling this function, the handle returned by 
//                RegFindFirstChange() can be waited on again to get 
//                the next change notification. Using these functions 
//                in this manner, all interim change notifications will
//                be caught.
// 
//  PARAMETERS:   lprcd - pointer to the same REG_CHANGE_DATA structure
//                that was initialized with RegFindFirstChange()
// 
//  RETURN VALUE: If the function succeeds, it returns TRUE. If it 
//                fails, it returns FALSE and a call to GetLastError() 
//                identifies why it failed.
// 
//**********************************************************************

BOOL RegFindNextChange( HANDLE hChange, LPREG_CHANGE_DATA lprcd ) {
   
   LONG lResult;

   // reset the event so the handle can be waited on again
   if ( !ResetEvent( hChange ) )
      return FALSE;
   
   // If you call this function, you want to catch interim changes, 
   // so simply call the API again.
   lResult = RegNotifyChangeKeyValue( lprcd->hKey,
         lprcd->bWatchSubtree, lprcd->dwNotifyFilter, 
         hChange, TRUE );

   if ( lResult != ERROR_SUCCESS ) {
      SetLastError( lResult );
      return FALSE;
   }

   return TRUE;
}


//**********************************************************************
// 
//  RegFindCloseChange()
// 
//  PURPOSE:      This function frees the resources allocated by a call
//                to RegFindFirstChange().
// 
//  PARAMETERS:   hChange - the waitable handle returned from 
//                RegFindFirstChange()
// 
//  RETURN VALUE: Always TRUE
// 
//**********************************************************************

BOOL RegFindCloseChange( HANDLE hChange ) {

   // free event
   if ( hChange ) {
      CloseHandle( hChange );
      hChange = NULL;
   }

   return TRUE;
}


//**********************************************************************
// 
//  DisplayError()
// 
//  PURPOSE :     This is a helper function to display an error message 
//                and exit the process if a function in _tmain() fails.
// 
//  PARAMETERS:   szAPI - the name of the function that failed
// 
//                dwError - the Win32 error code indicating why the
//                function failed
// 
//  RETURN VALUE: None
// 
//**********************************************************************

void DisplayError( LPTSTR szAPI, DWORD dwError ) {

   LPTSTR lpBuffer = NULL;

   FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER |
         FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError,
         MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
         (LPTSTR) &lpBuffer, 0, NULL );

   _tprintf( TEXT("%s failed:\n"), szAPI );
   _tprintf( TEXT("    error code = %u\n"), dwError );
   _tprintf( TEXT("    message    = %s\n"), lpBuffer );

   LocalFree( lpBuffer );
   
   ExitProcess(0);
} 

Additional query words:

Keywords : kbAPI kbKernBase kbNTOS kbWinOS2000 kbRegistry kbSDKPlatform kbSDKWin32 kbWinOS98 kbDSupport kbGrpKernBase
Version : winnt:
Platform : winnt
Issue type : kbprb


Last Reviewed: December 3, 1999
© 2000 Microsoft Corporation. All rights reserved. Terms of Use.