đŸŸ

Saverglass — PrĂ©pa Entretien

SystÚme de Gestion de Production · Delphi + Oracle

Delphi 12 Athens Oracle 19c ADO/FireDAC 3-Tier Architecture ⭐ GitHub
🏭 Le Projet
⚡ Delphi Fondamentaux
đŸ—„ïž Oracle & SQL
đŸ’Ÿ Persistance
🎯 Quiz Entretien
📋 Cheat Sheet

🏭 Projet : GlassTrack — Gestion de Production Saverglass

Application de suivi de production de flacons en verre pour une verrerie industrielle. Gestion des commandes, lots de fabrication, contrÎle qualité et stocks.

GlassTrack.exe — Delphi 12 Athens · Windows 32 bits
GlassTrack — Interface de gestion des commandes
Interface réelle · Gestion des Commandes avec filtrage par statut · Mode démonstration sans Oracle

Contexte métier

Saverglass fabrique des flacons de luxe (parfums, spiritueux, vins). Chaque flacon passe par : Conception → Moule → Fusion → Formage → ContrĂŽle qualitĂ© → ExpĂ©dition.

đŸ–„ïž Couche PrĂ©sentation
Forms Delphi — TForm, TGrid, TDBGrid, TPanel
frmCommandes.pas / frmProduction.pas
↕ Appels mĂ©thodes
⚙ Couche MĂ©tier (BLL)
Classes Pascal — TCommande, TLotProduction, TFlacon
uCommande.pas / uProduction.pas
↕ CRUD
🔌 Couche AccĂšs DonnĂ©es (DAL)
DataModules — TFDConnection, TFDQuery, TFDTable
dmDatabase.pas / dmCommandes.pas
↕ OCI / FireDAC
đŸ—„ïž Oracle 19c
Tables, Séquences, Triggers, Packages PL/SQL
SAVERGLASS schema

📊 SchĂ©ma de base de donnĂ©es

CLIENTS
🔑 CLIENT_IDNUMBER(10)
NOMVARCHAR2(100)
PAYSVARCHAR2(50)
SEGMENTVARCHAR2(20)
COMMANDES
🔑 CMD_IDNUMBER(10)
🔗 CLIENT_IDNUMBER(10)
DATE_CMDDATE
STATUTVARCHAR2(20)
QUANTITENUMBER(8)
FLACONS
🔑 FLACON_IDNUMBER(10)
REFERENCEVARCHAR2(30)
CONTENANCENUMBER(6,2)
COULEURVARCHAR2(20)
POIDSNUMBER(8,2)
LOTS_PRODUCTION
🔑 LOT_IDNUMBER(10)
🔗 CMD_IDNUMBER(10)
🔗 FLACON_IDNUMBER(10)
DATE_DEBUTDATE
FOUR_IDNUMBER(4)
QTE_PRODUITENUMBER(8)
TAUX_CASSENUMBER(5,2)
CONTROLE_QUALITE
🔑 CQ_IDNUMBER(10)
🔗 LOT_IDNUMBER(10)
DATE_CTRLDATE
RESULTATVARCHAR2(10)
NB_DEFAUTSNUMBER(6)
Les relations : un CLIENT a plusieurs COMMANDES → chaque COMMANDE gĂ©nĂšre un ou plusieurs LOTS → chaque LOT a un CONTRÔLE QUALITÉ.

⚡ Structure d'une application Delphi

Delphi (Object Pascal) est un langage orientĂ© objet basĂ© sur Pascal. Tout tourne autour des Forms (fenĂȘtres) et des composants visuels.

Fichiers clés d'un projet

structure projet delphi
MonProjet.dproj // Fichier projet principal MonProjet.dpr // Programme principal frmPrincipal.pas // Code de la fenĂȘtre principale frmPrincipal.dfm // DĂ©finition visuelle (composants) dmDatabase.pas // DataModule (accĂšs donnĂ©es) uCommande.pas // UnitĂ© mĂ©tier (classe TCommande)

Le fichier .dpr — point d'entrĂ©e

object pascal — .dpr
program GlassTrack; uses Vcl.Forms, frmPrincipal in 'frmPrincipal.pas' {frmMain}, dmDatabase in 'dmDatabase.pas' {DataModule1: TDataModule}; {$R *.res} begin Application.Initialize; Application.MainFormOnTaskbar := True; Application.CreateForm(TDataModule1, DataModule1); // DataModule en 1er ! Application.CreateForm(TfrmMain, frmMain); Application.Run; end.

