Mixer des procédures et des méthodes
Date de publication : 25/06/2007 , Date de mise à jour : 25/06/2007
Par
Laurent Dardenne (Contributions)
Il est parfois utile de pouvoir manipuler des procédures et/ou des méthodes au sein de traitement partagés. Voyons comment procéder.
Je tiens à remercier XXX pour ses corrections orthographiques et relectures attentives.
1. Public concerné
1-1. Les sources
2. Rappel sur le type procédure
2. Rappel sur le type méthode d'objet
4. Le grand mixe
4-1. Appel d'une méthode en lieu et place d'une procédure
4-2. Appel d'une procédure en lieu et place d'une méthode
5. Conclusion
6. Lien
1. Public concerné
Testé sous Xp et Delphi 2006.
1-1. Les sources
2. Rappel sur le type procédure
Comme nous le dit la documentation de Delphi :
Les types procédure permettent de traiter des procédures et des fonctions comme des valeurs pouvant être affectées à des variables
ou transmises à d'autres procédures ou fonctions.
...
Deux types de procédure sont compatibles si :
- Ils ont la même convention d'appel.
- Ils renvoient le même type de valeur ou pas de valeur.
- Ils ont le même nombre de paramètres, avec le même type aux mêmes positions. Le nom des paramètres est sans importance.
|
Une variable de type procédure peut également ce rapprocher du type pointeur, ces types étant compatible dans les affectations.
Déclarons un type procédure :
type
TProcedureTypee = Procedure (Element : String );
|
On peut dés lors l'utiliser comme paramètre d'une procédure :
procedure Enumere(Collection: TStringDynArray ; Traitement:TProcedureTypee);
var I : Integer ;
begin
for i := Low(Collection) to High(Collection) do
Traitement(Collection[i]);
end ;
|
La procédure de traitement, passée en paramètre, doit respecter la signature du type déclaré :
Procedure AfficheTableau(Element : String );
begin
Writeln(Element);
end ;
|
Bien évidemment ici cette approche semble suivre le principe du pourquoi faire simple quand on peut faire compliquer ?
On peut, dans notre cas, envisager des traitements différents lors de l'itération de notre collection, par exemple l'écrire dans un fichier, sur un port série, etc.
L'appel de notre procédure peut se faire directement en utilisant l'opérateur @ :
var I : Integer ;
tabNom : TStringDynArray;
begin
try
SetLength(tabNom,10 +1 );
For I:=0 to 10 do
tabNom[I]:=Chr(I+65 );
Enumere(tabNom, @AfficheTableau);
Readln;
finally
Finalize(tabNom);
end ;
|
Il reste possible d'utiliser une variable :
var MaProcedure : TProcedureTypee;
begin
...
maProcedure : = @AfficheTableau;
Enumere(tabNom, MaProcedure);
...
|
2. Rappel sur le type méthode d'objet
A la différence du type procédural, le type méthode d'objet se déclare en ajoutant le mot clé of object :
type
TProcedureObjet = Procedure (Element : String ) of object ;
|
On remarquera que nos deux déclaration sont très semblables.
Déclarons notre classe pour héberger notre méthode Affiche:
TUneClasse =Class
private
FProcedure : TProcedureObjet;
public
Procedure Affiche(Element : String );
Procedure Iteration(Collection: TStringDynArray);
property Traitement: TProcedureObjet read FProcedure write FProcedure;
End ;
|
Son implémentation étant la suivante :
procedure TUneClasse.Affiche(Element: String );
begin
Writeln(Element);
end ;
procedure TUneClasse.Iteration(Collection: TStringDynArray);
var
I : Integer ;
begin
if Assigned(Traitement) then
for i := Low(Collection) to High(Collection) do
Traitement(Collection[i]);
end ;
|
On utilisera cette méthode d'objet ainsi :
UnObjet:= TUneClasse.Create;
UnObjet.Traitement:=UnObjet.Affiche;
UnObjet.Iteration(tabNom);
Readln;
|
Notez qu'on peut affecter à la propriété Traitement n'importe quelle méthode de toutes classes, elle doit juste respecter la signature déclarée dans le type.
Jusqu'à maintenant ces 2 approches ne différent pas fonctionnellement.
4. Le grand mixe
Il peut être intéressant de pouvoir manipuler indistinctement ces 2 types.
4-1. Appel d'une méthode en lieu et place d'une procédure
Essayons d'appeler notre procédure Enumere avec une méthode d'objet :
Enumere(tabNom, UnObjet.Affiche);
|
Ici la compilation échoue :
Erreur E2009 : Types incompatibles : 'procédure normale et pointeur de méthode'
|
Un pointeur de méthode d'objet est en fait une paire de pointeurs, le premier stocke l'adresse d'une méthode et le second une référence à l'objet auquel appartient la méthode. En déclarant le type TMethod Delphi autorise la manipulation de ces 2 informations :
type
TMethod = record
Code,
Data : Pointer ;
end ;
|
|
Sous Win32, les types pointeurs de procédures sont toujours incompatibles avec les types pointeurs de méthodes, mais cela n'est pas vrai sur la plate-forme .NET (puisque tout est objet). La valeur nil peut être affectée à tous les types de procédure.
|
Note : Le type TMethod existe bien sous Delphi .NET mais est complétement adaptée à cette plate-forme :
TMethod = record
public
var
Data: TObject;
Code: TMethodCode;
constructor Create(AData: TObject; ACode: TMethodCode); overload ;
constructor Create(AData: TObject; const AName: string ); overload ;
function Clone: TMethod;
function CanInvoke: Boolean ;
function Invoke(const AParams: array of TObject): TObject;
function ToString: string ; override ;
function IsEmpty: Boolean ;
class function Empty: TMethod; static ;
class operator Implicit(ADelegate: Delegate): TMethod;
class operator Equal(const ALeft, ARight: TMethod): Boolean ;
class operator NotEqual(const ALeft, ARight: TMethod): Boolean ;
end ;
|
Déclarons une variable de type TMethod afin de construire notre appel :
var MaMethod : TMethod;
MaProc : TProcedureTypee;
|
On récupére d'abord un pointeur de méthode puis on affecte la partie code à une variable procédurale :
|
Je vous recommande de sauvegarder tous vos travaux avant d'exécuter le code suivant.
|
MaMethod:=TMethod(UnObjet.Traitement);
MaProc:=MaMethod.Code;
Enumere(tabNom,MaProc);
|
Ce code plante l'application car le compilateur insére dans le code d'appel, et dans celui de l'initialisation de la méthode, la gestion d'un paramètre implicite supplémentaire référençant l'adresse de l'objet hébergeant cette méthode. Il manque donc une information sur la pile.
Redéclarons notre type de procédure en prenant en compte ce paramètre supplémentaire :
TProcedureTypeeModifiee = Procedure (ParametreVide:Pointer ; Element : String );
|
Déclarons une nouvelle procédure EnumereV2 :
procedure EnumereV2(Collection: TStringDynArray ; Traitement:TProcedureTypeeModifiee);
var I : Integer ;
begin
for i := Low(Collection) to High(Collection) do
Traitement(Nil ,Collection[i]);
end ;
|
L'ajout d'un paramètre supplémentaire est inutile à nos traitement mais nécessaire pour se caler sur le code généré.
A partir du moment on utilise ce type de construction, les propriétés de l'objet ne sont plus accessible et leurs accés provoquera un comportement instable de l'application. On peut s'éviter ce désagrément en forcant, si possible, le type de la méthode d'objet en méthode de classe :
TUneClasse =Class
private
FProcedure : TProcedureObjet;
public
class procedure clsAffiche(Element: String );
...
|
Dans ce cas l'appel est "simplifié" :
EnumereV2(tabNom,@TUneClasse.clsAffiche);
|
4-2. Appel d'une procédure en lieu et place d'une méthode
Pour appeler une procédure via une variable de méthode d'objet on peut utiliser une fonction renvoyant une instance de TMethod :
Function MakeProcedureOfObject(ProcedureCible : Pointer ):TMethod;
begin
Result.code:=ProcedureCible;
Result.Data:=Nil ;
end ;
|
L'appel de la procédure nécessite de transtyper le résultat obtenu :
UnObjet.Traitement:=TProcedureObjet(MakeProcedureOfObject(@AfficheTableau2));
UnObjet.Iteration(tabNom);
|
Cette approche permet par exemple d'affecter à une méthode d'objet une procédure imbriquée.
On pourrait trés bien renseigner le champ Data avec la référence de l'objet mais dans ce cas là autant utiliser directement un objet ;-)
5. Conclusion
Bien évidement ce que l'on vient de voir ici n'est pas très orthodoxe comme approche mais peut dans certaines circonstances être utile au moins pour la seconde pratique. Je vous laisse seul juge de tels usages.
Sachez enfin que pour la manipulation de hook, ou appel callback, Delphi propose la méthode
Classes.MakeObjectInstance qui se charge de convertir un type particulier de méthode d'objet en un pointeur. Vous pouvez consulter le code source de la VCL ou ce
lien ;-)
6. Lien
Les sources présentées sur cette page sont libres de droits
et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation
constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ©
2007 Laurent Dardenne. Aucune reproduction, même partielle, ne peut être
faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc.
sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à
trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.