J'ai posé une question similaire sur les variables d'interface implicites il n'y a pas si longtemps.
La source de cette question était un bogue dans mon code car je ne connaissais pas l'existence d'une variable d'interface implicite créée par le compilateur. Cette variable a été finalisée lorsque la procédure qui la possédait s'est terminée. Cela a à son tour provoqué un bug car la durée de vie de la variable était plus longue que ce que j'avais prévu.
Maintenant, j'ai un projet simple pour illustrer un comportement intéressant du compilateur:
program ImplicitInterfaceLocals;
{$APPTYPE CONSOLE}
uses
Classes;
function Create: IInterface;
begin
Result := TInterfacedObject.Create;
end;
procedure StoreToLocal;
var
I: IInterface;
begin
I := Create;
end;
procedure StoreViaPointerToLocal;
var
I: IInterface;
P: ^IInterface;
begin
P := @I;
P^ := Create;
end;
begin
StoreToLocal;
StoreViaPointerToLocal;
end.
StoreToLocal
est compilé comme vous l'imaginez. La variable locale I
, le résultat de la fonction, est transmise en tant que var
paramètre implicite à Create
. Le rangement des StoreToLocal
résultats en un seul appel à IntfClear
. Pas de surprises là-bas.
Cependant, StoreViaPointerToLocal
est traité différemment. Le compilateur crée une variable locale implicite à laquelle il passe Create
. Au Create
retour, l'affectation à P^
est effectuée. Cela laisse la routine avec deux variables locales contenant des références à l'interface. Le rangement des StoreViaPointerToLocal
résultats en deux appels à IntfClear
.
Le code compilé pour StoreViaPointerToLocal
est comme ceci:
ImplicitInterfaceLocals.dpr.24: begin
00435C50 55 push ebp
00435C51 8BEC mov ebp,esp
00435C53 6A00 push $00
00435C55 6A00 push $00
00435C57 6A00 push $00
00435C59 33C0 xor eax,eax
00435C5B 55 push ebp
00435C5C 689E5C4300 push $00435c9e
00435C61 64FF30 push dword ptr fs:[eax]
00435C64 648920 mov fs:[eax],esp
ImplicitInterfaceLocals.dpr.25: P := @I;
00435C67 8D45FC lea eax,[ebp-$04]
00435C6A 8945F8 mov [ebp-$08],eax
ImplicitInterfaceLocals.dpr.26: P^ := Create;
00435C6D 8D45F4 lea eax,[ebp-$0c]
00435C70 E873FFFFFF call Create
00435C75 8B55F4 mov edx,[ebp-$0c]
00435C78 8B45F8 mov eax,[ebp-$08]
00435C7B E81032FDFF call @IntfCopy
ImplicitInterfaceLocals.dpr.27: end;
00435C80 33C0 xor eax,eax
00435C82 5A pop edx
00435C83 59 pop ecx
00435C84 59 pop ecx
00435C85 648910 mov fs:[eax],edx
00435C88 68A55C4300 push $00435ca5
00435C8D 8D45F4 lea eax,[ebp-$0c]
00435C90 E8E331FDFF call @IntfClear
00435C95 8D45FC lea eax,[ebp-$04]
00435C98 E8DB31FDFF call @IntfClear
00435C9D C3 ret
Je peux deviner pourquoi le compilateur fait cela. Lorsqu'il peut prouver que l'assignation à la variable de résultat ne lèvera pas d'exception (c'est-à-dire si la variable est locale), alors il utilise directement la variable de résultat. Sinon, il utilise un local implicite et copie l'interface une fois que la fonction est retournée, assurant ainsi que nous ne fuyons pas la référence en cas d'exception.
Mais je ne trouve aucune déclaration à ce sujet dans la documentation. C'est important car la durée de vie de l'interface est importante et en tant que programmeur, vous devez pouvoir l'influencer à l'occasion.
Alors, est-ce que quelqu'un sait s'il existe une documentation sur ce comportement? Sinon, est-ce que quelqu'un en a plus connaissance? Comment sont gérés les champs d'instance, je n'ai pas encore vérifié cela. Bien sûr, je pourrais tout essayer par moi-même, mais je recherche une déclaration plus formelle et je préfère toujours éviter de me fier aux détails d'implémentation élaborés par essais et erreurs.
Mise à jour 1
Pour répondre à la question de Remy, cela m'importait quand j'avais besoin de finaliser l'objet derrière l'interface avant de procéder à une autre finalisation.
begin
AcquirePythonGIL;
try
PyObject := CreatePythonObject;
try
//do stuff with PyObject
finally
Finalize(PyObject);
end;
finally
ReleasePythonGIL;
end;
end;
Comme écrit comme ça, c'est bien. Mais dans le vrai code, j'avais un deuxième local implicite qui a été finalisé après la libération du GIL et qui a été bombardé. J'ai résolu le problème en extrayant le code à l'intérieur du GIL Acquire / Release dans une méthode distincte et j'ai donc réduit la portée de la variable d'interface.