Не так давно я задал аналогичный вопрос о неявных переменных интерфейса.
Источником этого вопроса была ошибка в моем коде из-за того, что я не знал о существовании неявной интерфейсной переменной, созданной компилятором. Эта переменная была завершена после завершения процедуры, которой она принадлежала. Это, в свою очередь, вызвало ошибку из-за того, что время жизни переменной было больше, чем я ожидал.
Теперь у меня есть простой проект, иллюстрирующий интересное поведение компилятора:
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
компилируется так, как вы можете себе представить. Локальная переменная I
, результат функции, передается как неявный var
параметр в Create
. Приведение в порядок StoreToLocal
результатов за один вызов IntfClear
. Никаких сюрпризов.
Однако StoreViaPointerToLocal
трактуется иначе. Компилятор создает неявную локальную переменную, которой он передает Create
. При Create
возврате выполняется присвоение P^
. Это оставляет подпрограмму с двумя локальными переменными, содержащими ссылки на интерфейс. Приведение в порядок StoreViaPointerToLocal
приводит к двум вызовам IntfClear
.
Скомпилированный код StoreViaPointerToLocal
выглядит так:
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
Я могу догадаться, почему компилятор это делает. Когда он может доказать, что присвоение переменной результата не вызовет исключения (т. Е. Если переменная является локальной), он использует переменную результата напрямую. В противном случае он использует неявную локальную переменную и копирует интерфейс после возврата функции, что гарантирует отсутствие утечки ссылки в случае исключения.
Но я не могу найти никаких утверждений об этом в документации. Это важно, потому что время жизни интерфейса важно, и вам, как программисту, нужно иметь возможность время от времени на него влиять.
Итак, кто-нибудь знает, есть ли документация об этом поведении? Если нет, то кто-нибудь еще об этом знает? Как обрабатываются поля экземпляра, я еще не проверял. Конечно, я мог бы попробовать все это сам, но я ищу более формальное заявление и всегда предпочитаю не полагаться на детали реализации, разработанные методом проб и ошибок.
Обновление 1
Чтобы ответить на вопрос Реми, мне было важно, когда мне нужно было завершить объект, стоящий за интерфейсом, прежде чем выполнять еще одну финализацию.
begin
AcquirePythonGIL;
try
PyObject := CreatePythonObject;
try
//do stuff with PyObject
finally
Finalize(PyObject);
end;
finally
ReleasePythonGIL;
end;
end;
Как написано так, это нормально. Но в реальном коде у меня был второй неявный локальный код, который был завершен после того, как GIL был выпущен и провалился. Я решил проблему, выделив код внутри GIL Acquire / Release в отдельный метод и таким образом сузив область действия переменной интерфейса.