Jump to content


Check out our Community Blogs

Register and join over 40,000 other developers!


Recent Status Updates

View All Updates

Photo
- - - - -

Monitoring Registry Changes

delphi

  • Please log in to reply
No replies to this topic

#1 Luthfi

Luthfi

    CC Leader

  • Expert Member
  • PipPipPipPipPipPipPip
  • 1320 posts
  • Programming Language:PHP, Delphi/Object Pascal, Pascal, Transact-SQL
  • Learning:C, Java, PHP

Posted 21 November 2011 - 02:17 AM

Overview

In Windows operating systems, registry plays very important roles. Not only to store information or configuration of the system, it also reflect the current state of Windows. Basically we can consider that any changes happened in Windows can be examined through registry. The latest example I found in one of my projects is that the only reliable way to detect establishment of new internet connection is by detecting changes in a special registry key.

In order to detect changes in registry we have two choices
  • Polling approach, by continuously querying the registry after specific interval time elapsed.
  • Interrupt approach, by accepting and processing notification received only after the registry was really changed.

Polling approach is really a pain. Not only it will unnecessarily consume a lot of cpu times (which also slowing your program), it will need a lot more codes to write compared to interrupt approach. In polling we need codes to store the latest registry data and codes to compare the data. With interrupt approach, not only we won't use extra cpu time unless registry changes really happened, but we don't need to write codes for detecting changes. Means interrupt approach will be easier to maintain.

In this tutorial I will show you how to monitor registry using interrupt approach.

RegNotifyChangeKeyValue
Windows already provided an api that allows us to receive notification whenever values or keys under a monitored key get modified. The api is RegNotifyChangeKeyValue. See its official description here.

Based on information provided in that page, we know that there are two mode in monitoring registry using RegNotifyChangeKeyValue. The modes are:

  • Asynchronous. In this mode the call to RegNotifyChangeKeyValue will return immediately and changes on the monitored registry key will be notified by signalling a windows event (see CreateEvent on msdn). Therefore prior to call RegNotifyChangeKeyValue we have to create windows event and pass it to RegNotifyChangeKeyValue.
  • Synchronous. In this mode the call to RegNotifyChangeKeyValue will not return until a change in the monitored registry key has occured.

Our main target is to create a class or component (if you want the monitoring class available in design time) that is easily reusable. Therefore we want that our registry monitor class supports all possible situation. In this case we will choose the asynchronous path, since we want to anticipate the possibility to deactivate monitoring process before any changes occured.

To avoid our event waiting from blocking our program execution, we need to place the "waiting code" in separate thread. The thread will be created and executed when we activate the registry monitoring and will be destroyed when we deactivate the monitoring.

Main Class
The structure of our registry monitor main class will be like something like this:

  TRegMon=class(TComponent)
  private
    FMonitoredKey: string;        // the monitored registry key
    FOnChange    : TNotifyEvent;  // event that will be fired when a change
                                  // in monitored registry is detected
    FRootKey     : TRootKey;      // the root key of the monitored registry
    FMonitor     : TThread;
    FWatchSubKeys: Boolean;       // do we want to monitored the subkeys
                                  // under the monitored key?
    FOnActivate: TNotifyEvent;    // event to be fired when monitoring activates
    FOnDeactivate: TNotifyEvent;  // event to be fired when monitoring deactivates
    procedure SetActive(const Value: Boolean);
    procedure SetMonitoredKey(const Value: string);
    procedure SetRootKey(const Value: TRootKey);
    function GetActive: Boolean;
    procedure SetWatchSubKeys(const Value: Boolean);
  protected
    procedure DoRegChanged;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    procedure Activate;
    procedure Deactivate;
  published
    property RootKey: TRootKey read FRootKey write SetRootKey default rkCurrentUser;
    property MonitoredKey: string read FMonitoredKey write SetMonitoredKey;
    property WatchSubKeys: Boolean read FWatchSubKeys write SetWatchSubKeys default True;
    property Active: Boolean read GetActive write SetActive default false;

    // event that will be fired when monitored registry key is changed
    property OnChange: TNotifyEvent read FOnChange write FOnChange;
    // event that will be fired when monitoring activated
    property OnActivate: TNotifyEvent read FOnActivate write FOnActivate;
    // event that will be fired when monitoring deactivated
    property OnDeactivate: TNotifyEvent read FOnDeactivate write FOnDeactivate;
  end;

From this class the interesting parts are in methods Activate and Deactivate.

Activate
procedure TRegMon.Activate;
begin
  if Active then Exit;
  FMonitor := TMonitorThread.Create(Self);
  if Assigned(FOnActivate) then
    FOnActivate(Self);
end;