Une Form — structure typique

object pascal — frmCommandes.pas
unit frmCommandes; interface uses Winapi.Windows, Vcl.Forms, Vcl.Controls, Vcl.StdCtrls, Vcl.DBGrids, Data.DB, FireDAC.Comp.Client; type TfrmCommandes = class(TForm) // Composants dĂ©clarĂ©s ici (glisser-dĂ©poser dans l'IDE) DBGrid1: TDBGrid; // Grille liĂ©e aux donnĂ©es btnAjouter: TButton; btnSupprimer: TButton; edtRecherche: TEdit; lblStatut: TLabel; // Gestionnaires d'Ă©vĂ©nements (gĂ©nĂ©rĂ©s par l'IDE) procedure btnAjouterClick(Sender: TObject); procedure btnSupprimerClick(Sender: TObject); procedure FormCreate(Sender: TObject); procedure edtRechercheChange(Sender: TObject); private FCommandeID: Integer; // Variables internes procedure ChargerCommandes; procedure ValiderSaisie; public property CommandeID: Integer read FCommandeID; end; var frmCommandes: TfrmCommandes; implementation {$R *.dfm} // Lie le fichier visuel .dfm procedure TfrmCommandes.FormCreate(Sender: TObject); begin ChargerCommandes; lblStatut.Caption := 'PrĂȘt'; end; procedure TfrmCommandes.btnAjouterClick(Sender: TObject); begin if not ValiderSaisie then Exit; DataModule1.qryCommandes.Append; // Nouvelle ligne DataModule1.qryCommandes.FieldByName('STATUT').AsString := 'EN_COURS'; DataModule1.qryCommandes.FieldByName('DATE_CMD').AsDateTime := Now; DataModule1.qryCommandes.Post; // Valide en mĂ©moire DataModule1.ApplyUpdates; // Envoie Ă  Oracle end;

🔑 Concepts clĂ©s Object Pascal

Types fondamentaux

// Scalaires var n: Integer; // 32 bits d: Double; // flottant s: String; // chaĂźne Unicode b: Boolean; // True/False dt: TDateTime; // date+heure // Tableaux var arr: array of String; begin SetLength(arr, 5); arr[0] := 'Flacon 75cl'; end;

Exceptions

try DataModule1.Connexion.StartTransaction; // ... opérations Oracle ... DataModule1.Connexion.Commit; except on E: EFDException do begin DataModule1.Connexion.Rollback; ShowMessage('Erreur Oracle: ' + E.Message); end; end;

đŸ—ïž Classes & HĂ©ritage

type // Classe de base TArticle = class private FId: Integer; FRef: String; public constructor Create(aId: Integer); destructor Destroy; override; function ToString: String; virtual; property Id: Integer read FId; property Reference: String read FRef write FRef; end; // Classe dérivée TFlacon = class(TArticle) private FContenance: Double; FCouleur: String; public function ToString: String; override; property Contenance: Double read FContenance write FContenance; end; implementation constructor TArticle.Create(aId: Integer); begin inherited Create; FId := aId; end; function TFlacon.ToString: String; begin Result := Format('Flacon %s - %.0fcl', [Reference, FContenance]); end;
En Delphi, il faut toujours libĂ©rer les objets créés avec Free — pas de garbage collector !

đŸ—„ïž CrĂ©ation des tables Oracle

