Ho chiesto un simile question alle variabili di interfaccia implicite non molto tempo fa.È documentato il trattamento del compilatore delle variabili implicite dell'interfaccia?
L'origine di questa domanda era un bug nel mio codice a causa del fatto che non ero a conoscenza dell'esistenza di una variabile di interfaccia implicita creata dal compilatore. Questa variabile è stata finalizzata al termine della procedura di proprietà. Questo a sua volta ha causato un bug a causa della durata della variabile che è più lunga di quanto avessi previsto.
Ora, Ho un progetto semplice per illustrare alcuni comportamenti interessanti dal compilatore:
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
è compilato proprio come si potrebbe immaginare. La variabile locale I
, il risultato della funzione, viene passata come parametro implicito var
a Create
. L'ordine per StoreToLocal
produce una singola chiamata a IntfClear
. Nessuna sorpresa lì.
Tuttavia, StoreViaPointerToLocal
viene trattato in modo diverso. Il compilatore crea una variabile locale implicita che passa a Create
. Quando viene restituito Create
, viene eseguita l'assegnazione a P^
. Questo lascia la routine con due variabili locali che contengono riferimenti all'interfaccia. L'ordine per StoreViaPointerToLocal
comporta due chiamate allo IntfClear
.
Il codice compilato per StoreViaPointerToLocal
è come questo:
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
posso immaginare il motivo per cui il compilatore sta facendo questo. Quando può dimostrare che l'assegnazione alla variabile risultato non genera un'eccezione (ad esempio se la variabile è un locale), utilizza direttamente la variabile risultato. Altrimenti utilizza un locale implicito e copia l'interfaccia una volta restituita la funzione, assicurando così che non ci siano perdite di riferimento in caso di eccezione.
Ma non riesco a trovare alcuna dichiarazione di questo nella documentazione. È importante perché la vita dell'interfaccia è importante e, in quanto programmatore, devi essere in grado di influenzarlo occasionalmente.
Quindi, qualcuno sa se c'è qualche documentazione di questo comportamento? Se no qualcuno ha più conoscenza di esso? Come vengono gestiti i campi dell'istanza, non l'ho ancora verificato. Ovviamente potrei provare tutto da solo ma sto cercando una dichiarazione più formale e preferisco sempre evitare di fare affidamento sui dettagli di implementazione elaborati per tentativi ed errori.
Update 1
Per rispondere alla domanda di Remy, che contava per me quando avevo bisogno di finalizzare l'oggetto dietro l'interfaccia prima di effettuare un'altra finalizzazione.
begin
AcquirePythonGIL;
try
PyObject := CreatePythonObject;
try
//do stuff with PyObject
finally
Finalize(PyObject);
end;
finally
ReleasePythonGIL;
end;
end;
Come scritto in questo modo va bene. Ma nel codice reale ho avuto un secondo locale implicito che è stato finalizzato dopo che il GIL è stato rilasciato e quello bombardato. Ho risolto il problema estraendo il codice all'interno di Acquire/Release GIL in un metodo separato e quindi ridotto l'ambito della variabile di interfaccia.
Non so perché questo è stato downvoted, a parte che la domanda è davvero complessa. In aumento per essere al di sopra della mia testa. So che esattamente questo frammento di arcanum ha portato ad alcuni sottili bug di conteggio dei riferimenti in un'app che ho lavorato un anno fa. Uno dei nostri migliori geek ha passato ore a capirlo. Alla fine abbiamo lavorato attorno ad esso, ma non abbiamo mai capito come avrebbe dovuto funzionare il compilatore. –
Quando si lavora con le interfacce di conteggio dei riferimenti, non si deve presumere che non ci siano altri client (il compilatore nel proprio caso) che hanno i propri riferimenti di interfaccia. Se tutti i client eseguono correttamente 'AddRef' /' Release', tutto dovrebbe funzionare correttamente. Altrimenti è un bug (il tuo bug dal momento che presumo che il compilatore faccia correttamente il conteggio dei riferimenti). – kludg
@Serg Il compilatore ha fatto il suo conteggio di riferimento perfettamente. Il problema era che c'era una variabile extra con un riferimento che non potevo vedere. Quello che voglio sapere è ciò che provoca il compilatore a prendere un riferimento così extra, nascosto. –