I. Introduction▲
Vous avez sans doute remarqué, à l'ouverture d'une base de données, qu'un fichier avec l'extension ldb ou laccdb est généralement créé.
Ce fichier contient une liste d'utilisateurs (et le nom de leur ordinateur) qui se sont connectés à la base de données.
Mais cette liste n'est pas utilisable en l'état sans connaître le mécanisme de verrous utilisé.
Ce mécanisme est expliqué dans un document (en anglais) que vous pouvez trouver ici :
ACC : Utilitaires de Microsoft Jet disponibles dans le centre de téléchargement.
Le document en question est Jetlock.doc (Understanding Microsoft Jet Locking White Paper).
Il contient beaucoup d'informations techniques pas forcément à jour pour les dernières versions d'Access.
Il est donc réservé aux plus curieux et motivés !
Cet article explique ce mécanisme de connexion aux bases de données et comment lire les informations des connexions actives.
En fin d'article, vous pourrez télécharger le code qui permet de dresser la liste des utilisateurs d'une base de données.
II. Le fichier de verrouillage▲
Le fichier de verrouillage est géré par le moteur de base de données :
- JET jusqu'à Access 2003 inclus ;
- ACE à partir d'Access 2007.
Ce n'est donc pas l'application Access qui crée ce fichier.
Un accès à la base de données par une application externe utilisera donc ce même mécanisme de verrous.
II-A. Extension du fichier▲
Le fichier de verrouillage a une extension ldb pour les formats de fichier jusqu'à Access 2003 (mdb = JET).
À partir du format de fichier Access 2007 (accdb = ACE), le fichier de verrouillage a une extension laccdb.
II-B. Structure▲
Il contient une liste d'ordinateurs et d'utilisateurs.
Chacune de ces deux informations est écrite sur une longueur fixe de 32 caractères.
Un caractère Null (chr(0) ou vbNullChar) marque la fin de chaque information.
Si on ouvre le fichier de verrouillage avec Notepad++ par exemple, on voit très bien ce caractère de fin NUL :
Le nom d'utilisateur est celui de la sécurité au niveau utilisateur.
Si aucune sécurité utilisateur n'est mise en place, Admin sera l'utilisateur pour toutes les connexions.
Seul le nom de PC pourra alors différencier les utilisateurs.
Le nombre maximal de connexions est 255.
Le fichier de verrouillage sera donc d'une taille maximale de 255 * 64 = 16 320 octets.
II-C. Création, Modification et Suppression▲
Le fichier de verrouillage est créé à la première connexion (partagée) à la base de données.
Ce fichier est placé dans le même répertoire que le fichier de la base de données et porte le même nom, à part l'extension qui diffère.
Lors d'une ouverture de la base de données en mode exclusif, aucun fichier de verrouillage n'est créé.
C'est le fichier de base de données (mdb, accdb...) qui est verrouillé. Une seule personne à la fois peut ouvrir une base de données en mode exclusif.
Dans ce fichier, une ligne est ajoutée (ou modifiée) avec les informations de l'utilisateur (cf. le chapitre suivant pour la structure des données).
Lors de nouvelles connexions, une ligne est ajoutée pour chaque nouvel utilisateur.
Attention : lors d'une déconnexion, la ligne correspondant à l'utilisateur n'est pas supprimée.
La ligne d'information d'un utilisateur déconnecté est marquée comme disponible grâce à un mécanisme de verrou que nous verrons dans le chapitre suivant.
Une prochaine connexion écrasera les informations d'une ligne disponible dans le fichier s'il en existe une, ou créera une nouvelle ligne si nécessaire.
Lorsqu'il n'y a plus aucun utilisateur connecté, le fichier de verrou est supprimé.
Notez que le contenu du fichier de verrous est présenté avec une ligne par connexion pour une meilleure compréhension.
En réalité les informations sont écrites dans le fichier sans retour à la ligne.
Lire le contenu du fichier de verrouillage n'est donc pas suffisant pour connaître la liste des utilisateurs connectés.
Des utilisateurs qui se sont connectés puis déconnectés peuvent apparaître dans la liste.
II-D. Les Verrous▲
II-D-1. Le verrouillage de fichiers sous Windows▲
Un fichier sous Windows peut être ouvert en mode partagé ou en mode exclusif :
- en mode partagé, chaque utilisateur peut verrouiller une partie différente du fichier ;
- en mode exclusif, l'ensemble du fichier est verrouillé et un seul utilisateur à la fois peut ouvrir le fichier.
Dans ces deux cas, le fichier verrouillé en totalité ou en partie ne peut pas être supprimé.
Type d'ouverture du fichier | Type de verrouillage | Utilisateurs simultanés |
---|---|---|
Partagé | Chaque utilisateur peut verrouiller une partie du fichier | Plusieurs possibles |
Exclusif | Un seul utilisateur verrouille l'ensemble du fichier | Un seul |
Les bases de données Access sont ouvertes en mode exclusif ou partagé.
Les fichiers de verrouillage Access sont ouverts en mode partagé.
II-D-2. Verrouillage des fichiers ldb et accdb▲
À chaque connexion, le système vérifie si une connexion est disponible.
Un verrou est alors posé pour cette connexion.
Lorsqu'un utilisateur quitte la base de données, le verrou utilisé est alors supprimé et la connexion est réutilisable.
On voit sur ce diagramme que la connexion de l'utilisateur 2 est réutilisée par l'utilisateur 4.
Pourtant la deuxième "ligne" du fichier était déjà renseignée avec les données de l'utilisateur 2, mais le verrou (qui n'est pas visible) a été supprimé.
III. Lire et filtrer le fichier de verrouillage▲
Dans un premier temps nous allons lire le contenu d'un fichier de verrouillage.
Ensuite nous verrons comment filtrer la liste des utilisateurs pour ne garder que ceux qui sont connectés.
III-A. Lire le contenu du fichier▲
On pourrait lire le fichier de verrouillage grâce aux fonctions VBA.
Voici un exemple de code pour illustrer cette éventualité.
Sub
ReadLDB
(
)
Dim
iFile As
Integer
Dim
sComputer As
String
Dim
sUser As
String
Dim
cptUser As
Long
iFile =
FreeFile
Open "C:\MaBase.ldb"
For
Input As
iFile
For
cptUser =
1
To
LOF
(
iFile) /
64
sComputer =
RTrim
(
Input
(
31
, iFile))
sUser =
RTrim
(
Input
(
31
, iFile))
Debug.Print
sComputer &
":"
&
sUser
Next
Close iFile
End
Function
Chaque entrée dans le fichier contient l'ordinateur et l'utilisateur, chacun sur trente-deux caractères.
Notez qu'on ne lit que trente-et-un caractères. Le trente-deuxième est un caractère Null que l'instruction Input ignore.
Lien vers un tutoriel : Manipulation des fichiers en VBAManipulation des fichiers en VBA
Nous allons plutôt utiliser des API pour lire le fichier, car elles nous seront également utiles pour lire les verrous.
Ces API sont des fonctions contenues dans la bibliothèque kernel32.dll.
Voici leurs déclarations, ainsi que les constantes dont nous avons besoin :
Private
Declare
Function
ReadFile Lib
"kernel32"
(
ByVal
hFile As
Long
, lpBuffer As
Any, _
ByVal
nNumberOfBytesToRead As
Long
, lpNumberOfBytesRead As
Long
, lpOverlapped As
Any) As
Long
Private
Declare
Function
CreateFile Lib
"kernel32"
Alias "CreateFileA"
(
ByVal
lpFileName As
String
, _
ByVal
dwDesiredAccess As
Long
, ByVal
dwShareMode As
Long
, lpSecurityAttributes As
Any, _
ByVal
dwCreationDisposition As
Long
, ByVal
dwFlagsAndAttributes As
Long
, _
ByVal
hTemplateFile As
Long
) As
Long
Private
Declare
Function
CloseHandle Lib
"kernel32"
(
ByVal
hObject As
Long
) As
Long
Private
Declare
Function
GetFileSize Lib
"kernel32"
(
ByVal
hFile As
Long
, lpFileSizeHigh As
Long
) As
Long
Private
Declare
Function
LockFile Lib
"kernel32"
(
ByVal
hFile As
Long
, ByVal
dwFileOffsetLow As
Long
, _
ByVal
dwFileOffsetHigh As
Long
, ByVal
nNumberOfBytesToLockLow As
Long
, _
ByVal
nNumberOfBytesToLockHigh As
Long
) As
Long
Private
Declare
Function
UnlockFile Lib
"kernel32"
(
ByVal
hFile As
Long
, ByVal
dwFileOffsetLow As
Long
, _
ByVal
dwFileOffsetHigh As
Long
, ByVal
nNumberOfBytesToUnlockLow As
Long
, _
ByVal
nNumberOfBytesToUnlockHigh As
Long
) As
Long
Private
Declare
Function
SetFilePointer Lib
"kernel32"
(
ByVal
hFile As
Long
, ByVal
lDistanceToMove As
Long
, _
lpDistanceToMoveHigh As
Long
, ByVal
dwMoveMethod As
Long
) As
Long
Private
Const
GENERIC_READ =
&
H80000000 ' Ouverture en lecture
Private
Const
GENERIC_WRITE =
&
H40000000 ' Ouverture en écriture
Private
Const
FILE_SHARE_READ =
&
H1 ' Lecture partagée
Private
Const
FILE_SHARE_WRITE =
&
H2 ' Écriture partagée
Private
Const
OPEN_EXISTING =
3
' Le fichier doit exister
Private
Const
DEBUT_LOCK =
&
H10000001 ' Adresse de début des locks
Pour simplifier, le code est limité aux versions 32 bits d'Office.
Pour une version 64 bits d'Office, voir cet article : Développer avec Office 64 bitsDévelopper avec Office 64 bits.
Les modules de l'application à télécharger sont compatibles avec Office 64 bits.
Retrouvez la documentation de ces fonctions sur MSDN : File Management FunctionsFile Management Functions.
Voici une procédure qui utilise les API déclarées précédemment pour lire le contenu du fichier de verrous :
Sub
ReadLDB
(
)
Dim
hFile As
Long
Dim
lReturn As
Long
Dim
lBytesRead As
Long
Dim
sComputer As
String
Dim
sUser As
String
' Ouverture fichier en mode partagé
hFile =
CreateFile
(
ByVal
"C:\Article_Dvp\documents\utilisateurs-ldb\work\ldbviewer - Copie.ldb"
, _
ByVal
GENERIC_READ Or
GENERIC_WRITE, _
ByVal
FILE_SHARE_READ Or
FILE_SHARE_WRITE, _
ByVal
0
&
, ByVal
OPEN_EXISTING, ByVal
0
&
, ByVal
0
&
)
' Si -1 => erreur
If
hFile <>
-
1
Then
' Boucle pour lire le contenu du fichier
Do
' Initialise la zone qui reçoit le nom de l'ordinateur
sComputer =
Space
(
32
)
' Lecture de 32 caractères
lReturn =
ReadFile
(
hFile, ByVal
sComputer, 32
, lBytesRead, ByVal
0
&
)
' Si erreur ou plus de données => on sort
If
lReturn =
0
Or
lBytesRead =
0
Then
Exit
Do
' Retire le Null et les espaces à droite
sComputer =
Left
(
sComputer, InStr
(
sComputer, Chr
(
0
)) -
1
)
' Initialise la zone qui reçoit le nom de l'utilisateur
sUser =
Space
(
32
)
' Lecture de 32 caractères
lReturn =
ReadFile
(
hFile, ByVal
sUser, 32
, lBytesRead, ByVal
0
&
)
' Retire le Null et les espaces à droite
sUser =
Left
(
sUser, InStr
(
sUser, Chr
(
0
)) -
1
)
' Si erreur ou plus de données => on sort
If
lReturn =
0
Or
lBytesRead =
0
Then
Exit
Do
' Écriture dans fenêtre exécution
Debug.Print
sComputer &
":"
&
sUser
Loop
' Fermeture du fichier
CloseHandle hFile
End
If
End
Function
Il nous reste à filtrer les connexions non actives.
III-B. Filtrer le contenu du fichier grâce aux verrous▲
Les verrous sont posés par Access à un emplacement défini à &H10000001 (en hexadécimal), c'est-à-dire au-delà du fichier physique.
Cf. Understanding Microsoft Jet Locking White Paper pour plus d'explication (en anglais).
Cet emplacement a été défini précédemment par la constante DEBUT_LOCK.
La première connexion a donc son verrou à DEBUT_LOCK.
La deuxième connexion à DEBUT_LOCK + 1.
Il peut y avoir 255 connexions au maximum.
Windows peut placer un verrou à une position supérieure à la taille de fichier.
Cela ne verrouillera pas un endroit spécifique du fichier.
Imaginez un catalogue de verrous liés à un fichier, et non pas un verrou posé "sur" ce fichier.
En fait il n'est pas possible de lire l'état d'un verrou.
Il faut tenter d'en poser un : si cela retourne une erreur c'est qu'il y a déjà un verrou.
Voici la procédure avec l'ajout de la tentative de pose de verrou pour savoir si la connexion est active.
Function
ReadLDB
(
)
Dim
hFile As
Long
Dim
lReturn As
Long
Dim
lBytesRead As
Long
Dim
sComputer As
String
Dim
sUser As
String
Dim
lNbUser As
Long
' Ouverture fichier en mode partagé
hFile =
CreateFile
(
ByVal
"C:\Article_Dvp\documents\utilisateurs-ldb\work\ldbviewer.ldb"
, _
ByVal
GENERIC_READ Or
GENERIC_WRITE, _
ByVal
FILE_SHARE_READ Or
FILE_SHARE_WRITE, _
ByVal
0
&
, ByVal
OPEN_EXISTING, ByVal
0
&
, ByVal
0
&
)
' Si -1 => erreur
If
hFile <>
-
1
Then
' Boucle pour lire le contenu du fichier
Do
lNbUser =
lNbUser +
1
' Initialise la zone qui reçoit le nom de l'ordinateur
sComputer =
Space
(
32
)
' Lecture de 32 caractères
lReturn =
ReadFile
(
hFile, ByVal
sComputer, 32
, lBytesRead, ByVal
0
&
)
' Si erreur ou plus de données => on sort
If
lReturn =
0
Or
lBytesRead =
0
Then
Exit
Do
' Retire le Null et les espaces à droite
sComputer =
Left
(
sComputer, InStr
(
sComputer, Chr
(
0
)) -
1
)
' Initialise la zone qui reçoit le nom de l'utilisateur
sUser =
Space
(
32
)
' Lecture de 32 caractères
lReturn =
ReadFile
(
hFile, ByVal
sUser, 32
, lBytesRead, ByVal
0
&
)
' Retire le Null et les espaces à droite
sUser =
Left
(
sUser, InStr
(
sUser, Chr
(
0
)) -
1
)
' Si erreur ou plus de données => on sort
If
lReturn =
0
Or
lBytesRead =
0
Then
Exit
Do
' Test le lock pour savoir si l'utilisateur est toujours connecté
If
LockFile
(
hFile, DEBUT_LOCK +
lNbUser -
1
, 0
, 1
, 0
) =
0
Then
' Erreur de lock => utilisateur connecté
' Écriture dans fenêtre exécution
Debug.Print
sComputer &
":"
&
sUser
Else
' Lock réussi => on "unlock"
Call
UnlockFile
(
hFile, DEBUT_LOCK +
lNbUser -
1
, 0
, 1
, 1
)
End
If
Loop
' Fermeture du fichier
CloseHandle hFile
End
If
End
Function
On a dû rajouter un compteur lNbUser qui permet d'incrémenter la position du verrou à tester.
Ne pas oublier de déverrouiller si la tentative de pose de verrou aboutit.
Cependant, les verrous posés sont supprimés lorsque le fichier hFile est fermé par CloseHandle.
IV. Cas particulier du mode exclusif▲
Si la base est ouverte en mode exclusif, aucun fichier de verrouillage n'est créé.
Il est possible de détecter qu'une base est ouverte en mode exclusif en essayant de l'ouvrir... en mode exclusif.
Function
isExclusive
(
pDataBase As
String
) As
Boolean
Dim
f As
Integer
On
Error
GoTo
Gestion_Erreur
' Cherche un identifiant fichier libre
f =
FreeFile
' Tente d'ouvrir la base de données en lecture exclusive
Open pDataBase For
Binary Access Read Lock Read As
#f
' Ferme le fichier s'il a été ouvert
Close f
Exit
Function
Gestion_Erreur
:
' Erreur 70 (Permission refusée) si base déjà ouverte en exclusif
If
Err
.Number
=
70
Then
isExclusive =
True
End
Function
Dans la fonction ci-dessus, l'instruction Open lève une erreur si la base (dont le chemin est donné dans le paramètre pDatabase) est déjà ouverte en mode exclusif.
Il est possible de faire la même tentative d'ouverture avec les API : cf. les modules de l'application à télécharger pour un exemple.
V. Cas de la fermeture brutale d'Access▲
Ce mécanisme de verrou a un avantage non négligeable : le verrou ne survit pas à la fermeture du processus (donc de l'application) qui l'a posé.
C'est-à-dire que la connexion est rendue disponible car il n'y a plus de verrou dessus.
Notez que le fichier ldb (ou accdb) n'est pas supprimé lors de la déconnexion brutale du dernier utilisateur.
Comme il n'y a plus de verrou sur le fichier, il est possible (mais pas indispensable) de le supprimer manuellement.
VI. Verrouiller les connexions▲
VI-A. En posant des verrous sur le fichier ldb ou accdb▲
Ce paragraphe et notamment le code qui s'y trouve n'est pas prévu pour fonctionner en production.
Ce n'est qu'un exemple pour illustrer le mécanisme de verrou.
Petite astuce si vous souhaitez empêcher toute nouvelle connexion à la base de données :
Posez un verrou avec LockFile sur chacune des 255 connexions.
Cela nécessite de conserver un pointeur vers le fichier (le hFile obtenu avec CreateFile) pour que les verrous perdurent.
Voici un module pour exemple :
Option
Compare Database
Option
Explicit
Private
Declare
Function
ReadFile Lib
"kernel32"
(
ByVal
hFile As
Long
, lpBuffer As
Any, _
ByVal
nNumberOfBytesToRead As
Long
, lpNumberOfBytesRead As
Long
, lpOverlapped As
Any) As
Long
Private
Declare
Function
CreateFile Lib
"kernel32"
Alias "CreateFileA"
(
ByVal
lpFileName As
String
, _
ByVal
dwDesiredAccess As
Long
, ByVal
dwShareMode As
Long
, lpSecurityAttributes As
Any, _
ByVal
dwCreationDisposition As
Long
, ByVal
dwFlagsAndAttributes As
Long
, _
ByVal
hTemplateFile As
Long
) As
Long
Private
Declare
Function
CloseHandle Lib
"kernel32"
(
ByVal
hObject As
Long
) As
Long
Private
Declare
Function
GetFileSize Lib
"kernel32"
(
ByVal
hFile As
Long
, lpFileSizeHigh As
Long
) As
Long
Private
Declare
Function
LockFile Lib
"kernel32"
(
ByVal
hFile As
Long
, ByVal
dwFileOffsetLow As
Long
, _
ByVal
dwFileOffsetHigh As
Long
, ByVal
nNumberOfBytesToLockLow As
Long
, _
ByVal
nNumberOfBytesToLockHigh As
Long
) As
Long
Private
Declare
Function
UnlockFile Lib
"kernel32"
(
ByVal
hFile As
Long
, ByVal
dwFileOffsetLow As
Long
, _
ByVal
dwFileOffsetHigh As
Long
, ByVal
nNumberOfBytesToUnlockLow As
Long
, _
ByVal
nNumberOfBytesToUnlockHigh As
Long
) As
Long
Private
Declare
Function
SetFilePointer Lib
"kernel32"
(
ByVal
hFile As
Long
, ByVal
lDistanceToMove As
Long
, _
lpDistanceToMoveHigh As
Long
, ByVal
dwMoveMethod As
Long
) As
Long
Private
Const
GENERIC_READ =
&
H80000000 ' Ouverture en lecture
Private
Const
GENERIC_WRITE =
&
H40000000 ' Ouverture en écriture
Private
Const
FILE_SHARE_READ =
&
H1 ' Lecture partagée
Private
Const
FILE_SHARE_WRITE =
&
H2 ' Écriture partagée
Private
Const
OPEN_EXISTING =
3
' Le fichier doit exister
Private
Const
DEBUT_LOCK =
&
H10000001 ' Adresse de début des locks
Private
hFile As
Long
Public
Sub
LockDb
(
pFile As
String
)
Dim
lNbUser As
Long
Dim
lNbOK As
Long
Dim
lNbKO As
Long
' Ouverture fichier en mode partagé
hFile =
CreateFile
(
ByVal
pFile, _
ByVal
GENERIC_READ Or
GENERIC_WRITE, _
ByVal
FILE_SHARE_READ Or
FILE_SHARE_WRITE, _
ByVal
0
&
, ByVal
OPEN_EXISTING, ByVal
0
&
, ByVal
0
&
)
' Si -1 => erreur
If
hFile <>
-
1
Then
' Boucle sur les 255 verrous
For
lNbUser =
1
To
255
' Pose d'un verrou
If
LockFile
(
hFile, DEBUT_LOCK +
lNbUser -
1
, 0
, 1
, 0
) =
0
Then
' Connexion déjà utilisée => pose de verrou impossible
lNbKO =
lNbKO +
1
Else
' Pose de verrou réussie
lNbOK =
lNbOK +
1
End
If
Next
End
If
MsgBox
lNbOK &
" connexions verrouillées"
&
vbCrLf
&
_
lNbKO &
" connexions en utilisation"
End
Sub
Public
Sub
UnlockDb
(
)
Dim
lNbUser As
Long
Dim
lNbOK As
Long
Dim
lNbKO As
Long
' Si -1 => erreur
If
hFile <>
-
1
And
hFile <>
0
Then
' Boucle sur les 255 verrous
For
lNbUser =
1
To
255
' Retire un verrou
If
UnlockFile
(
hFile, DEBUT_LOCK +
lNbUser -
1
, 0
, 1
, 1
) =
0
Then
' Connexion déjà utilisée par un autre process => retrait de verrou impossible
lNbKO =
lNbKO +
1
Else
' Retrait de verrou réussi
lNbOK =
lNbOK +
1
End
If
Next
End
If
' Libère le fichier
CloseHandle hFile
hFile =
0
MsgBox
lNbOK &
" connexions déverrouillées"
&
vbCrLf
&
_
lNbKO &
" connexions en utilisation"
End
Sub
Verrouillez les connexions avec la procédure LockDb :
LockDb "C:\MaBase.ldb"
Mettez en paramètre le fichier ldb ou accdb.
Notez que seules les connexions libres peuvent être verrouillées.
Après verrouillage de toutes les connexions, un message apparait à l'ouverture de la base de données comme s'il y avait 255 utilisateurs connectés.
Exécutez la procédure UnLockDb pour déverrouiller les connexions.
Il faut un fichier ldb ou accdb pour verrouiller une base de données.
Si aucun utilisateur n'est connecté, on peut créer un fichier texte vide nommé NomDeLaBase.ldb ou NomDeLaBase.accdb et y poser des verrous.
Les verrous posés sont retirés lorsqu'on ferme l'application qui les a créés, même si on n'exécute pas la fonction UnlockFile.
VI-B. Avec ADO et Connection Control▲
Beaucoup plus simple (à partir d'Access 2000), ouvrez la base de données à verrouiller et exécutez ce code :
CurrentProject.Connection.Properties
(
"Jet OLEDB:Connection Control"
) =
1
La base de données est alors verrouillée pour les nouvelles connexions, tant que vous ne fermez pas la base de données d'où vous avez exécuté ce code.
Les utilisateurs déjà connectés conservent leur connexion jusqu'à sa fermeture.
Un message apparait à l'ouverture de la base de données :
Pour déverrouiller la base de données, exécutez le même code avec la valeur 2 au lieu de 1.
Source : Connection ControlConnection Control .
VII. Application à télécharger▲
Voici une application qui utilise la technique expliquée dans cet article.
clLDBUser est un module qui contient les données des utilisateurs.
clLDBViewer est le module qui contient le code permettant de lister les utilisateurs connectés.
Le formulaire FLdbViewer utilise ces deux modules au sein d'une interface.
Vous pouvez réutiliser les modules de code clLDBUser et clLDBViewer.
Télécharger l'application au format Access 2000
VIII. Autres possibilités pour lister les utilisateurs▲
La méthode OpenSchema de la bibliothèque ADO permet de lire les connexions d'une base de données.
Lien MSDN : Use ADO to Return a List of Users Connected to a DatabaseUse ADO to Return a List of Users Connected to a Database.
Si vous avez besoin d'un outil prêt à l'emploi, utilisez cette visionneuse d'argyronetargyronet :
Visionneuse des Utilisateurs d'une base de données AccessVisionneuse des Utilisateurs d'une base de données Access.
IX. Remerciements▲
Merci à Domi2 et Pierre Fauconnier pour leurs remarques.
Merci à ClaudeLELOUP pour sa relecture orthographique.