sql — oracle ddl
-- Séquence pour les IDs (Oracle < 12c) CREATE SEQUENCE SEQ_COMMANDE START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE; CREATE TABLE CLIENTS ( CLIENT_ID NUMBER(10) GENERATED ALWAYS AS IDENTITY, -- Oracle 12c+ NOM VARCHAR2(100) NOT NULL, PAYS VARCHAR2(50), SEGMENT VARCHAR2(20) CHECK (SEGMENT IN ('LUXE','PREMIUM','STANDARD')), CONSTRAINT PK_CLIENT PRIMARY KEY (CLIENT_ID) ); CREATE TABLE COMMANDES ( CMD_ID NUMBER(10) PRIMARY KEY, CLIENT_ID NUMBER(10) NOT NULL, DATE_CMD DATE DEFAULT SYSDATE NOT NULL, STATUT VARCHAR2(20) DEFAULT 'EN_ATTENTE', QUANTITE NUMBER(8) NOT NULL, CONSTRAINT FK_CMD_CLIENT FOREIGN KEY (CLIENT_ID) REFERENCES CLIENTS(CLIENT_ID), CONSTRAINT CHK_STATUT CHECK (STATUT IN ('EN_ATTENTE','EN_COURS','LIVREE','ANNULEE')) ); -- Trigger pour auto-incrément (style Oracle classique) CREATE OR REPLACE TRIGGER TRG_COMMANDE_ID BEFORE INSERT ON COMMANDES FOR EACH ROW BEGIN IF :NEW.CMD_ID IS NULL THEN :NEW.CMD_ID := SEQ_COMMANDE.NEXTVAL; END IF; END;

📩 Package PL/SQL — MĂ©tier Production

pl/sql — package
CREATE OR REPLACE PACKAGE PKG_PRODUCTION AS -- Crée un lot de production, retourne l'ID FUNCTION CREER_LOT( p_cmd_id NUMBER, p_flacon_id NUMBER, p_four_id NUMBER ) RETURN NUMBER; -- Met à jour le résultat du contrÎle qualité PROCEDURE VALIDER_QUALITE( p_lot_id NUMBER, p_resultat VARCHAR2, p_defauts NUMBER DEFAULT 0 ); -- Calcule le taux de casse moyen d'un four FUNCTION TAUX_CASSE_FOUR(p_four_id NUMBER) RETURN NUMBER; END PKG_PRODUCTION; / CREATE OR REPLACE PACKAGE BODY PKG_PRODUCTION AS FUNCTION CREER_LOT(p_cmd_id NUMBER, p_flacon_id NUMBER, p_four_id NUMBER) RETURN NUMBER IS v_lot_id NUMBER; BEGIN INSERT INTO LOTS_PRODUCTION (CMD_ID, FLACON_ID, FOUR_ID, DATE_DEBUT) VALUES (p_cmd_id, p_flacon_id, p_four_id, SYSDATE) RETURNING LOT_ID INTO v_lot_id; COMMIT; RETURN v_lot_id; EXCEPTION WHEN OTHERS THEN ROLLBACK; RAISE_APPLICATION_ERROR(-20001, 'Erreur création lot: ' || SQLERRM); END; FUNCTION TAUX_CASSE_FOUR(p_four_id NUMBER) RETURN NUMBER IS v_taux NUMBER; BEGIN SELECT AVG(TAUX_CASSE) INTO v_taux FROM LOTS_PRODUCTION WHERE FOUR_ID = p_four_id AND DATE_DEBUT >= ADD_MONTHS(SYSDATE, -3); RETURN NVL(v_taux, 0); END; END PKG_PRODUCTION;

🔍 RequĂȘtes mĂ©tier importantes

Commandes en retard

SELECT c.CMD_ID, cl.NOM AS CLIENT, c.DATE_CMD, c.QUANTITE, c.STATUT FROM COMMANDES c JOIN CLIENTS cl ON cl.CLIENT_ID = c.CLIENT_ID WHERE c.STATUT IN ('EN_ATTENTE','EN_COURS') AND c.DATE_CMD < SYSDATE - 30 ORDER BY c.DATE_CMD;

Tableau de bord production

SELECT f.FOUR_ID, COUNT(*) AS NB_LOTS, SUM(QTE_PRODUITE) AS TOTAL, ROUND(AVG(TAUX_CASSE),2) AS CASSE_MOY, SUM(CASE WHEN cq.RESULTAT='OK' THEN 1 ELSE 0 END ) AS LOTS_OK FROM LOTS_PRODUCTION f LEFT JOIN CONTROLE_QUALITE cq ON cq.LOT_ID = f.LOT_ID WHERE TRUNC(f.DATE_DEBUT,'MM') = TRUNC(SYSDATE,'MM') GROUP BY f.FOUR_ID ORDER BY TOTAL DESC;

đŸ’Ÿ DataModule — Le cƓur de la persistance

En Delphi, le TDataModule est une form invisible qui regroupe tous les composants d'accÚs aux données. C'est le pattern DAL standard de Delphi.