This method first see if the monitoring is already activated. If so, it just silently skip the rest of the code. Otherwise it will spawn the monitoring thread and then fire the OnActivate event.

Deactivate
procedure TRegMon.Deactivate;
var
  vThread: TMonitorThread;
begin
  if not Active then Exit;
  // we have to specifically typecasting to TMonitorThread since we want to call
  // the reintroduced Terminate instead of the original Terminate of TThread
  vThread := TMonitorThread(FMonitor);
  FMonitor := nil;
  vThread.Terminate;  // call the reintroduced Terminate
  Sleep(0);           // to immediately yield cpu to other thread/process.
                      // We aim for our monitoring thread to "sense" termination
                      // and clean up accordingly.
  if Assigned(FOnDeactivate) then
    FOnDeactivate(Self);
end;

Monitoring Thread
Monitoring thread is a class that able to do the waiting in separate thread. This to avoid the waiting from blocking the main thread. If the waiting is done in main thread, all other process will have to wait until the waiting is over, whether by registry change notification or time out, making our program looks like hung from time to time. Delphi already provided a TThread class which able to run codes in separate thread. So we will base our monitoring thread from TThread class.

And our monitoring thread goes something like this:
  TMonitorThread=class(TThread)
  private
    FReg: TRegistry;
    FOwner: TRegMon;
    FFilter: DWord;
    FTerminateEvent: THandle;
    FMonitorEvent  : THandle;
    procedure Setup;    // here we initiated our object and vars prior to start
                        // executing the thread
    procedure TearDown; // here we finalize our object and when the thread is
                        // terminated
  protected
    // main code of the thread
    procedure Execute; override;
  public
    constructor Create(AOwner: TRegMon); reintroduce;
    // we introduce new Terminate method, since we want to do something after
    // calling the original Terminate 
    procedure Terminate; reintroduce;
  end;

In the monitoring thread, the interesting codes are in Setup and Execute methods.

Setup
Here we are preparing for registry key monitoring. We initialize the filter of what kind of change we are interested in, constructing the events, and register our monitoring event so it will be fired when a change in our monitored registry key is detected. Setup will be called as soon as the thread starts, i.e. soon after entering Execute method.

The code for Setup:
procedure TMonitorThread.Setup;
begin
  FFilter := REG_NOTIFY_CHANGE_NAME or REG_NOTIFY_CHANGE_LAST_SET;
  FMonitorEvent   := CreateEvent(nil, True, False, nil);
  FTerminateEvent := CreateEvent(nil, True, False, nil);
  if RegNotifyChangeKeyValue(FReg.CurrentKey,
       FOwner.FWatchSubKeys, FFilter, FMonitorEvent, True) <> ERROR_SUCCESS then
    raise Exception.Create('Can not start monitoring');
end;

Execute
This method actually is the body of the thread. The thread is running by executing this method. Exiting this method means terminating the thread. The code for our Execute method is:
procedure TMonitorThread.Execute;
var
  vEvents: array[1..2] of THandle;
begin
  Setup;
  try
    vEvents[1] := FMonitorEvent;
    vEvents[2] := FTerminateEvent;
    while not Terminated do
    begin
      if WaitForMultipleObjects(2, @vEvents, False, INFINITE)=WAIT_OBJECT_0 then
      begin
        Synchronize(FOwner.DoRegChanged);
        ResetEvent(FMonitorEvent);
        if RegNotifyChangeKeyValue(FReg.CurrentKey,
             FOwner.FWatchSubKeys, FFilter, FMonitorEvent, True) <> ERROR_SUCCESS then Exit;
      end;
    end;
  finally
    TearDown;
  end;
end;

Basically in the Execute method we only wait for two events, registry change event or thread got terminated event. When the first one is occurred, it will call the main class' DoRegChange (which in turn fire the OnChange event) and then back to waiting for events (after resetting back some values). If the latter one is occurred, it simply exit the method (after does the tearing down in TearDown method) which consequently terminate the thread.

Demo Project
In our demo project we will test the monitoring component. Note that in the demo project, the registry monitor component was created in design time. Therefore you need to install the component before you can cleanly open the main form in design time. The design package for the component is included in the source code.

In the demo project we create a test key, we named it CodeCall, activate and deactivate registry monitoring, and see if any change to the test key resulting in event/notification. Upon running and testing the demo project, you would get something like this:

RegistryMonitor_Main_00.png

Note that the first 3 registry change events were fired by modification of the test key (by clicking Change content of Test Key button). But the last one was caused by removal of the test key (by clicking Delete Test Key button). So you can see that the monitor does not only watch for modifications, but also for deletion.

Here is the source code (Attached File  RegistryMonitor.zip   221.18KB   766 downloads) of the registry monitor component and demo project. Feel free to use it for anything.
  • 0





Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download