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.
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.
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.
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.
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 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.
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;
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 := FMonitorEvent; vEvents := 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.
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:
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 ( RegistryMonitor.zip 221.18KB 766 downloads) of the registry monitor component and demo project. Feel free to use it for anything.