object pascal — dmDatabase.pas (DataModule)
unit dmDatabase; interface uses System.SysUtils, System.Classes, FireDAC.Comp.Client, // TFDConnection, TFDQuery FireDAC.Stan.Param, // TFDParam FireDAC.Phys.OracleDef,// Driver Oracle Data.DB; // TDataSet, TField type TDataModule1 = class(TDataModule) // Composants posĂ©s dans l'IDE FDConnection1: TFDConnection; // Connexion Oracle qryCommandes: TFDQuery; // SELECT sur COMMANDES qryLots: TFDQuery; // SELECT sur LOTS_PRODUCTION dsCommandes: TDataSource; // Lien Query → Grid FDTransaction1: TFDTransaction; // Gestion transactions procedure DataModuleCreate(Sender: TObject); procedure DataModuleDestroy(Sender: TObject); private procedure ConfigurerConnexion; public procedure ApplyUpdates; function ExecuterProcedure( const aProcedure: String; const aParams: array of Variant ): Variant; end; var DataModule1: TDataModule1; implementation {$R *.dfm} procedure TDataModule1.DataModuleCreate(Sender: TObject); begin ConfigurerConnexion; // Configuration de la query principale qryCommandes.Connection := FDConnection1; qryCommandes.SQL.Text := 'SELECT c.CMD_ID, c.DATE_CMD, c.STATUT, c.QUANTITE, ' + ' cl.NOM AS CLIENT_NOM ' + 'FROM COMMANDES c ' + 'JOIN CLIENTS cl ON cl.CLIENT_ID = c.CLIENT_ID ' + 'WHERE (:statut IS NULL OR c.STATUT = :statut) ' + 'ORDER BY c.DATE_CMD DESC'; qryCommandes.ParamByName('statut').Clear; // = tous qryCommandes.Open; dsCommandes.DataSet := qryCommandes; end; procedure TDataModule1.ConfigurerConnexion; begin with FDConnection1 do begin DriverName := 'Oracle'; Params.Clear; Params.Add('DriverID=Oracle'); Params.Add('Server=SAVERGLASS_DB'); // TNS alias Params.Add('Database=ORCL'); Params.Add('User_Name=GLASSTRACK'); Params.Add('Password=xxxx'); Params.Add('CharacterSet=AL32UTF8'); LoginPrompt := False; Connected := True; end; end; // Appel d'une procĂ©dure/fonction PL/SQL function TDataModule1.ExecuterProcedure( const aProcedure: String; const aParams: array of Variant ): Variant; var qry: TFDQuery; i: Integer; begin qry := TFDQuery.Create(nil); try qry.Connection := FDConnection1; qry.SQL.Text := 'BEGIN :result := ' + aProcedure + '('; for i := 0 to High(aParams) do qry.SQL.Add(':p' + IntToStr(i) + if i<High(aParams) then ',' else ''); qry.SQL.Add('); END;'); qry.Params[0].ParamType := ptOutput; for i := 1 to Length(aParams) do qry.Params[i].Value := aParams[i-1]; qry.ExecSQL; Result := qry.Params[0].Value; finally qry.Free; end; end;

🔄 Pattern CRUD complet

object pascal — uCommandeDAO.pas
unit uCommandeDAO; // DAO = Data Access Object — sĂ©paration logique mĂ©tier / accĂšs donnĂ©es type TCommandeDAO = class private FConn: TFDConnection; public constructor Create(aConn: TFDConnection); // READ — retourne toutes les commandes filtrĂ©es function GetAll(aStatut: String = ''): TFDQuery; // CREATE function Inserer( aClientID: Integer; aQuantite: Integer ): Integer; // retourne le nouvel ID // UPDATE procedure MAJStatut(aCmdID: Integer; aStatut: String); // DELETE procedure Supprimer(aCmdID: Integer); end; function TCommandeDAO.Inserer(aClientID, aQuantite: Integer): Integer; var qry: TFDQuery; begin qry := TFDQuery.Create(nil); try qry.Connection := FConn; qry.SQL.Text := 'INSERT INTO COMMANDES (CLIENT_ID, QUANTITE, STATUT) ' + 'VALUES (:cid, :qty, ''EN_ATTENTE'') ' + 'RETURNING CMD_ID INTO :newid'; // RETURNING = astuce Oracle qry.ParamByName('cid').AsInteger := aClientID; qry.ParamByName('qty').AsInteger := aQuantite; qry.ParamByName('newid').ParamType := ptOutput; qry.ParamByName('newid').DataType := ftInteger; qry.ExecSQL; Result := qry.ParamByName('newid').AsInteger; finally qry.Free; end; end; procedure TCommandeDAO.MAJStatut(aCmdID: Integer; aStatut: String); var qry: TFDQuery; begin qry := TFDQuery.Create(nil); try qry.Connection := FConn; qry.SQL.Text := 'UPDATE COMMANDES SET STATUT = :st WHERE CMD_ID = :id'; qry.ParamByName('st').AsString := aStatut; qry.ParamByName('id').AsInteger := aCmdID; qry.ExecSQL; finally qry.Free; end; end;
Toujours utiliser des paramĂštres liĂ©s (:param) et jamais concatĂ©ner des valeurs dans le SQL — protection contre les injections SQL et meilleures performances (plan de requĂȘte rĂ©utilisĂ© par Oracle).

