Dépendance au niveau implémentation▲
Pour commencer, il y a un cas où cela marche sans problème, c'est quand la clause uses qui fait référence à l'autre unité se trouve dans la section d'implémentation. Dans ce cas, l'ensemble des déclarations de types, de procédures et de fonctions sont lues dans chacune des deux unités. En effet, ces déclarations se trouvent dans la section interface, c'est-à -dire avant que la question de la dépendance mutuelle soit soulevée. Quand, dans l'implémentation, on fait référence à l'autre unité, tous les membres publics sont déjà définis.
Dépendance mixte interface-implémentation▲
Il se peut qu'une unité A fasse référence à une unité B par une clause uses dans sa partie implémentation, tandis que l'unité B fasse référence l'unité A dans sa partie interface. Dans ce cas, le compilateur va d'abord lire l'interface de A, parce qu'elle est nécessaire à l'interprétation de l'interface de l'unité B. Par exemple, l'unité B peut proposer des fonctions qui prennent en paramètre un type défini dans l'unité A. Une fois l'interface de B déterminée, le compilateur va s'occuper des implémentations. Par la suite, l'unité A et l'unité B peuvent utiliser dans leur implémentation les fonctions de l'autre.
Référence circulaire ou dépendance interface-interface▲
Maintenant, si l'on fait deux unités qui font référence l'une à l'autre, mais avec une clause uses dans leurs interfaces, on obtient l'erreur de référence circulaire. En effet, pour pouvoir interpréter correctement l'interface d'une unité, il faut que le compilateur ait lu l'interface de l'autre unité, et vice-versa. Certains compilateurs ne génèrent pas d'erreur dans ces cas-là , mais en Pascal, pour le moment, une telle chose est impossible.
Fausse référence circulaire et vraie référence circulaire▲
Il se peut qu'en réalité, il ne soit pas nécessaire de déclarer la dépendance au niveau de l'interface de l'unité. Dans ce cas, il suffit de déplacer une partie de la clause uses dans la partie implémentation, pour les unités ne faisant appel seulement à ce moment à des procédures, des fonctions, ou à des types déclarés dans d'autres unités. C'est le cas le plus fréquent fort heureusement.
Sinon, c'est généralement que certains objets définis dans une unité A contiennent des champs ou des méthodes utilisant des objets définis dans une unité B, qui eux-mêmes contiennent des champs ou des méthodes utilisant des objets définis dans l'unité A.
Par exemple :
{ dans l'unité A }
type
TObjetA = class
function
DonneObjetB: TObjetB;
end
;
{ dans l'unité B }
type
TObjetB = class
function
DonneObjetA: TObjetA;
end
;
Solution par la fusion▲
On peut régler le problème de la référence circulaire en mettant les deux objets dans une seule et même unité et en prédéclarant les types.
Cela se fait de la manière suivante :
{ dans l'unité fusionnée AB }
type
TObjetA = class
; { prédéclaration de A }
TObjetB = class
; { prédéclaration de B }
TObjetA = class
{ déclaration complète de A }
function
DonneObjetB: TObjetB;
end
;
TObjetB = class
{ déclaration complète de B }
function
DonneObjetA: TObjetA;
end
;
Bien entendu, il se peut que, de fil en aiguille, on obtienne des fichiers de code très gros, ce qui est l'inconvénient de cette méthode.
Solution par le non-typage▲
Enfin, on peut résoudre le problème en ne typant pas les champs ou les paramètres et les valeurs de retour des procédures et des fonctions.
{ dans l'unité A }
type
TObjetA = class
function
DonneObjetB: TObject;
end
;
{ dans l'unité B }
type
TObjetB = class
function
DonneObjetA: TObject;
end
;
Bien que la solution ne soit pas très élégante, elle permet de contourner le problème. Lors de l'appel des fonctions DonneObjetA et DonneObjetB, il faudra faire un transtypage pour avoir un objet bel et bien identifié comme étant de type TObjetA ou TObjetB.
Par exemple :
procedure
UtiliseObjetA;
var
objA : TObjetA;
begin
objA := TObjetA(objB.DonneObjetA);
objA.Affiche;
end
;