Я просил аналогичную question о неявных интерфейсных переменных не так давно.
Источником этого вопроса была ошибка в моем коде из-за того, что я не знал о существовании неявной переменной интерфейса, созданной компилятором. Эта переменная была завершена, когда процедура, в которой она была закончена. Это, в свою очередь, вызвало ошибку из-за того, что время жизни переменной было дольше, чем я ожидал.
Теперь у меня есть простой проект, чтобы проиллюстрировать интересное поведение компилятора:
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 был выпущен и был взломан. Я решил проблему, извлекая код внутри Acquire/Release GIL в отдельный метод и тем самым сузил область интерфейсной переменной.