🔗 Composants de liaison donnĂ©es → UI

TDataSource — le mĂ©diateur

Le TDataSource fait le pont entre un TDataSet (query) et les composants visuels DB-aware :

// Dans le DataModule : dsCommandes.DataSet := qryCommandes; // Dans la Form (cĂŽtĂ© UI) : DBGrid1.DataSource := DataModule1.dsCommandes; DBEdit1.DataSource := DataModule1.dsCommandes; DBEdit1.DataField := 'CLIENT_NOM'; // → DBEdit1 affiche/Ă©dite automatiquement // le champ CLIENT_NOM de la ligne courante !

Navigation dans un TDataSet

with DataModule1.qryCommandes do begin First; // 1Úre ligne Next; // ligne suivante Prior; // ligne précédente Last; // derniÚre // Itérer toutes les lignes First; while not EOF do begin total += FieldByName('QUANTITE').AsInteger; Next; end; // Lire un champ id := FieldByName('CMD_ID').AsInteger; nom := FieldByName('CLIENT_NOM').AsString; dt := FieldByName('DATE_CMD').AsDateTime; end;

🎯 Questions d'entretien — Testez-vous !

Questions typiques pour un poste Delphi/Oracle chez Saverglass.

Score : 0 / 0

1. Qu'est-ce qu'un TDataModule en Delphi ?

Le TDataModule est une form invisible qui regroupe les composants DAL : TFDConnection, TFDQuery, TDataSource
 Il est instancié avant toutes les forms dans le .dpr et partagé dans toute l'application via la variable globale DataModule1.

2. Quelle est la différence entre .Post et .ApplyUpdates en FireDAC ?

Post valide l'enregistrement dans le buffer local du TDataSet (passage de dsEdit/dsInsert à dsBrowse). ApplyUpdates envoie les modifications bufférisées vers Oracle via des INSERT/UPDATE/DELETE générés automatiquement. Le COMMIT Oracle reste explicite ou géré par la TFDTransaction.

3. Pourquoi utilise-t-on des paramÚtres liés (:parametre) au lieu de concaténer du SQL ?

Double avantage : SĂ©curitĂ© — les paramĂštres sont typĂ©s et Ă©chappĂ©s, impossible d'injecter du SQL. Performance — Oracle parse et stocke le plan d'exĂ©cution la 1Ăšre fois, les appels suivants rĂ©utilisent ce plan (soft parse vs hard parse). C'est crucial pour un ERP industriel avec des milliers d'appels/heure.

4. Dans Oracle, qu'est-ce qu'une séquence (SEQUENCE) ?

Une SEQUENCE Oracle est un objet indĂ©pendant qui gĂ©nĂšre des valeurs numĂ©riques uniques (SEQ.NEXTVAL). Contrairement Ă  l'IDENTITY de SQL Server, elle est indĂ©pendante de toute table — plusieurs tables peuvent partager une mĂȘme sĂ©quence. UtilisĂ©e classiquement dans un TRIGGER BEFORE INSERT pour simuler l'auto-incrĂ©ment.

5. Quel composant Delphi fait le lien entre un TFDQuery et un TDBGrid ?

