Nie tak dawno zadałem podobne pytanie dotyczące niejawnych zmiennych interfejsu.
Źródłem tego pytania był błąd w moim kodzie, ponieważ nie byłem świadomy istnienia niejawnej zmiennej interfejsu utworzonej przez kompilator. Ta zmienna została sfinalizowana, gdy procedura, która była jej właścicielem, zakończyła się. To z kolei spowodowało błąd, ponieważ czas życia zmiennej był dłuższy niż się spodziewałem.
Teraz mam prosty projekt ilustrujący kilka interesujących zachowań kompilatora:
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
jest kompilowany tak, jak można sobie wyobrazić. Zmienna lokalna I
, wynik funkcji, jest przekazywana jako niejawny var
parametr do Create
. Porządkowanie StoreToLocal
wyników w jednym telefonie do IntfClear
. Żadnych niespodzianek.
Jednak StoreViaPointerToLocal
jest traktowany inaczej. Kompilator tworzy niejawną zmienną lokalną, do której przekazuje Create
. Kiedy Create
zwraca, P^
wykonywane jest przypisanie do . To pozostawia procedurę z dwiema zmiennymi lokalnymi przechowującymi odwołania do interfejsu. Porządkowanie StoreViaPointerToLocal
wyników w dwóch wezwaniach IntfClear
.
Skompilowany kod StoreViaPointerToLocal
wygląda następująco:
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
Mogę się domyślić, dlaczego kompilator to robi. Jeśli może udowodnić, że przypisanie do zmiennej wynikowej nie spowoduje wyjątku (tj. Jeśli zmienna jest lokalna), wówczas używa zmiennej wynikowej bezpośrednio. W przeciwnym razie używa niejawnego lokalnego i kopiuje interfejs po zwróceniu funkcji, zapewniając w ten sposób, że nie wyciekniemy odwołania w przypadku wyjątku.
Ale nie mogę znaleźć żadnego stwierdzenia na ten temat w dokumentacji. Ma to znaczenie, ponieważ żywotność interfejsu jest ważna i jako programista musisz mieć możliwość wpływania na niego od czasu do czasu.
Więc czy ktoś wie, czy istnieje dokumentacja tego zachowania? Jeśli nie, to czy ktoś ma o tym więcej wiedzy? Jak obsługiwane są pola instancji, jeszcze tego nie sprawdziłem. Oczywiście mógłbym wypróbować to wszystko samodzielnie, ale szukam bardziej formalnego oświadczenia i zawsze wolę unikać polegania na szczegółach implementacji opracowanych metodą prób i błędów.
Zaktualizuj 1
Odpowiadając na pytanie Remy'ego, ważne było dla mnie, kiedy musiałem sfinalizować obiekt za interfejsem przed wykonaniem kolejnej finalizacji.
begin
AcquirePythonGIL;
try
PyObject := CreatePythonObject;
try
//do stuff with PyObject
finally
Finalize(PyObject);
end;
finally
ReleasePythonGIL;
end;
end;
Jak napisano w ten sposób, jest w porządku. Ale w prawdziwym kodzie miałem drugi domyślny lokalny, który został sfinalizowany po wydaniu GIL i zbombardowaniu. Rozwiązałem problem, wyodrębniając kod z GIL Acquire / Release do oddzielnej metody i tym samym zawęziłem zakres zmiennej interfejsu.