🔝 Retour au Sommaire
Surveiller les modifications du Registre Windows permet à votre application de réagir en temps réel aux changements de configuration. C'est particulièrement utile pour :
- Détecter les changements de paramètres effectués par d'autres applications
- Synchroniser plusieurs instances de votre application
- Réagir aux modifications système (nouveau matériel, changement de configuration)
- Surveiller la sécurité (détection de modifications non autorisées)
- Déboguer et comprendre ce qui modifie vos clés
Windows fournit des mécanismes pour être notifié quand une clé du Registre change :
Votre Application
↓
[Demande de surveillance sur HKEY_CURRENT_USER\Software\MonApp]
↓
Windows Registry Monitor
↓
[Attend les modifications...]
↓
Une autre application modifie la clé
↓
Windows envoie une notification
↓
Votre application est informée
↓
[Réaction au changement]
La fonction principale pour surveiller le Registre est RegNotifyChangeKeyValue :
function RegNotifyChangeKeyValue(
hKey: HKEY; // Handle de la clé à surveiller
bWatchSubtree: BOOL; // True = surveiller les sous-clés aussi
dwNotifyFilter: DWORD; // Ce qu'il faut surveiller
hEvent: THandle; // Event à signaler (optionnel)
fAsynchronous: BOOL // True = non bloquant
): Longint;Vous pouvez surveiller différents types de changements :
const
REG_NOTIFY_CHANGE_NAME = $00000001; // Ajout/suppression de sous-clés
REG_NOTIFY_CHANGE_ATTRIBUTES = $00000002; // Changement d'attributs
REG_NOTIFY_CHANGE_LAST_SET = $00000004; // Changement de valeur
REG_NOTIFY_CHANGE_SECURITY = $00000008; // Changement de sécurité
// Combinaison courante : surveiller tout
REG_NOTIFY_ALL = REG_NOTIFY_CHANGE_NAME or
REG_NOTIFY_CHANGE_ATTRIBUTES or
REG_NOTIFY_CHANGE_LAST_SET or
REG_NOTIFY_CHANGE_SECURITY;program SurveillanceSimple;
{$mode objfpc}{$H+}
uses
Windows, Registry, SysUtils;
procedure SurveillerCleBloquant;
var
Reg: TRegistry;
KeyHandle: HKEY;
Result: Longint;
begin
Reg := TRegistry.Create(KEY_NOTIFY);
try
Reg.RootKey := HKEY_CURRENT_USER;
if Reg.OpenKeyReadOnly('Software\MonApp\Settings') then
begin
KeyHandle := Reg.CurrentKey;
WriteLn('Surveillance de la clé démarrée...');
WriteLn('Modifiez la clé dans regedit pour tester');
WriteLn('Appuyez sur Ctrl+C pour arrêter');
WriteLn('');
repeat
// Attendre un changement (BLOQUANT)
Result := RegNotifyChangeKeyValue(
KeyHandle, // Clé à surveiller
True, // Surveiller les sous-clés
REG_NOTIFY_CHANGE_LAST_SET, // Surveiller les valeurs
0, // Pas d'event
False // Mode synchrone (bloquant)
);
if Result = ERROR_SUCCESS then
begin
WriteLn(FormatDateTime('hh:nn:ss', Now) +
' - Changement détecté !');
// Ici, vous pourriez relire les valeurs pour voir ce qui a changé
ReadAndDisplayChanges(Reg);
end
else
begin
WriteLn('Erreur de surveillance : ' + SysErrorMessage(Result));
Break;
end;
until False;
Reg.CloseKey;
end
else
WriteLn('Impossible d''ouvrir la clé');
finally
Reg.Free;
end;
end;
begin
SurveillerCleBloquant;
end.Pour ne pas bloquer votre application, utilisez un Event :
unit RegistryMonitor;
{$mode objfpc}{$H+}
interface
uses
Windows, Classes, SysUtils, Registry;
type
TRegistryChangeEvent = procedure(Sender: TObject) of object;
{ TRegistryMonitor }
TRegistryMonitor = class(TThread)
private
FRootKey: HKEY;
FKeyPath: string;
FWatchSubTree: Boolean;
FNotifyFilter: DWORD;
FOnChange: TRegistryChangeEvent;
FEventHandle: THandle;
FKeyHandle: HKEY;
FActive: Boolean;
procedure DoNotifyChange;
protected
procedure Execute; override;
public
constructor Create(ARootKey: HKEY; const AKeyPath: string);
destructor Destroy; override;
procedure Start;
procedure Stop;
property WatchSubTree: Boolean read FWatchSubTree write FWatchSubTree;
property NotifyFilter: DWORD read FNotifyFilter write FNotifyFilter;
property OnChange: TRegistryChangeEvent read FOnChange write FOnChange;
property Active: Boolean read FActive;
end;
implementation
{ TRegistryMonitor }
constructor TRegistryMonitor.Create(ARootKey: HKEY; const AKeyPath: string);
begin
inherited Create(True); // Créé suspendu
FRootKey := ARootKey;
FKeyPath := AKeyPath;
FWatchSubTree := True;
FNotifyFilter := REG_NOTIFY_CHANGE_LAST_SET or REG_NOTIFY_CHANGE_NAME;
FActive := False;
// Créer un event pour la notification
FEventHandle := CreateEvent(nil, False, False, nil);
FreeOnTerminate := False;
end;
destructor TRegistryMonitor.Destroy;
begin
Stop;
if FEventHandle <> 0 then
CloseHandle(FEventHandle);
inherited;
end;
procedure TRegistryMonitor.Start;
var
Reg: TRegistry;
begin
if FActive then Exit;
// Ouvrir la clé
Reg := TRegistry.Create(KEY_NOTIFY);
try
Reg.RootKey := FRootKey;
if Reg.OpenKeyReadOnly(FKeyPath) then
begin
FKeyHandle := Reg.CurrentKey;
FActive := True;
// Démarrer le thread
inherited Start;
end
else
raise Exception.CreateFmt('Impossible d''ouvrir la clé : %s', [FKeyPath]);
finally
Reg.Free;
end;
end;
procedure TRegistryMonitor.Stop;
begin
if not FActive then Exit;
FActive := False;
// Signaler l'event pour débloquer le thread
SetEvent(FEventHandle);
// Attendre la fin du thread
if not Finished then
begin
Terminate;
WaitFor;
end;
// Fermer la clé
if FKeyHandle <> 0 then
begin
RegCloseKey(FKeyHandle);
FKeyHandle := 0;
end;
end;
procedure TRegistryMonitor.Execute;
var
Result: Longint;
begin
while not Terminated and FActive do
begin
// Configurer la surveillance
Result := RegNotifyChangeKeyValue(
FKeyHandle,
FWatchSubTree,
FNotifyFilter,
FEventHandle,
True // Asynchrone
);
if Result <> ERROR_SUCCESS then
begin
FActive := False;
raise Exception.Create('Erreur RegNotifyChangeKeyValue : ' +
SysErrorMessage(Result));
end;
// Attendre l'event
if WaitForSingleObject(FEventHandle, INFINITE) = WAIT_OBJECT_0 then
begin
if not Terminated and FActive then
begin
// Notification reçue
Synchronize(@DoNotifyChange);
end;
end;
end;
end;
procedure TRegistryMonitor.DoNotifyChange;
begin
if Assigned(FOnChange) then
FOnChange(Self);
end;
end.unit MainForm;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Forms, Controls, StdCtrls, Windows, Registry,
RegistryMonitor;
type
{ TForm1 }
TForm1 = class(TForm)
Memo1: TMemo;
ButtonStart: TButton;
ButtonStop: TButton;
Label1: TLabel;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure ButtonStartClick(Sender: TObject);
procedure ButtonStopClick(Sender: TObject);
private
FMonitor: TRegistryMonitor;
procedure OnRegistryChange(Sender: TObject);
procedure RefreshValues;
public
end;
var
Form1: TForm1;
implementation
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
// Créer le moniteur
FMonitor := TRegistryMonitor.Create(
HKEY_CURRENT_USER,
'Software\MonApp\Settings'
);
FMonitor.OnChange := @OnRegistryChange;
ButtonStop.Enabled := False;
// Afficher les valeurs initiales
RefreshValues;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FMonitor.Free;
end;
procedure TForm1.ButtonStartClick(Sender: TObject);
begin
try
FMonitor.Start;
ButtonStart.Enabled := False;
ButtonStop.Enabled := True;
Label1.Caption := 'Surveillance active';
Label1.Font.Color := clGreen;
Memo1.Lines.Add(FormatDateTime('hh:nn:ss', Now) +
' - Surveillance démarrée');
except
on E: Exception do
begin
ShowMessage('Erreur : ' + E.Message);
Label1.Caption := 'Erreur';
Label1.Font.Color := clRed;
end;
end;
end;
procedure TForm1.ButtonStopClick(Sender: TObject);
begin
FMonitor.Stop;
ButtonStart.Enabled := True;
ButtonStop.Enabled := False;
Label1.Caption := 'Surveillance arrêtée';
Label1.Font.Color := clDefault;
Memo1.Lines.Add(FormatDateTime('hh:nn:ss', Now) +
' - Surveillance arrêtée');
end;
procedure TForm1.OnRegistryChange(Sender: TObject);
begin
// Cette méthode est appelée quand le registre change
Memo1.Lines.Add(FormatDateTime('hh:nn:ss', Now) +
' - Changement détecté !');
// Rafraîchir l'affichage des valeurs
RefreshValues;
end;
procedure TForm1.RefreshValues;
var
Reg: TRegistry;
Values: TStringList;
i: Integer;
begin
Memo1.Lines.Add('--- Valeurs actuelles ---');
Reg := TRegistry.Create(KEY_READ);
Values := TStringList.Create;
try
Reg.RootKey := HKEY_CURRENT_USER;
if Reg.OpenKeyReadOnly('Software\MonApp\Settings') then
begin
try
Reg.GetValueNames(Values);
for i := 0 to Values.Count - 1 do
begin
if Reg.GetDataType(Values[i]) = rdString then
Memo1.Lines.Add(Format(' %s = %s',
[Values[i], Reg.ReadString(Values[i])]));
end;
finally
Reg.CloseKey;
end;
end;
finally
Values.Free;
Reg.Free;
end;
Memo1.Lines.Add('-------------------------');
end;
end.unit MultiRegistryMonitor;
{$mode objfpc}{$H+}
interface
uses
Windows, Classes, SysUtils, Registry, Generics.Collections;
type
TRegistryChangeInfo = record
RootKey: HKEY;
KeyPath: string;
ChangeType: DWORD;
Timestamp: TDateTime;
end;
TMultiRegistryChangeEvent = procedure(Sender: TObject;
const ChangeInfo: TRegistryChangeInfo) of object;
{ TMonitoredKey }
TMonitoredKey = class
public
RootKey: HKEY;
KeyPath: string;
Handle: HKEY;
EventHandle: THandle;
WatchSubTree: Boolean;
NotifyFilter: DWORD;
constructor Create(ARootKey: HKEY; const AKeyPath: string);
destructor Destroy; override;
end;
{ TMultiRegistryMonitor }
TMultiRegistryMonitor = class(TThread)
private
FKeys: TObjectList<TMonitoredKey>;
FOnChange: TMultiRegistryChangeEvent;
FActive: Boolean;
FLastChangeInfo: TRegistryChangeInfo;
procedure DoNotifyChange;
procedure SetupNotification(Key: TMonitoredKey);
protected
procedure Execute; override;
public
constructor Create;
destructor Destroy; override;
procedure AddKey(ARootKey: HKEY; const AKeyPath: string;
AWatchSubTree: Boolean = True);
procedure RemoveKey(const AKeyPath: string);
procedure Start;
procedure Stop;
property OnChange: TMultiRegistryChangeEvent read FOnChange write FOnChange;
property Active: Boolean read FActive;
end;
implementation
{ TMonitoredKey }
constructor TMonitoredKey.Create(ARootKey: HKEY; const AKeyPath: string);
begin
RootKey := ARootKey;
KeyPath := AKeyPath;
Handle := 0;
EventHandle := CreateEvent(nil, False, False, nil);
WatchSubTree := True;
NotifyFilter := REG_NOTIFY_CHANGE_LAST_SET or REG_NOTIFY_CHANGE_NAME;
end;
destructor TMonitoredKey.Destroy;
begin
if Handle <> 0 then
RegCloseKey(Handle);
if EventHandle <> 0 then
CloseHandle(EventHandle);
inherited;
end;
{ TMultiRegistryMonitor }
constructor TMultiRegistryMonitor.Create;
begin
inherited Create(True);
FKeys := TObjectList<TMonitoredKey>.Create(True); // True = owns objects
FActive := False;
FreeOnTerminate := False;
end;
destructor TMultiRegistryMonitor.Destroy;
begin
Stop;
FKeys.Free;
inherited;
end;
procedure TMultiRegistryMonitor.AddKey(ARootKey: HKEY; const AKeyPath: string;
AWatchSubTree: Boolean);
var
Key: TMonitoredKey;
Reg: TRegistry;
begin
// Vérifier si la clé n'est pas déjà surveillée
for Key in FKeys do
begin
if (Key.RootKey = ARootKey) and (Key.KeyPath = AKeyPath) then
begin
raise Exception.Create('Cette clé est déjà surveillée');
end;
end;
// Créer la nouvelle clé surveillée
Key := TMonitoredKey.Create(ARootKey, AKeyPath);
Key.WatchSubTree := AWatchSubTree;
// Ouvrir la clé du registre
Reg := TRegistry.Create(KEY_NOTIFY);
try
Reg.RootKey := ARootKey;
if Reg.OpenKeyReadOnly(AKeyPath) then
begin
Key.Handle := Reg.CurrentKey;
FKeys.Add(Key);
// Si la surveillance est active, configurer la notification
if FActive then
SetupNotification(Key);
end
else
begin
Key.Free;
raise Exception.CreateFmt('Impossible d''ouvrir la clé : %s', [AKeyPath]);
end;
finally
Reg.Free;
end;
end;
procedure TMultiRegistryMonitor.RemoveKey(const AKeyPath: string);
var
i: Integer;
begin
for i := FKeys.Count - 1 downto 0 do
begin
if FKeys[i].KeyPath = AKeyPath then
begin
FKeys.Delete(i);
Break;
end;
end;
end;
procedure TMultiRegistryMonitor.Start;
var
Key: TMonitoredKey;
begin
if FActive then Exit;
if FKeys.Count = 0 then
raise Exception.Create('Aucune clé à surveiller');
// Configurer la surveillance pour toutes les clés
for Key in FKeys do
SetupNotification(Key);
FActive := True;
inherited Start;
end;
procedure TMultiRegistryMonitor.Stop;
var
Key: TMonitoredKey;
begin
if not FActive then Exit;
FActive := False;
// Signaler tous les events pour débloquer le thread
for Key in FKeys do
SetEvent(Key.EventHandle);
// Attendre la fin du thread
if not Finished then
begin
Terminate;
WaitFor;
end;
end;
procedure TMultiRegistryMonitor.SetupNotification(Key: TMonitoredKey);
var
Result: Longint;
begin
Result := RegNotifyChangeKeyValue(
Key.Handle,
Key.WatchSubTree,
Key.NotifyFilter,
Key.EventHandle,
True
);
if Result <> ERROR_SUCCESS then
raise Exception.CreateFmt('Erreur surveillance pour %s : %s',
[Key.KeyPath, SysErrorMessage(Result)]);
end;
procedure TMultiRegistryMonitor.Execute;
var
Handles: array of THandle;
i, WaitResult: Integer;
Key: TMonitoredKey;
begin
// Créer le tableau des handles d'events
SetLength(Handles, FKeys.Count);
for i := 0 to FKeys.Count - 1 do
Handles[i] := FKeys[i].EventHandle;
while not Terminated and FActive do
begin
// Attendre qu'un des events soit signalé
WaitResult := WaitForMultipleObjects(
Length(Handles),
@Handles[0],
False, // Attendre UN event, pas tous
INFINITE
);
if (WaitResult >= WAIT_OBJECT_0) and
(WaitResult < WAIT_OBJECT_0 + DWORD(Length(Handles))) then
begin
// Identifier quelle clé a changé
i := WaitResult - WAIT_OBJECT_0;
Key := FKeys[i];
// Préparer les informations de changement
FLastChangeInfo.RootKey := Key.RootKey;
FLastChangeInfo.KeyPath := Key.KeyPath;
FLastChangeInfo.ChangeType := Key.NotifyFilter;
FLastChangeInfo.Timestamp := Now;
// Notifier le changement
if not Terminated and FActive then
begin
Synchronize(@DoNotifyChange);
// Reconfigurer la surveillance pour cette clé
SetupNotification(Key);
end;
end;
end;
end;
procedure TMultiRegistryMonitor.DoNotifyChange;
begin
if Assigned(FOnChange) then
FOnChange(Self, FLastChangeInfo);
end;
end.procedure TForm1.FormCreate(Sender: TObject);
begin
FMultiMonitor := TMultiRegistryMonitor.Create;
FMultiMonitor.OnChange := @OnMultiRegistryChange;
// Surveiller plusieurs clés
FMultiMonitor.AddKey(HKEY_CURRENT_USER,
'Software\MonApp\Settings', True);
FMultiMonitor.AddKey(HKEY_CURRENT_USER,
'Software\MonApp\RecentFiles', True);
FMultiMonitor.AddKey(HKEY_CURRENT_USER,
'Software\Microsoft\Windows\CurrentVersion\Run', False);
FMultiMonitor.Start;
end;
procedure TForm1.OnMultiRegistryChange(Sender: TObject;
const ChangeInfo: TRegistryChangeInfo);
var
RootKeyName: string;
begin
// Identifier la ruche
case ChangeInfo.RootKey of
HKEY_CLASSES_ROOT: RootKeyName := 'HKCR';
HKEY_CURRENT_USER: RootKeyName := 'HKCU';
HKEY_LOCAL_MACHINE: RootKeyName := 'HKLM';
HKEY_USERS: RootKeyName := 'HKU';
else
RootKeyName := 'Unknown';
end;
// Logger le changement
Memo1.Lines.Add(Format('[%s] Changement dans %s\%s',
[FormatDateTime('hh:nn:ss', ChangeInfo.Timestamp),
RootKeyName,
ChangeInfo.KeyPath]));
// Réagir selon la clé modifiée
if Pos('Settings', ChangeInfo.KeyPath) > 0 then
begin
Memo1.Lines.Add(' → Rechargement des paramètres...');
ReloadSettings;
end
else if Pos('RecentFiles', ChangeInfo.KeyPath) > 0 then
begin
Memo1.Lines.Add(' → Mise à jour de la liste des fichiers récents...');
UpdateRecentFilesList;
end
else if Pos('Run', ChangeInfo.KeyPath) > 0 then
begin
Memo1.Lines.Add(' → Programme ajouté/retiré du démarrage automatique');
end;
end;Pour savoir exactement ce qui a changé, vous devez comparer l'état avant et après :
unit RegistryChangeDetector;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Registry, Generics.Collections;
type
TRegistryValue = record
Name: string;
DataType: TRegDataType;
Value: Variant;
end;
TRegistrySnapshot = class
private
FValues: TDictionary<string, TRegistryValue>;
FSubKeys: TStringList;
public
constructor Create;
destructor Destroy; override;
procedure TakeSnapshot(Reg: TRegistry);
function CompareWith(Other: TRegistrySnapshot;
out Added, Modified, Deleted: TStringList): Boolean;
property Values: TDictionary<string, TRegistryValue> read FValues;
property SubKeys: TStringList read FSubKeys;
end;
{ TRegistryChangeDetector }
TRegistryChangeDetector = class
private
FRootKey: HKEY;
FKeyPath: string;
FLastSnapshot: TRegistrySnapshot;
public
constructor Create(ARootKey: HKEY; const AKeyPath: string);
destructor Destroy; override;
procedure TakeInitialSnapshot;
function DetectChanges(out Report: string): Boolean;
end;
implementation
{ TRegistrySnapshot }
constructor TRegistrySnapshot.Create;
begin
FValues := TDictionary<string, TRegistryValue>.Create;
FSubKeys := TStringList.Create;
end;
destructor TRegistrySnapshot.Destroy;
begin
FValues.Free;
FSubKeys.Free;
inherited;
end;
procedure TRegistrySnapshot.TakeSnapshot(Reg: TRegistry);
var
ValueNames: TStringList;
i: Integer;
RegValue: TRegistryValue;
begin
FValues.Clear;
FSubKeys.Clear;
ValueNames := TStringList.Create;
try
// Capturer les valeurs
Reg.GetValueNames(ValueNames);
for i := 0 to ValueNames.Count - 1 do
begin
RegValue.Name := ValueNames[i];
RegValue.DataType := Reg.GetDataType(ValueNames[i]);
// Lire la valeur selon son type
case RegValue.DataType of
rdString, rdExpandString:
RegValue.Value := Reg.ReadString(ValueNames[i]);
rdInteger:
RegValue.Value := Reg.ReadInteger(ValueNames[i]);
rdBinary:
begin
// Pour simplifier, on stocke la taille
RegValue.Value := Reg.GetDataSize(ValueNames[i]);
end;
else
RegValue.Value := Null;
end;
FValues.Add(ValueNames[i], RegValue);
end;
// Capturer les sous-clés
Reg.GetKeyNames(FSubKeys);
finally
ValueNames.Free;
end;
end;
function TRegistrySnapshot.CompareWith(Other: TRegistrySnapshot;
out Added, Modified, Deleted: TStringList): Boolean;
var
Key: string;
OldValue, NewValue: TRegistryValue;
begin
Result := False;
Added := TStringList.Create;
Modified := TStringList.Create;
Deleted := TStringList.Create;
// Chercher les valeurs supprimées et modifiées
for Key in FValues.Keys do
begin
if Other.Values.TryGetValue(Key, NewValue) then
begin
// La valeur existe dans les deux
OldValue := FValues[Key];
if (OldValue.DataType <> NewValue.DataType) or
(OldValue.Value <> NewValue.Value) then
begin
Modified.Add(Key);
Result := True;
end;
end
else
begin
// La valeur a été supprimée
Deleted.Add(Key);
Result := True;
end;
end;
// Chercher les valeurs ajoutées
for Key in Other.Values.Keys do
begin
if not FValues.ContainsKey(Key) then
begin
Added.Add(Key);
Result := True;
end;
end;
end;
{ TRegistryChangeDetector }
constructor TRegistryChangeDetector.Create(ARootKey: HKEY;
const AKeyPath: string);
begin
FRootKey := ARootKey;
FKeyPath := AKeyPath;
FLastSnapshot := nil;
end;
destructor TRegistryChangeDetector.Destroy;
begin
FLastSnapshot.Free;
inherited;
end;
procedure TRegistryChangeDetector.TakeInitialSnapshot;
var
Reg: TRegistry;
begin
FreeAndNil(FLastSnapshot);
FLastSnapshot := TRegistrySnapshot.Create;
Reg := TRegistry.Create(KEY_READ);
try
Reg.RootKey := FRootKey;
if Reg.OpenKeyReadOnly(FKeyPath) then
begin
try
FLastSnapshot.TakeSnapshot(Reg);
finally
Reg.CloseKey;
end;
end;
finally
Reg.Free;
end;
end;
function TRegistryChangeDetector.DetectChanges(out Report: string): Boolean;
var
Reg: TRegistry;
CurrentSnapshot: TRegistrySnapshot;
Added, Modified, Deleted: TStringList;
i: Integer;
begin
Result := False;
Report := '';
if not Assigned(FLastSnapshot) then
begin
Report := 'Pas de snapshot initial';
Exit;
end;
CurrentSnapshot := TRegistrySnapshot.Create;
try
Reg := TRegistry.Create(KEY_READ);
try
Reg.RootKey := FRootKey;
if Reg.OpenKeyReadOnly(FKeyPath) then
begin
try
CurrentSnapshot.TakeSnapshot(Reg);
finally
Reg.CloseKey;
end;
end;
finally
Reg.Free;
end;
// Comparer les snapshots
Result := FLastSnapshot.CompareWith(CurrentSnapshot,
Added, Modified, Deleted);
if Result then
begin
Report := 'Changements détectés :' + sLineBreak;
if Added.Count > 0 then
begin
Report := Report + sLineBreak + 'Valeurs ajoutées :';
for i := 0 to Added.Count - 1 do
Report := Report + sLineBreak + ' + ' + Added[i];
end;
if Modified.Count > 0 then
begin
Report := Report + sLineBreak + 'Valeurs modifiées :';
for i := 0 to Modified.Count - 1 do
Report := Report + sLineBreak + ' * ' + Modified[i];
end;
if Deleted.Count > 0 then
begin
Report := Report + sLineBreak + 'Valeurs supprimées :';
for i := 0 to Deleted.Count - 1 do
Report := Report + sLineBreak + ' - ' + Deleted[i];
end;
// Mettre à jour le snapshot pour la prochaine comparaison
FLastSnapshot.Free;
FLastSnapshot := CurrentSnapshot;
CurrentSnapshot := nil; // Pour éviter la double libération
end;
Added.Free;
Modified.Free;
Deleted.Free;
finally
CurrentSnapshot.Free;
end;
end;procedure TForm1.FormCreate(Sender: TObject);
begin
// Créer le détecteur
FChangeDetector := TRegistryChangeDetector.Create(
HKEY_CURRENT_USER,
'Software\MonApp\Settings'
);
// Prendre un snapshot initial
FChangeDetector.TakeInitialSnapshot;
// Créer le moniteur pour être alerté des changements
FMonitor := TRegistryMonitor.Create(
HKEY_CURRENT_USER,
'Software\MonApp\Settings'
);
FMonitor.OnChange := @OnRegistryChangeDetailed;
FMonitor.Start;
end;
procedure TForm1.OnRegistryChangeDetailed(Sender: TObject);
var
Report: string;
begin
// Détecter exactement ce qui a changé
if FChangeDetector.DetectChanges(Report) then
begin
Memo1.Lines.Add(FormatDateTime('hh:nn:ss', Now));
Memo1.Lines.Add(Report);
Memo1.Lines.Add('---');
// Réagir selon les changements
ProcessChanges(Report);
end;
end;
procedure TForm1.ProcessChanges(const Report: string);
begin
// Analyser le rapport pour prendre des actions spécifiques
if Pos('Theme', Report) > 0 then
begin
ShowMessage('Le thème a changé - application du nouveau thème...');
ApplyTheme;
end;
if Pos('Language', Report) > 0 then
begin
ShowMessage('La langue a changé - rechargement des ressources...');
ReloadLanguage;
end;
end;unit RegistryChangeLogger;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Registry, Windows, DateUtils;
type
{ TRegistryChangeLogger }
TRegistryChangeLogger = class
private
FLogFile: TextFile;
FLogFileName: string;
FMaxLogSize: Int64;
FRotateCount: Integer;
FEnabled: Boolean;
procedure RotateLogIfNeeded;
procedure WriteLogEntry(const Entry: string);
public
constructor Create(const LogFileName: string);
destructor Destroy; override;
procedure LogChange(RootKey: HKEY; const KeyPath: string;
const Description: string);
procedure LogValueChange(RootKey: HKEY; const KeyPath, ValueName: string;
const OldValue, NewValue: string);
procedure LogKeyAdded(RootKey: HKEY; const KeyPath: string);
procedure LogKeyDeleted(RootKey: HKEY; const KeyPath: string);
property MaxLogSize: Int64 read FMaxLogSize write FMaxLogSize;
property RotateCount: Integer read FRotateCount write FRotateCount;
property Enabled: Boolean read FEnabled write FEnabled;
end;
implementation
{ TRegistryChangeLogger }
constructor TRegistryChangeLogger.Create(const LogFileName: string);
begin
FLogFileName := LogFileName;
FMaxLogSize := 10 * 1024 * 1024; // 10 MB par défaut
FRotateCount := 5; // Garder 5 anciennes versions
FEnabled := True;
// Créer ou ouvrir le fichier log
AssignFile(FLogFile, FLogFileName);
if FileExists(FLogFileName) then
begin
Append(FLogFile);
RotateLogIfNeeded;
end
else
begin
Rewrite(FLogFile);
WriteLn(FLogFile, '=== Registry Change Log Started ===');
WriteLn(FLogFile, 'Date: ' + DateTimeToStr(Now));
WriteLn(FLogFile, '===================================');
end;
Flush(FLogFile);
end;
destructor TRegistryChangeLogger.Destroy;
begin
if FEnabled then
begin
WriteLn(FLogFile, '=== Log Closed: ' + DateTimeToStr(Now) + ' ===');
CloseFile(FLogFile);
end;
inherited;
end;
procedure TRegistryChangeLogger.RotateLogIfNeeded;
var
FileInfo: TSearchRec;
i: Integer;
OldName, NewName: string;
begin
// Vérifier la taille du fichier
if FindFirst(FLogFileName, faAnyFile, FileInfo) = 0 then
begin
try
if FileInfo.Size > FMaxLogSize then
begin
CloseFile(FLogFile);
// Supprimer le plus ancien
if FileExists(FLogFileName + '.' + IntToStr(FRotateCount)) then
DeleteFile(FLogFileName + '.' + IntToStr(FRotateCount));
// Décaler les autres
for i := FRotateCount - 1 downto 1 do
begin
OldName := FLogFileName + '.' + IntToStr(i);
NewName := FLogFileName + '.' + IntToStr(i + 1);
if FileExists(OldName) then
RenameFile(OldName, NewName);
end;
// Renommer le fichier actuel
RenameFile(FLogFileName, FLogFileName + '.1');
// Créer un nouveau fichier
Rewrite(FLogFile);
WriteLn(FLogFile, '=== Log Rotated: ' + DateTimeToStr(Now) + ' ===');
end;
finally
FindClose(FileInfo);
end;
end;
end;
procedure TRegistryChangeLogger.WriteLogEntry(const Entry: string);
begin
if not FEnabled then Exit;
WriteLn(FLogFile, FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz', Now) +
' | ' + Entry);
Flush(FLogFile);
RotateLogIfNeeded;
end;
procedure TRegistryChangeLogger.LogChange(RootKey: HKEY; const KeyPath: string;
const Description: string);
var
RootName: string;
begin
case RootKey of
HKEY_CLASSES_ROOT: RootName := 'HKCR';
HKEY_CURRENT_USER: RootName := 'HKCU';
HKEY_LOCAL_MACHINE: RootName := 'HKLM';
HKEY_USERS: RootName := 'HKU';
HKEY_CURRENT_CONFIG: RootName := 'HKCC';
else
RootName := Format('0x%x', [RootKey]);
end;
WriteLogEntry(Format('CHANGE | %s\%s | %s',
[RootName, KeyPath, Description]));
end;
procedure TRegistryChangeLogger.LogValueChange(RootKey: HKEY;
const KeyPath, ValueName: string; const OldValue, NewValue: string);
var
Entry: string;
begin
Entry := Format('VALUE_CHANGED | %s | "%s" -> "%s"',
[ValueName, OldValue, NewValue]);
LogChange(RootKey, KeyPath, Entry);
end;
procedure TRegistryChangeLogger.LogKeyAdded(RootKey: HKEY; const KeyPath: string);
begin
LogChange(RootKey, KeyPath, 'KEY_ADDED');
end;
procedure TRegistryChangeLogger.LogKeyDeleted(RootKey: HKEY; const KeyPath: string);
begin
LogChange(RootKey, KeyPath, 'KEY_DELETED');
end;unit ConfigSyncManager;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Windows, Registry, SyncObjs;
type
{ TConfigSyncManager }
TConfigSyncManager = class
private
FMutex: TMutex;
FMonitor: TRegistryMonitor;
FOnConfigChanged: TNotifyEvent;
FIgnoreNextChange: Boolean;
procedure OnRegistryChange(Sender: TObject);
public
constructor Create;
destructor Destroy; override;
procedure WriteConfig(const Name, Value: string);
function ReadConfig(const Name: string; const Default: string = ''): string;
procedure StartSync;
procedure StopSync;
property OnConfigChanged: TNotifyEvent read FOnConfigChanged
write FOnConfigChanged;
end;
implementation
const
CONFIG_KEY = 'Software\MonEntreprise\SharedConfig';
MUTEX_NAME = 'MonEntreprise_ConfigSync_Mutex';
{ TConfigSyncManager }
constructor TConfigSyncManager.Create;
begin
// Créer un mutex pour la synchronisation inter-processus
FMutex := TMutex.Create(nil, False, MUTEX_NAME);
// Créer le moniteur
FMonitor := TRegistryMonitor.Create(HKEY_CURRENT_USER, CONFIG_KEY);
FMonitor.OnChange := @OnRegistryChange;
FIgnoreNextChange := False;
end;
destructor TConfigSyncManager.Destroy;
begin
StopSync;
FMonitor.Free;
FMutex.Free;
inherited;
end;
procedure TConfigSyncManager.WriteConfig(const Name, Value: string);
var
Reg: TRegistry;
begin
// Acquérir le mutex pour éviter les conflits
if FMutex.WaitFor(1000) = wrSignaled then
begin
try
FIgnoreNextChange := True; // Ignorer notre propre modification
Reg := TRegistry.Create;
try
Reg.RootKey := HKEY_CURRENT_USER;
if Reg.OpenKey(CONFIG_KEY, True) then
begin
try
Reg.WriteString(Name, Value);
// Ajouter un timestamp pour indiquer la dernière modification
Reg.WriteDateTime('LastModified_' + Name, Now);
Reg.WriteString('LastModifiedBy_' + Name,
ExtractFileName(ParamStr(0)));
finally
Reg.CloseKey;
end;
end;
finally
Reg.Free;
end;
finally
FMutex.Release;
end;
end
else
raise Exception.Create('Timeout lors de l''accès au mutex de configuration');
end;
function TConfigSyncManager.ReadConfig(const Name: string;
const Default: string): string;
var
Reg: TRegistry;
begin
Result := Default;
// Lecture rapide sans mutex (lecture seule)
Reg := TRegistry.Create(KEY_READ);
try
Reg.RootKey := HKEY_CURRENT_USER;
if Reg.OpenKeyReadOnly(CONFIG_KEY) then
begin
try
if Reg.ValueExists(Name) then
Result := Reg.ReadString(Name);
finally
Reg.CloseKey;
end;
end;
finally
Reg.Free;
end;
end;
procedure TConfigSyncManager.StartSync;
begin
FMonitor.Start;
end;
procedure TConfigSyncManager.StopSync;
begin
FMonitor.Stop;
end;
procedure TConfigSyncManager.OnRegistryChange(Sender: TObject);
begin
if FIgnoreNextChange then
begin
FIgnoreNextChange := False;
Exit;
end;
// Notification de changement externe
if Assigned(FOnConfigChanged) then
FOnConfigChanged(Self);
end;unit RegistrySecurityMonitor;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Windows, Registry;
type
TSecurityLevel = (slNormal, slWarning, slCritical);
TSecurityAlert = record
Level: TSecurityLevel;
Timestamp: TDateTime;
KeyPath: string;
Description: string;
ProcessName: string;
end;
TSecurityAlertEvent = procedure(Sender: TObject;
const Alert: TSecurityAlert) of object;
{ TRegistrySecurityMonitor }
TRegistrySecurityMonitor = class
private
FCriticalKeys: TStringList;
FMonitors: TList;
FOnSecurityAlert: TSecurityAlertEvent;
FLogFile: string;
procedure MonitorChange(Sender: TObject);
procedure CheckForSuspiciousActivity(const KeyPath: string);
procedure RaiseAlert(Level: TSecurityLevel; const KeyPath,
Description: string);
procedure LogAlert(const Alert: TSecurityAlert);
public
constructor Create;
destructor Destroy; override;
procedure AddCriticalKey(RootKey: HKEY; const KeyPath: string);
procedure StartMonitoring;
procedure StopMonitoring;
property OnSecurityAlert: TSecurityAlertEvent read FOnSecurityAlert
write FOnSecurityAlert;
property LogFile: string read FLogFile write FLogFile;
end;
implementation
{ TRegistrySecurityMonitor }
constructor TRegistrySecurityMonitor.Create;
begin
FCriticalKeys := TStringList.Create;
FMonitors := TList.Create;
FLogFile := ExtractFilePath(ParamStr(0)) + 'security.log';
// Ajouter les clés critiques par défaut
AddCriticalKey(HKEY_LOCAL_MACHINE,
'SOFTWARE\Microsoft\Windows\CurrentVersion\Run');
AddCriticalKey(HKEY_CURRENT_USER,
'Software\Microsoft\Windows\CurrentVersion\Run');
AddCriticalKey(HKEY_LOCAL_MACHINE,
'SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon');
AddCriticalKey(HKEY_LOCAL_MACHINE,
'SYSTEM\CurrentControlSet\Services');
AddCriticalKey(HKEY_CLASSES_ROOT,
'.exe');
end;
destructor TRegistrySecurityMonitor.Destroy;
begin
StopMonitoring;
// Libérer les moniteurs
while FMonitors.Count > 0 do
begin
TObject(FMonitors[0]).Free;
FMonitors.Delete(0);
end;
FMonitors.Free;
FCriticalKeys.Free;
inherited;
end;
procedure TRegistrySecurityMonitor.AddCriticalKey(RootKey: HKEY;
const KeyPath: string);
var
Monitor: TRegistryMonitor;
begin
// Créer un moniteur pour cette clé
Monitor := TRegistryMonitor.Create(RootKey, KeyPath);
Monitor.OnChange := @MonitorChange;
Monitor.WatchSubTree := True;
FMonitors.Add(Monitor);
FCriticalKeys.Add(Format('%d|%s', [RootKey, KeyPath]));
end;
procedure TRegistrySecurityMonitor.StartMonitoring;
var
i: Integer;
begin
for i := 0 to FMonitors.Count - 1 do
TRegistryMonitor(FMonitors[i]).Start;
end;
procedure TRegistrySecurityMonitor.StopMonitoring;
var
i: Integer;
begin
for i := 0 to FMonitors.Count - 1 do
TRegistryMonitor(FMonitors[i]).Stop;
end;
procedure TRegistrySecurityMonitor.MonitorChange(Sender: TObject);
var
Monitor: TRegistryMonitor;
begin
Monitor := Sender as TRegistryMonitor;
// Vérifier l'activité suspecte
CheckForSuspiciousActivity(Monitor.KeyPath);
end;
procedure TRegistrySecurityMonitor.CheckForSuspiciousActivity(
const KeyPath: string);
var
Reg: TRegistry;
Values: TStringList;
i: Integer;
SuspiciousFound: Boolean;
Value: string;
begin
SuspiciousFound := False;
Reg := TRegistry.Create(KEY_READ);
Values := TStringList.Create;
try
// Analyser les valeurs pour détecter des patterns suspects
if Pos('\Run', KeyPath) > 0 then
begin
// Vérifier les nouvelles entrées de démarrage automatique
Reg.RootKey := HKEY_CURRENT_USER;
if Reg.OpenKeyReadOnly(KeyPath) then
begin
try
Reg.GetValueNames(Values);
for i := 0 to Values.Count - 1 do
begin
Value := Reg.ReadString(Values[i]);
// Patterns suspects
if (Pos('temp\', LowerCase(Value)) > 0) or
(Pos('appdata\local\temp', LowerCase(Value)) > 0) or
(Pos('powershell', LowerCase(Value)) > 0) or
(Pos('cmd.exe', LowerCase(Value)) > 0) or
(Pos('wscript', LowerCase(Value)) > 0) then
begin
RaiseAlert(slCritical, KeyPath,
Format('Programme suspect au démarrage : %s = %s',
[Values[i], Value]));
SuspiciousFound := True;
end;
end;
finally
Reg.CloseKey;
end;
end;
end;
if not SuspiciousFound then
begin
// Alerte normale pour les clés surveillées
RaiseAlert(slWarning, KeyPath, 'Modification détectée dans une clé critique');
end;
finally
Values.Free;
Reg.Free;
end;
end;
procedure TRegistrySecurityMonitor.RaiseAlert(Level: TSecurityLevel;
const KeyPath, Description: string);
var
Alert: TSecurityAlert;
begin
Alert.Level := Level;
Alert.Timestamp := Now;
Alert.KeyPath := KeyPath;
Alert.Description := Description;
Alert.ProcessName := ExtractFileName(ParamStr(0));
// Logger l'alerte
LogAlert(Alert);
// Notifier l'application
if Assigned(FOnSecurityAlert) then
FOnSecurityAlert(Self, Alert);
end;
procedure TRegistrySecurityMonitor.LogAlert(const Alert: TSecurityAlert);
var
F: TextFile;
LevelStr: string;
begin
case Alert.Level of
slNormal: LevelStr := 'NORMAL';
slWarning: LevelStr := 'WARNING';
slCritical: LevelStr := 'CRITICAL';
end;
AssignFile(F, FLogFile);
try
if FileExists(FLogFile) then
Append(F)
else
Rewrite(F);
WriteLn(F, Format('[%s] [%s] %s | %s | %s',
[FormatDateTime('yyyy-mm-dd hh:nn:ss', Alert.Timestamp),
LevelStr,
Alert.KeyPath,
Alert.Description,
Alert.ProcessName]));
finally
CloseFile(F);
end;
end;unit OptimizedRegistryMonitor;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Windows, Registry, DateUtils;
type
{ TOptimizedRegistryMonitor }
TOptimizedRegistryMonitor = class(TThread)
private
FKeyPaths: TStringList;
FLastCheck: TDateTime;
FCheckInterval: Integer; // Secondes
FCache: TStringList;
FUseCache: Boolean;
FCacheTimeout: Integer; // Secondes
procedure UpdateCache(const KeyPath, Data: string);
function GetFromCache(const KeyPath: string; out Data: string): Boolean;
procedure ClearExpiredCache;
protected
procedure Execute; override;
public
constructor Create;
destructor Destroy; override;
procedure AddKey(const KeyPath: string);
procedure RemoveKey(const KeyPath: string);
property CheckInterval: Integer read FCheckInterval write FCheckInterval;
property UseCache: Boolean read FUseCache write FUseCache;
property CacheTimeout: Integer read FCacheTimeout write FCacheTimeout;
end;
implementation
type
TCacheEntry = class
Data: string;
Timestamp: TDateTime;
end;
{ TOptimizedRegistryMonitor }
constructor TOptimizedRegistryMonitor.Create;
begin
inherited Create(True);
FKeyPaths := TStringList.Create;
FCache := TStringList.Create;
FCache.OwnsObjects := True;
FCheckInterval := 5; // Vérifier toutes les 5 secondes
FCacheTimeout := 30; // Cache valide 30 secondes
FUseCache := True;
FLastCheck := 0;
FreeOnTerminate := False;
end;
destructor TOptimizedRegistryMonitor.Destroy;
begin
FCache.Free;
FKeyPaths.Free;
inherited;
end;
procedure TOptimizedRegistryMonitor.AddKey(const KeyPath: string);
begin
FKeyPaths.Add(KeyPath);
end;
procedure TOptimizedRegistryMonitor.RemoveKey(const KeyPath: string);
var
Index: Integer;
begin
Index := FKeyPaths.IndexOf(KeyPath);
if Index >= 0 then
begin
FKeyPaths.Delete(Index);
// Retirer du cache aussi
Index := FCache.IndexOf(KeyPath);
if Index >= 0 then
FCache.Delete(Index);
end;
end;
procedure TOptimizedRegistryMonitor.UpdateCache(const KeyPath, Data: string);
var
Entry: TCacheEntry;
Index: Integer;
begin
if not FUseCache then Exit;
Index := FCache.IndexOf(KeyPath);
if Index >= 0 then
begin
Entry := TCacheEntry(FCache.Objects[Index]);
Entry.Data := Data;
Entry.Timestamp := Now;
end
else
begin
Entry := TCacheEntry.Create;
Entry.Data := Data;
Entry.Timestamp := Now;
FCache.AddObject(KeyPath, Entry);
end;
end;
function TOptimizedRegistryMonitor.GetFromCache(const KeyPath: string;
out Data: string): Boolean;
var
Index: Integer;
Entry: TCacheEntry;
begin
Result := False;
Data := '';
if not FUseCache then Exit;
Index := FCache.IndexOf(KeyPath);
if Index >= 0 then
begin
Entry := TCacheEntry(FCache.Objects[Index]);
// Vérifier si le cache est encore valide
if SecondsBetween(Now, Entry.Timestamp) <= FCacheTimeout then
begin
Data := Entry.Data;
Result := True;
end;
end;
end;
procedure TOptimizedRegistryMonitor.ClearExpiredCache;
var
i: Integer;
Entry: TCacheEntry;
begin
if not FUseCache then Exit;
for i := FCache.Count - 1 downto 0 do
begin
Entry := TCacheEntry(FCache.Objects[i]);
if SecondsBetween(Now, Entry.Timestamp) > FCacheTimeout then
FCache.Delete(i);
end;
end;
procedure TOptimizedRegistryMonitor.Execute;
var
i: Integer;
CachedData: string;
begin
while not Terminated do
begin
// Vérifier l'intervalle
if SecondsBetween(Now, FLastCheck) >= FCheckInterval then
begin
FLastCheck := Now;
// Nettoyer le cache expiré
ClearExpiredCache;
// Vérifier chaque clé
for i := 0 to FKeyPaths.Count - 1 do
begin
if Terminated then Break;
// Essayer d'abord le cache
if not GetFromCache(FKeyPaths[i], CachedData) then
begin
// Pas dans le cache ou expiré, lire du registre
// ... (code de lecture du registre)
// Mettre à jour le cache
UpdateCache(FKeyPaths[i], CachedData);
end;
end;
end;
Sleep(100); // Petite pause pour ne pas surcharger le CPU
end;
end;
end.// MAUVAIS - Surveiller tout le registre
Monitor.Create(HKEY_LOCAL_MACHINE, 'SOFTWARE');
Monitor.WatchSubTree := True; // Surveille des milliers de clés !
// BON - Surveiller uniquement ce qui est nécessaire
Monitor.Create(HKEY_CURRENT_USER, 'Software\MonApp\Settings');
Monitor.WatchSubTree := False; // Seulement cette clé// MAUVAIS - Bloque l'interface
procedure TForm1.Button1Click(Sender: TObject);
begin
RegNotifyChangeKeyValue(..., False); // Bloquant !
end;
// BON - Utiliser un thread
procedure TForm1.Button1Click(Sender: TObject);
begin
FMonitor := TRegistryMonitor.Create(...);
FMonitor.Start; // Non bloquant
end;try
Monitor.Start;
except
on E: Exception do
begin
if Pos('Access denied', E.Message) > 0 then
ShowMessage('Permissions insuffisantes pour surveiller cette clé')
else
ShowMessage('Erreur : ' + E.Message);
end;
end;procedure TForm1.FormDestroy(Sender: TObject);
begin
// Toujours arrêter et libérer les moniteurs
if Assigned(FMonitor) then
begin
FMonitor.Stop;
FMonitor.Free;
end;
end;procedure OnRegistryChange;
begin
// MAUVAIS - Peut créer une boucle
Reg.WriteString('LastCheck', DateTimeToStr(Now)); // Déclenche un autre changement !
// BON - Utiliser un flag
if not FUpdating then
begin
FUpdating := True;
try
Reg.WriteString('LastCheck', DateTimeToStr(Now));
finally
FUpdating := False;
end;
end;
end;- Deux approches : Synchrone (bloquante) ou asynchrone (avec threads)
- RegNotifyChangeKeyValue est l'API Windows de base
- Utiliser des Events pour la surveillance non-bloquante
- Comparer des snapshots pour détecter les changements exacts
- Logger les modifications pour l'audit et le débogage
- Surveiller la sécurité pour détecter les activités suspectes
- Optimiser avec un cache pour réduire les accès au registre
- Gérer les erreurs de permission et d'accès
- Libérer les ressources proprement
- Éviter les boucles infinies lors des modifications
Causes possibles et solutions :
// Vérifier les permissions
procedure CheckMonitoringPermissions;
var
Reg: TRegistry;
begin
Reg := TRegistry.Create(KEY_NOTIFY or KEY_READ);
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
if not Reg.OpenKeyReadOnly('SOFTWARE\TestKey') then
begin
ShowMessage('Impossible d''ouvrir la clé - vérifiez les permissions');
// Essayer avec des permissions utilisateur
Reg.RootKey := HKEY_CURRENT_USER;
if Reg.OpenKeyReadOnly('Software\TestKey') then
ShowMessage('Fonctionne avec HKEY_CURRENT_USER');
end;
finally
Reg.Free;
end;
end;
// Vérifier le filtre de notification
procedure CheckNotificationFilter;
begin
// S'assurer que le bon filtre est utilisé
Monitor.NotifyFilter := REG_NOTIFY_CHANGE_LAST_SET; // Pour les valeurs
// ou
Monitor.NotifyFilter := REG_NOTIFY_CHANGE_NAME; // Pour les clés
// ou
Monitor.NotifyFilter := REG_NOTIFY_CHANGE_LAST_SET or
REG_NOTIFY_CHANGE_NAME; // Pour tout
end;Solution : Utiliser la surveillance asynchrone
// PROBLÈME : Code bloquant
procedure TForm1.Button1Click(Sender: TObject);
begin
RegNotifyChangeKeyValue(Handle, True, REG_NOTIFY_ALL, 0, False);
// L'interface est bloquée jusqu'à un changement !
end;
// SOLUTION : Utiliser un thread
procedure TForm1.Button1Click(Sender: TObject);
begin
TThread.CreateAnonymousThread(
procedure
begin
RegNotifyChangeKeyValue(Handle, True, REG_NOTIFY_ALL, 0, False);
TThread.Synchronize(nil,
procedure
begin
ShowMessage('Changement détecté !');
end
);
end
).Start;
end;Solution : Toujours nettoyer les ressources
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FMonitors: TObjectList<TRegistryMonitor>;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FMonitors := TObjectList<TRegistryMonitor>.Create(True); // True = owns objects
// Ajouter des moniteurs
FMonitors.Add(TRegistryMonitor.Create(...));
end;
procedure TForm1.FormDestroy(Sender: TObject);
var
Monitor: TRegistryMonitor;
begin
// Arrêter tous les moniteurs avant de les libérer
for Monitor in FMonitors do
Monitor.Stop;
FMonitors.Free; // Libère automatiquement tous les moniteurs
end;unit RegistryWatcher;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Windows, Registry, Generics.Collections, DateUtils;
type
TChangeType = (ctValueAdded, ctValueModified, ctValueDeleted,
ctKeyAdded, ctKeyDeleted);
TRegistryChange = record
ChangeType: TChangeType;
KeyPath: string;
ValueName: string;
OldValue: string;
NewValue: string;
Timestamp: TDateTime;
end;
TOnRegistryChange = procedure(Sender: TObject;
const Change: TRegistryChange) of object;
{ TRegistryWatcher }
TRegistryWatcher = class(TComponent)
private
FRootKey: HKEY;
FKeyPath: string;
FActive: Boolean;
FWatchSubTree: Boolean;
FThread: TThread;
FOnChange: TOnRegistryChange;
FSnapshot: TDictionary<string, string>;
FEventHandle: THandle;
procedure TakeSnapshot;
procedure CompareSnapshots;
procedure StartWatching;
procedure StopWatching;
procedure SetActive(AValue: Boolean);
protected
procedure DoChange(const Change: TRegistryChange); virtual;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Start;
procedure Stop;
procedure Refresh;
published
property RootKey: HKEY read FRootKey write FRootKey default HKEY_CURRENT_USER;
property KeyPath: string read FKeyPath write FKeyPath;
property Active: Boolean read FActive write SetActive default False;
property WatchSubTree: Boolean read FWatchSubTree write FWatchSubTree default True;
property OnChange: TOnRegistryChange read FOnChange write FOnChange;
end;
procedure Register;
implementation
type
{ TWatcherThread }
TWatcherThread = class(TThread)
private
FWatcher: TRegistryWatcher;
FKeyHandle: HKEY;
FEventHandle: THandle;
protected
procedure Execute; override;
public
constructor Create(AWatcher: TRegistryWatcher; AEventHandle: THandle);
destructor Destroy; override;
end;
{ TRegistryWatcher }
constructor TRegistryWatcher.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FRootKey := HKEY_CURRENT_USER;
FKeyPath := '';
FActive := False;
FWatchSubTree := True;
FSnapshot := TDictionary<string, string>.Create;
FEventHandle := CreateEvent(nil, False, False, nil);
end;
destructor TRegistryWatcher.Destroy;
begin
Stop;
if FEventHandle <> 0 then
CloseHandle(FEventHandle);
FSnapshot.Free;
inherited;
end;
procedure TRegistryWatcher.Start;
begin
if FActive then Exit;
if FKeyPath = '' then
raise Exception.Create('KeyPath non défini');
TakeSnapshot;
StartWatching;
FActive := True;
end;
procedure TRegistryWatcher.Stop;
begin
if not FActive then Exit;
StopWatching;
FActive := False;
end;
procedure TRegistryWatcher.Refresh;
begin
if FActive then
begin
CompareSnapshots;
TakeSnapshot;
end;
end;
procedure TRegistryWatcher.SetActive(AValue: Boolean);
begin
if FActive = AValue then Exit;
if AValue then
Start
else
Stop;
end;
procedure TRegistryWatcher.TakeSnapshot;
var
Reg: TRegistry;
Values: TStringList;
i: Integer;
Key, Value: string;
begin
FSnapshot.Clear;
Reg := TRegistry.Create(KEY_READ);
Values := TStringList.Create;
try
Reg.RootKey := FRootKey;
if Reg.OpenKeyReadOnly(FKeyPath) then
begin
try
Reg.GetValueNames(Values);
for i := 0 to Values.Count - 1 do
begin
Key := Values[i];
case Reg.GetDataType(Key) of
rdString, rdExpandString:
Value := Reg.ReadString(Key);
rdInteger:
Value := IntToStr(Reg.ReadInteger(Key));
else
Value := '[Binary Data]';
end;
FSnapshot.Add(Key, Value);
end;
finally
Reg.CloseKey;
end;
end;
finally
Values.Free;
Reg.Free;
end;
end;
procedure TRegistryWatcher.CompareSnapshots;
var
NewSnapshot: TDictionary<string, string>;
Reg: TRegistry;
Values: TStringList;
i: Integer;
Key, OldValue, NewValue: string;
Change: TRegistryChange;
begin
NewSnapshot := TDictionary<string, string>.Create;
try
// Prendre un nouveau snapshot
Reg := TRegistry.Create(KEY_READ);
Values := TStringList.Create;
try
Reg.RootKey := FRootKey;
if Reg.OpenKeyReadOnly(FKeyPath) then
begin
try
Reg.GetValueNames(Values);
for i := 0 to Values.Count - 1 do
begin
Key := Values[i];
case Reg.GetDataType(Key) of
rdString, rdExpandString:
NewValue := Reg.ReadString(Key);
rdInteger:
NewValue := IntToStr(Reg.ReadInteger(Key));
else
NewValue := '[Binary Data]';
end;
NewSnapshot.Add(Key, NewValue);
// Vérifier si c'est une nouvelle valeur ou une modification
if FSnapshot.TryGetValue(Key, OldValue) then
begin
if OldValue <> NewValue then
begin
// Valeur modifiée
Change.ChangeType := ctValueModified;
Change.KeyPath := FKeyPath;
Change.ValueName := Key;
Change.OldValue := OldValue;
Change.NewValue := NewValue;
Change.Timestamp := Now;
DoChange(Change);
end;
end
else
begin
// Nouvelle valeur
Change.ChangeType := ctValueAdded;
Change.KeyPath := FKeyPath;
Change.ValueName := Key;
Change.OldValue := '';
Change.NewValue := NewValue;
Change.Timestamp := Now;
DoChange(Change);
end;
end;
finally
Reg.CloseKey;
end;
end;
finally
Values.Free;
Reg.Free;
end;
// Vérifier les valeurs supprimées
for Key in FSnapshot.Keys do
begin
if not NewSnapshot.ContainsKey(Key) then
begin
// Valeur supprimée
FSnapshot.TryGetValue(Key, OldValue);
Change.ChangeType := ctValueDeleted;
Change.KeyPath := FKeyPath;
Change.ValueName := Key;
Change.OldValue := OldValue;
Change.NewValue := '';
Change.Timestamp := Now;
DoChange(Change);
end;
end;
// Mettre à jour le snapshot
FSnapshot.Free;
FSnapshot := NewSnapshot;
NewSnapshot := nil;
finally
NewSnapshot.Free;
end;
end;
procedure TRegistryWatcher.StartWatching;
begin
FThread := TWatcherThread.Create(Self, FEventHandle);
FThread.Start;
end;
procedure TRegistryWatcher.StopWatching;
begin
if Assigned(FThread) then
begin
FThread.Terminate;
SetEvent(FEventHandle); // Débloquer le thread
FThread.WaitFor;
FThread.Free;
FThread := nil;
end;
end;
procedure TRegistryWatcher.DoChange(const Change: TRegistryChange);
begin
if Assigned(FOnChange) then
FOnChange(Self, Change);
end;
{ TWatcherThread }
constructor TWatcherThread.Create(AWatcher: TRegistryWatcher;
AEventHandle: THandle);
var
Reg: TRegistry;
begin
inherited Create(True);
FWatcher := AWatcher;
FEventHandle := AEventHandle;
FreeOnTerminate := False;
// Ouvrir la clé
Reg := TRegistry.Create(KEY_NOTIFY);
try
Reg.RootKey := FWatcher.RootKey;
if Reg.OpenKeyReadOnly(FWatcher.KeyPath) then
FKeyHandle := Reg.CurrentKey
else
raise Exception.Create('Impossible d''ouvrir la clé pour surveillance');
finally
Reg.Free;
end;
end;
destructor TWatcherThread.Destroy;
begin
if FKeyHandle <> 0 then
RegCloseKey(FKeyHandle);
inherited;
end;
procedure TWatcherThread.Execute;
var
Result: Longint;
begin
while not Terminated do
begin
// Configurer la notification
Result := RegNotifyChangeKeyValue(
FKeyHandle,
FWatcher.WatchSubTree,
REG_NOTIFY_CHANGE_LAST_SET or REG_NOTIFY_CHANGE_NAME,
FEventHandle,
True
);
if Result <> ERROR_SUCCESS then
Break;
// Attendre l'event ou la terminaison
if WaitForSingleObject(FEventHandle, INFINITE) = WAIT_OBJECT_0 then
begin
if not Terminated then
begin
// Un changement a eu lieu
TThread.Synchronize(nil,
procedure
begin
FWatcher.CompareSnapshots;
FWatcher.TakeSnapshot;
end
);
end;
end;
end;
end;
procedure Register;
begin
RegisterComponents('System', [TRegistryWatcher]);
end;
end.procedure TForm1.FormCreate(Sender: TObject);
begin
// Créer le watcher
FWatcher := TRegistryWatcher.Create(Self);
FWatcher.RootKey := HKEY_CURRENT_USER;
FWatcher.KeyPath := 'Software\MonApp\Settings';
FWatcher.OnChange := @OnRegistryChanged;
// Démarrer la surveillance
FWatcher.Start;
end;
procedure TForm1.OnRegistryChanged(Sender: TObject;
const Change: TRegistryChange);
var
ChangeTypeStr: string;
begin
case Change.ChangeType of
ctValueAdded: ChangeTypeStr := 'Ajout';
ctValueModified: ChangeTypeStr := 'Modification';
ctValueDeleted: ChangeTypeStr := 'Suppression';
ctKeyAdded: ChangeTypeStr := 'Nouvelle clé';
ctKeyDeleted: ChangeTypeStr := 'Clé supprimée';
end;
Memo1.Lines.Add(Format('[%s] %s - %s: %s -> %s',
[FormatDateTime('hh:nn:ss', Change.Timestamp),
ChangeTypeStr,
Change.ValueName,
Change.OldValue,
Change.NewValue]));
end;- Identifier les clés exactes à surveiller
- Déterminer le niveau de détail nécessaire (clé seule ou avec sous-clés)
- Vérifier les permissions d'accès aux clés
- Choisir entre surveillance synchrone ou asynchrone
- Prévoir la gestion des erreurs
- Implémenter la surveillance de base
- Ajouter la comparaison de snapshots si nécessaire
- Mettre en place la journalisation
- Tester avec différents types de modifications
- Gérer les cas limites (clé supprimée, permissions perdues)
- Optimiser la fréquence de vérification
- Implémenter un cache si approprié
- Tester les performances avec plusieurs clés
- Vérifier l'absence de fuites mémoire
- Documenter les clés surveillées et pourquoi
- Nombre maximum d'events : Windows limite le nombre d'objets event
- Performance : Surveiller trop de clés peut impacter les performances
- Notifications manquées : Si trop de changements trop rapidement
// Surveiller efficacement
const
MAX_MONITORED_KEYS = 10; // Limiter le nombre de clés
CHECK_INTERVAL = 5000; // Ne pas vérifier trop souvent (ms)
CACHE_TIMEOUT = 30; // Utiliser un cache (secondes)
// Regrouper les notifications
type
TBatchedMonitor = class
private
FChanges: TList<TRegistryChange>;
FBatchTimer: TTimer;
procedure FlushBatch;
public
procedure AddChange(const Change: TRegistryChange);
end;
procedure TBatchedMonitor.AddChange(const Change: TRegistryChange);
begin
FChanges.Add(Change);
// Traiter par batch toutes les 500ms
FBatchTimer.Enabled := False;
FBatchTimer.Interval := 500;
FBatchTimer.Enabled := True;
end;
procedure TBatchedMonitor.FlushBatch;
begin
// Traiter tous les changements d'un coup
ProcessAllChanges(FChanges);
FChanges.Clear;
end;La surveillance des modifications du Registre Windows est un outil puissant pour :
- Synchroniser des configurations entre applications
- Détecter des modifications non autorisées
- Déboguer des problèmes de configuration
- Auditer les changements système
Les points essentiels à retenir :
- Utilisez
RegNotifyChangeKeyValuepour la surveillance de base - Implémentez avec des threads pour ne pas bloquer
- Comparez des snapshots pour détecter les changements précis
- Gérez correctement les erreurs et permissions
- Optimisez pour éviter l'impact sur les performances
Avec les exemples et templates fournis, vous avez tout ce qu'il faut pour implémenter une surveillance robuste et efficace du Registre Windows dans vos applications FreePascal/Lazarus.