Le TDataSource est le mĂ©diateur indispensable : TFDQuery → TDataSource → TDBGrid/TDBEdit/TDBLabel
. Quand le curseur se dĂ©place dans la query, tous les composants DB-aware liĂ©s au mĂȘme TDataSource se mettent Ă  jour automatiquement. C'est le pattern Observer de Delphi.

6. Qu'est-ce que RETURNING ... INTO en Oracle SQL ?

INSERT INTO ... RETURNING COL INTO :var permet de rĂ©cupĂ©rer l'ID gĂ©nĂ©rĂ© (via trigger/sĂ©quence) immĂ©diatement aprĂšs l'insertion, sans faire un SELECT MAX() ou CURRVAL ensuite — ce qui serait dangereux en accĂšs concurrent. TrĂšs utilisĂ© en Delphi avec FireDAC : qry.ParamByName('newid').ParamType := ptOutput.

⚡ Delphi — MĂ©mo rapide

ÉvĂ©nements courants

OnClickOnChangeOnKeyDown OnCreateOnDestroyOnClose OnShowOnDblClickOnEnter

Composants visuels clés

TFormTPanelTPageControl TDBGridTDBEditTDBComboBox TButtonTLabelTEdit TListViewTTreeViewTStatusBar

Composants FireDAC (données)

TFDConnectionTFDQueryTFDTable TFDTransactionTFDStoredProc TDataSourceTFDMemTable

Raccourcis IDE (Delphi/RAD Studio)

F9 Compiler + ExĂ©cuter Ctrl+F9 Compiler seulement F12 Basculer Form ↔ Code Ctrl+D Formater le code Ctrl+Clic Aller Ă  la dĂ©claration F2 Renommer (refactoring)

đŸ—„ïž Oracle — Fonctions essentielles

-- Dates SYSDATE -- date+heure systĂšme TRUNC(date, 'MM') -- 1er jour du mois ADD_MONTHS(SYSDATE, -3) -- il y a 3 mois TO_CHAR(dt, 'DD/MM/YYYY') -- formatage TO_DATE('01/01/2024','DD/MM/YYYY') -- ChaĂźnes UPPER(NOM) LOWER TRIM LENGTH SUBSTR(NOM, 1, 3) -- 3 premiers chars INSTR(NOM, 'GL') -- position de 'GL' -- Nulls & Conditions NVL(TAUX_CASSE, 0) -- remplace NULL par 0 NVL2(val, 'oui', 'non') -- si non-null/null COALESCE(a, b, c) -- 1er non-null DECODE(STATUT,'OK',1,'KO',0,-1) -- AgrĂ©gats COUNT(*) SUM() AVG() MIN() MAX() LISTAGG(NOM, ', ') -- concatĂ©nation groupĂ©e -- Analytiques (fenĂȘtres) ROW_NUMBER() OVER (PARTITION BY CLIENT_ID ORDER BY DATE_CMD DESC) RANK() / DENSE_RANK() LAG(QTE, 1) OVER (...) -- valeur prĂ©cĂ©dente

💬 Questions frĂ©quentes en entretien — RĂ©ponses clĂ©s

"Expliquez votre approche de la persistance en Delphi"

"J'utilise un DataModule dédié comme couche DAL, avec des composants TFDQuery paramétrés. Pour la logique métier complexe, je délÚgue à des packages PL/SQL cÎté Oracle. Tous les accÚs passent par des paramÚtres liés pour la sécurité et la performance."

"Comment gérez-vous les transactions ?"

"Via TFDTransaction avec StartTransaction / Commit / Rollback dans un bloc try/except. Pour les opérations critiques (ex: créer un lot + mise à jour stock), je regroupe tout en une seule transaction Oracle pour garantir la cohérence ACID."

"Différence TFDQuery vs TFDTable ?"

"TFDTable mappe directement une table Oracle entiÚre, rapide à configurer. TFDQuery est plus flexible : SQL personnalisé, jointures, filtres dynamiques. En production j'utilise surtout TFDQuery pour le contrÎle et la performance."

"Saverglass vous demande d'optimiser une requĂȘte lente ?"

"Je commence par EXPLAIN PLAN pour voir le plan d'exécution. Je vérifie les index manquants, les full table scans inutiles. Je collabore avec le DBA Oracle pour créer les index adaptés et, si nécessaire, je réécris la logique en PL/SQL pour éviter les appels répétés depuis Delphi."