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