Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Подводные камни
  
О разделе

Список по категориям
Общий список

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  12:40[Войти] | [Зарегистрироваться]

Обсуждение материала
Интерфейсы.Access Violation в _IntfClear для TComponent
Полный текст материала


Другие публикации автора: Алексей Михайличенко

Цитата или краткий комментарий:

«... Речь идет об использовании интерфейсных ссылок на объекты — потомки TComponent. В отличие от потомков TInterfacedObject, уничтожение которых происходит автоматически по подсчету ссылок, потомки TComponent уничтожаются другими методами. ...»


Важно:
  • Страница предназначена для обсуждения материала, его содержания, полезности, соответствия действительности и так далее. Смысл не в разборке, а в приближении к истине :о) и пользе для всех.
  • Любые другие сообщения или вопросы, а так же личные эмоции в адрес авторов и полемика, не относящаяся к теме обсуждаемого материала, будут удаляться без предупреждения авторов, дабы не мешать жителям нормально общаться.
  • Размер одного сообщений не должен превышать 5К. Если Вам нужно сказать больше, сделайте это за два раза. Или, что в данной ситуации правильнее, напишите свою статью.
Всегда легче осудить сделанное, нежели сделать самому. Поэтому, пожалуйста, соблюдайте правила Королевства и уважайте друг друга.



Добавить свое мнение.




Смотрите также материалы по темам:
[TComponent] [Жизненный цикл] [Интерфейсы]

Комментарии жителей
Отслеживать это обсуждение

Всего сообщений: 50

15-06-2008 15:08
>>> Александр, не знаю, очевидно ли это из предыдущего сообщения или нет, но я предполагаю, что у интерфейса IInterface не должно быть методов _Release и _AddRef
Да нет, это понятно. Наверное, я смотрю на это под неверным углом ;)


15-06-2008 15:06
Александр, не знаю, очевидно ли это из предыдущего сообщения или нет, но я предполагаю, что у интерфейса IInterface не должно быть методов _Release и _AddRef. Т.е. IInterface и IUnknown предполагаю совершенно разными интерфейсами с совершенно разным набором методов. Во многих языках так и реализовано (в том же Delphi.NET), базовый интерфейс не имеет каких-либо методов, тем более связанных с управлением временем жизни.
А IUnknown должен быть базовым интерфейсом для поддержки COM-объектов и только, а для них сама спецификация технологии требует автоматического уничтожения объекта при обнулении счетчика ссылок. И то, что код управления счетчиком для него будет автоматически вставлять компилятор - нет ничего плохого.
 Ins


15-06-2008 15:01
Кстати, как вариант решения можно рассмотреть выдачу WARNING-а в местах использования временных авто-финализируемых интерфейсных переменных.


15-06-2008 14:40
Я просто хочу сказать, что интерфейс не должен декларировать, как его нужно реализовывать. А именно это сделает разделение на авто-финализируемый IUnknown и нефинализируемый IInterface. Иначе говоря, я считаю, что управление временем жизни реализации не является частью контракта интерфейса. Ок, это моё мнение и другие могут считать иначе.
Ну вот сделал я объект, реализующий интерфейс. Положим, я сделал его нефинализируемым и явно управлял временем его жизни. И клиентский код, использующий мой интерфейс, был скомпилирован компилятором без вызова AddRef/Release.
А потом я понял, что сделал себе только кучу проблем, и захотел переписать реализацию. В этот раз я построил её на базе счётчика ссылок. И что получили? Клиентский код становится неработоспособным, поскольку в нём нет необходимых вызовов.
Конечно, я в явном виде сделал бяку - сменил IInterface на IUnknown. Но с логической точки зрения я изменил только релизацию интерфейса - и от этого отвалился клиентсий код.


15-06-2008 14:22
Ведь это значит, что клиент будет иметь информацию о внутренней реализации интерфейса. А именно, как реализация управляет временем своей жизни.

Эээ, не совсем понял. Я имею в виду следующее: если компилятор видит ссылку интерфейсного типа, унаследованного от IUnknown, он генерирует код автоматической финализации. Если он видит, что интерфейс не является потомком IUnknown, а является потомком IInterface - ничего такого не делает. IInterface должен быть предком по-умолчанию, подобно тому, как таким предком по-умолчанию является TObject для классов. И все. Поясню на примере:

IMyIntf = interface(IUnknown)
...
end;

var
  Intf: IMyIntf;
begin
  Intf := ...
...
end; // Здесь компилятор вставляет код финализации Intf



IMyIntf = interface(IInterface)
...
end;

var
  Intf: IMyIntf;
begin
  Intf := ...
...
end; // А здесь - нет.


Просто нужно, чтобы был интерфейсный тип без автоматической финализации, а к IUnknown у компилятора было особое отношение, подобно тому, как он по-особому относится к IDispatch или IVarInvokeable.
 Ins


15-06-2008 14:11
>>> Я наступил на эти грабли, даже не использовав функции как процедуры, а именно как функции
Ну такой сценарий мне в голову не приходил.

Ещё, насчёт разделения IUnknown и IInterface. Ведь это значит, что клиент будет иметь информацию о внутренней реализации интерфейса. А именно, как реализация управляет временем своей жизни. По логике, такой информации у него буть не должно. Лично мне это кажется неправильным. Ну а как, если я захочу сменить вид управления (с явного на неявный или наоборот)? Тогда придётся перекомпилировать весь код, который использует мой интерфейс, т.к. компилятор не проставил управление ссылками.
По-моему, от такого решения проблем возникает больше, чем оно решает.


15-06-2008 07:43
Тот же самый расширенный синтаксис. Его не просто добавили. А ведь ещё ввели возможность его отключать! Т.е. как бы говорили: те, кто не уверены - отключайте!

Расширенный синтаксис тут ни причем. Я наступил на эти грабли, даже не использовав функции как процедуры, а именно как функции. Некоторые скажут: "возвращать интерфейсы следует только через out/var параметры, тогда не будет проблем". Ну уж нет! :) У меня был интерфейс - узел дерева, который имел в числе прочих следующие свойства:

ITreeNode = interface
  ...
  property Nodes[Index: Integer]: ITreeNode read GetNode;
  property NodeByName[AName: String]: ITreeNode read GetNodeByName;
  ...
end;


И сделано это специально для того, чтобы я мог писать так:

Root.NodeByName['SomeNode'].AddNode(...);


а не так:

Root.GetNodeByName('SomeNode', Node);
Node.AddNode(...);
Node := nil;


Я очень сильно теряю в удобстве использования во втором случае ;)
 Ins


15-06-2008 07:31
Та какие проблемы, давайте попросим CodeGear написать один раз такой объект и включить его в Classes.pas, а мы уж будем от него наследоваться :D

Я тут подумал над возможностью реализации такого класса. Не совсем так, как я предположил сразу, но вроде бы не сложно. Тут нужно кое что учесть. Проблема может быть вот в чем:
1. Для того, чтобы запретить объекту уничтожиться, необходимо перекрыть FreeInstance, где в зависимости от каких-либо условий, вызывать либо не вызывать inherited
2. Код метода Destroy выполняется всегда перед FreeInstance. К тому моменту, когда мы захотим принять решение о том, что объект уничтожать еще рано, произойдет его внутренняя очистка в Destroy. В итоге, когда дело дойдет до настоящего уничтожения объекта, повторный вызов Destroy может привести к исключению.
Решить, в принипе, можно: в методе _Release вместо Destroy вызывать непосредственно FreeInstance... Как-нибудь так:

TMyInterfacedObject = class(TObject, IInterface)
protected
  FRefCount: Integer;
  FDestroyed: Boolean;
  function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
  function _AddRef: Integer; stdcall;
  function _Release: Integer; stdcall;
public
  procedure BeforeDestruction; override;
  procedure FreeInstance; override;
  property RefCount: Integer read FRefCount;
end;

procedure TMyInterfacedObject.BeforeDestruction;
begin
  FDestroyed := True;
end;

procedure TMyInterfacedObject.FreeInstance;
begin
  if RefCount = 0 then
    inherited FreeInstance;
end;

function TMyInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  Result := IfThen(GetInterface(IID, Obj), 0, E_NOINTERFACE);
end;

function TMyInterfacedObject._AddRef: Integer;
begin
  Result := InterlockedIncrement(FRefCount);
end;

function TMyInterfacedObject._Release: Integer;
begin
  Result := InterlockedDecrement(FRefCount);
  if (Result = 0) and FDestroyed then
    FreeInstance;
end;


 Ins


15-06-2008 04:11
>>> Думаю все дело не только в простоте программирования компилятора, а еще и в оптимизации. Так будет только один блок try finally end, а не несколько.
Вот уж точно дело не в этом :) Если подумать, никаких try/finally там стоять не должно. Потому что там нет кода, который может возбудить исключение.

Если бы компилятор финализировал временную переменную сразу после использования, то код

  FuncThatReturnsInterface(...);


был бы скомпилирован в (примерно):

  Temp := FuncThatReturnsInterface(...);
  Finalize(Temp);



Ну и где тут место, которое нужно защищать try/finally? Саму функцию помещать в блок try:

  try
    Temp := FuncThatReturnsInterface(...);
  finally
    Finalize(Temp);
  end;


не имеет смысла: если в функции возникнет исключение, то Temp останется пуста (из-за исключения не выполнятся команды присваивания). Ставить же try после вызова функции ещё более бессмысленно:

Temp := FuncThatReturnsInterface(...);
  try
  finally
    Finalize(Temp);
  end;


В блоке try вообще нет команд, поэтому он не нужен.

В идеале, код мог бы быть таким:

  FinalizeInterface(FuncThatReturnsInterface(...));


В машинном коде это выглядело бы примерно так:

  // здесь установка параметров функции
  call FuncThatReturnsInterface
  call FinalizeInterface   // функция вернёт интерфейс в EAX, его же и передадим в функцию очистки (может сюда подойдёт и обычный Finalize)
  ... // продолжение работы



Можно наверное, но слишком много действий требует: мне нужно и счетчик ссылок заводить, который мне не нужен, и FreeInstance перекрывать, и в Destroy выставление флажка делать и анализ счетчика, причем в Destroy мне одинаковый код писать как в данном классе, так и во всех его потомках. Опять-таки, борьба с дырявыми абстракциями :(
Та какие проблемы, давайте попросим CodeGear написать один раз такой объект и включить его в Classes.pas, а мы уж будем от него наследоваться :D

P.S. Ниже - сугубо мои мысли.
Кстати, можно заметить, что общая тенденция была таковой: разработчики дают нам в руки опасную игрушку и говорят: вот тут мы ввели новую мощную штуку, но, смотрите, пользуйтесь ей аккуратно! Вы должны понимать, что вы делаете, что происходит под капотом, и к чему это может привести. Всё - сугубо под вашу ответственность. Вы должны вести себя хорошо. Т.е. это ваша обязанность - правильно применять мощный инструментарий.
Тот же самый расширенный синтаксис. Его не просто добавили. А ведь ещё ввели возможность его отключать! Т.е. как бы говорили: те, кто не уверены - отключайте! Т.е. сознавали потенциальную опасность такого упрощения жизни.
И если теперь захочешь что-то изменить в другую сторону - всё, слишком поздно. Написаны тонны кода, который просто обязан компилироваться в новых версиях среды. Всё то же удаление интерфейса сразу по месту вызова: быть может существует код, который полагается на эту особенность? А может быть добавление такого поведения внесёт новые подводные камни? Ах, как всё не просто в нашем мире :)
И ведь не сказать, что эта тенданция меняется. Посмотрите на уже произошедшие и ожидаемые изменения в языке: оператор "for in", перегружаемые операторы для классов и записей. Думаю, и другие примеры найдутся. Какое количество кода скрывает использование этого инструментария? Какие новые просторы для подводных камней! XD


10-06-2008 14:28
Правда, его вряд ли внедрят.

Если я ничего не путаю, то в Delphi.NET так дело и обстоит. Там IInterface и IUnknown - это не псевдонимы одного интерфейса, а два разных интерфейса. Правда в .NET вообще не нужно самому следить за временем жизни :)

Интерфейсы не унаследованные от IUnknown есть, но они все равно неявно его наследуют. Это все описано в документации. И кода с учетом этого недостатка люди уже понаписали немало.

От IUnknown унаследованы только интерфейсы, имеющие отношение к COM. Все остальные - от IInterface. Многие классы, такие как TInterfacedObject, TInterfacedPersistent, TAggregatedObject, TComponent и другие содержат в своих объявлениях именно IInterface, а не от IUnknown. IUnknown реализуют только TComObject и прочие подобные. Подумайте, почему ;)

Кстати, свои объекты вполне можно переписать так, чтобы они не умирали от Destroy пока счетчик ссылок больше нуля. А на _Release не умирали пока не был вызван Destroy.

Можно наверное, но слишком много действий требует: мне нужно и счетчик ссылок заводить, который мне не нужен, и FreeInstance перекрывать, и в Destroy выставление флажка делать и анализ счетчика, причем в Destroy мне одинаковый код писать как в данном классе, так и во всех его потомках. Опять-таки, борьба с дырявыми абстракциями :(
 Ins


10-06-2008 13:28
Кстати, Антон Григорьев предложил прекрасное решение.

Правда, его вряд ли внедрят. Интерфейсы не унаследованные от IUnknown есть, но они все равно неявно его наследуют. Это все описано в документации. И кода с учетом этого недостатка люди уже понаписали немало.

Почему интрефейсные переменные не освобождают сразу, а ждут конца процедуры.
Думаю все дело не только в простоте программирования компилятора, а еще и в оптимизации.
Так будет только один блок try finally end, а не несколько. А такие блоки имеют реальное воплощение в виде команд процессора, это не абстрактные begin end.

К сведению тех, кто любит оптимизацию кода: try except end намного более тормознутый, чем try finally end.

Кстати, свои объекты вполне можно переписать так, чтобы они не умирали от Destroy пока счетчик ссылок больше нуля. А на _Release не умирали пока не был вызван Destroy.
Так можно решить проблему не обыскивая весь код и устраняя опасные вызовы.


10-06-2008 03:54
Возможно логичнее было бы освобождать кое-что сразу, но это бы сильно усложнило и запутало работу разработчиков компилятора.

Угу. В том то и дело, что это проблемы не мои, а разработчиков компилятора. ;-)

Правильнее было бы, если бы можно было создавать интерфейсы, как унаследованные от IUnknown, так и не унаследованные.

Кстати да, мягкое от теплого следует отличать. Интерфейсы - это не только COM/DCOM. Следовало бы разделить IInterface и IUnknown.
 Ins


10-06-2008 02:34
>>> Если единственный путь - явное создание и ручное обнуление промежуточной переменной, то пусть компилятор вообще запретит мне создание и вызов функций, возвращающих интерфейсы, если он не может иначе.
Ну отключите опцию Extended Syntax ({$X-}) :D   Блин, хотя при этом отвалится Result...  :((

Предлагаю зарегистрировать это на Quality Central как предложение по улучшению и поддержать всем составом.


10-06-2008 00:10
По-моему, проблема вообще не столько в том, где финализируются неявные переменные, сколько в том, что язык вообще допускает такое смешивание: временем жизни одного и того же объекта, с одной стороны, явно управляет программист, а с другой - неявно пытается управлять компилятор. А у семи нянек дитя без глаза. Правильнее было бы, если бы можно было создавать интерфейсы, как унаследованные от IUnknown, так и не унаследованные. И чтобы для объектов, реализующих интерфейсы - потомки IUnknown, компилятор вообще не давал возможности удалять их иначе, чем через обнуление счётчика ссылок на интерфейсы, а для интерфейсов, не унаследованных от IUnknown, вообще не считал ссылки. Тогда удалось бы избежать множества ошибок, да и абстракция не была бы такой дырявой.


09-06-2008 15:46
сообщение от автора материала
Сторонникам того, что все происходит правильно. Вопрос.

1. Я хочу создать объект.
2. Я хочу вызывать его методы через реализуемые им интерфейсы, как аналог множественого наследования.
3. Объект больше не нужен, я хочу его освободить.

И получаю AV. И это никуда не годится. Как мне достигнуть желаемого?

Если единственный путь - явное создание и ручное обнуление промежуточной переменной, то пусть компилятор вообще запретит мне создание и вызов функций, возвращающих интерфейсы, если он не может иначе.

Либо пусть финализирует свои временные переменные непосредственно после шага 2. Если это уж такие его личные временные переменные, пусть их областью видимости будет шаг 2 - возврат этой переменной и вызов функции. И ничего в этом странного нет - в других языках в порядке вещей области видимости, меньшие, чем процедура.

Этот мир погубят протекающие абстракции. Уже сейчас мы все больше становимся не математиками, инженерами и творцами, каковыми изначально задумывались быть программисты, а специалистами по дырявым абстракциям. Чем больше знаешь дырявых абстракций, багов и недокументированных фич - тем круче и нужнее - гуру, способный объяснить очередные "чудеса" реляционной алгебры в глючащем SQL-запросе дырками операционки и сборками ядра - эдакий астроном-сантехник :-)


09-06-2008 14:32
>>> Если честно, то я не нашел, где в документации сказано о неявных интерфейсных ссылках и их финализации, хотя допускаю, что плохо смотрел.
Не, ну прям так, конечно не написано :) Но это напрямую следует из двух общеизвестных фактов:
1). Функция всегда что-то возвращает. В нашем случае, если она возвращает интерфейс, то он должен быть финализирован. Включение расширенного синтаксиса позволяет вам вызывать функцию как процедуру, но это не превращает функцию в процедуру. Она всё равно вернёт интерфейс и этот интерфейс нужно освободить.
2). Все авто-финализируемые переменные освобождаются при выходе из области видимости. В данном случае - при выходе из вызывающей процедуры.
Неявная временная переменная вообще здесь не причём. Она - не причина, а следствие. Компилятор создаёт её только с целью обеспечить выполнение п2.
Кстати, жаль, что компилятор не допускает выражений типа FuncThatReturnsInterface := nil или хотя бы Finalize(FuncThatReturnsInterface). Тогда бы можно было бы вообще без переменных обойтись.


09-06-2008 13:45
Поясню как работает.

Если вы выйдете из процедуры по Exit
Если процедуру прервет Exception
Если вы переместитесь по процедуре при помощи Break, Continue или goto
тогда _Release где нибудь да потеряется и не вызовется.

Поэтому существует неявный блок try finally end, где гарантированно освобождаются интерфейсы.
В начале процедуры они инициализируются nil, а в конце процедуры освобождаются.

Возможно логичнее было бы освобождать кое-что сразу, но это бы сильно усложнило и запутало работу разработчиков компилятора. Да и нашу-то тоже, если подумать.

Писать лениво, но подумайте сами про все сложности и поймете, что в Borland все верно сделали.


09-06-2008 03:44
Ну, так уж устроен наш мир, что абстракциям свойственно протекать :)


08-06-2008 05:36
Он всё делает так, как указано в документации, разве нет?

Если честно, то я не нашел, где в документации сказано о неявных интерфейсных ссылках и их финализации, хотя допускаю, что плохо смотрел. Но даже если там черным по белому сказано, что компилятор заводит ссылки сам и производит их финализацию в эпилоге, то это надо признать весьма оригинальный способ снять с себя ответсвенность и переложить на программиста :)
Это плохо, когда особенности реализации компилятора диктуют программисту как писать код. Диктовать должны правила языка, а язык - это лишь набор абстракций. Borland мог себе позволить обратное, будучи одновременно поставщиком и компилятора, и языка, и следовательно мог перенести особенности реализации компилятора на сам язык. Скажем, этим легко объяснить также тот факт, что я обязан назначать глобально-уникальный идентификатор интерфейсам, даже если не собираюсь выносить их за пределы своего приложения. Иначе я не смогу полноценно с интерфейсами работать. Компилятор не обеспечивает должной прозрачности и заставляет меня выполнять лишние действия. Есть и другие примеры таких "переносов" - делегация реализации интерфейса классовому свойству и ограничения, связанные с таким делегированием.
 Ins


08-06-2008 04:21
>>> А по-моему, самый настоящий "камушек"
Камушек. Только вот компилятор по-моему не при чём. Он всё делает так, как указано в документации, разве нет?


08-06-2008 03:09
Народ. Все правильно!
Так и должно работать!
:)


А по-моему, самый настоящий "камушек", так как не очевидно, что неявная интерфейсная ссылка финализируется лишь в самом конце процедуры. Если быть точнее, то из-за того, что она неявная, программиста не должен волновать сам факт ее существования. Задача компилятора - предоставить программисту набор абстракций, но если программист обязан знать об особенностях поведения компилятора, значит у последнего имеются явные проблемы. ;)
 Ins


08-06-2008 02:29
Народ. Все правильно!
Так и должно работать!
:)

Если используйте интерфейс сделанный из TInterfacedObject все работает во всех ситуациях.
Если берете интерфейс от объекта, который умирает "не по понятиям", то есть не по Release, то НЕЛЬЗЯ УБИВАТЬ ЕГО ВНУТРИ ТОЙ ЖЕ ПРОЦЕДУРЫ, ГДЕ ВЗЯТ ИНТЕРФЕЙС.

Интерфейсные переменные ЖИВУТ ДО КОНЦА процедуры, а там для них вызывается Release. Вызов GetInterface создавал НЕЯВНУЮ интерфейсную переменную. Подругому потому, что нельзя.

Delphi не освобождает строки, массивы и интерфейсы сразу, а ждет до конца процедуры.

Если сильно надо убить такой объект именно внутри процедуры, то заверните код использующий интерфейс в отдельную процедуру, вызываемую внутри оной.
Или ВСЕГДА интерфейсную переменную используйте явно:

CreateObjects;
Intf := GetInterface;
...
Intf := nil;
{ и потом можно }
DestroyObjects;



05-06-2008 07:31
Вот и я наступил на эти грабли :) Только у меня не потомок TComponent, а свой класс, который хоть и реализует интерфейсы, но не ведет учет ссылок (_AddRef и _Release просто возвращают -1).
 Ins


28-04-2007 04:12
to  Антон Григорьев

Ещё раз спасибо


28-04-2007 03:04
Осталась некорректная ссылка на интерфейс, но вслучае с TComponent она будет просто уничтожена, без попыток обращения к уже разрушенному объекту, т.к. у нас VCLComObject=nil и _Release вернёт -1.

Где ошибка? В чём я не прав?


Ну как же без попыток обращения к объекту? Как вы сами заметили, будет вызван метод _Release - метод того самого уже уничтоженного объекта. А этот _Release начнёт с того, что проверит значение поля FVCLComObject. А где в памяти располагается поле FVCLComObject? В той самой памяти, которая былда выделена объекту и которую менеджер памяти Delphi после вызова objTest.Free считает свободной. Вот именно при чтении значения FVCLComObject и происходит некорректное обращение к уже освобождённой памяти, которое может дать AV, а може и не дать - в зависимости от того, вернул ли эту память системе менеджер памяти или нет.

А если не вернул, то, в зависимости от действий после objTest.Free, эта память уже может быть использована для чего-то другого. И та её часть, где хранилось значение FVCLComObject, может получить любое непредсказуемое значение. Тогда в _Release проверка на nil даст False, ну а дальше, я думаю, объяснять не надо.

Вообще, проблемы с использованием той памяти, которая уже помечена как освобождённая, но системе ещё не была возвращена, типичны для Delphi. Если хотите разобраться с другими похожими ошибками, можете прочитать http://www.delphikingdom.com/asp/viewitem.asp?catalogid=1154 и http://www.delphikingdom.com/asp/viewitem.asp?catalogid=1153 - хотя на первый взгляд там речь идёт совсем о других вещах, корни у проблем те же.


28-04-2007 02:39
to  Антон Григорьев

Извиняюсь за настойчивость, но

>Просто у автора статьи получилось так, что менеджер памяти вернул кусок, занятый объектом, системе, и обращение к такому объекту возвращает AV. А у вас - не вернул...

Почему же "не вернул"?

  objTest := TTest.Create(nil);
  intfTest := objTest;
  objTest.Free;



Осталась некорректная ссылка на интерфейс, но вслучае с TComponent она будет просто уничтожена, без попыток обращения к уже разрушенному объекту, т.к. у нас VCLComObject=nil и _Release вернёт -1.

Где ошибка? В чём я не прав?


28-04-2007 00:14
to  Антон Григорьев

Спасибо


27-04-2007 07:58
А зачем? В процедуре InitInterface ставим "брякпойнт" на строчку intfTest := objTest;. Запускаем, нажимаем, смотрим: objTest.VCLComObject =nil. Или я что-то не так делаю?

Делаете вы всё так, вы просто не понимаете того, что видите.

С программой работают два менеджера памяти: системный и внутренний дельфийский. Дельфийский берёт у системного память большими кусками, а потом распределяет маленькими. Соответственно, есть область памяти, которая уже выделена программе системным менджером памяти, но ещё не распределена дельфийским и считается им неиспользуемой. Обращение к этой памяти не вызывает Access violation, но всё равно программа не должна к ней обращаться, т.к. в любой момент дельфийский менеджер памяти может выделить эту память для других нужд.

Что происходит, когда программа освобождает память (например, удаляя объект)? Менеджер памяти в соответствии со своими алгоритмами может либо немедленно вернуть эту память системе, либо придержать её для использования в будущем. В первом случае при попытке работы с уже удалённым объектом мы получим AV, а вот во втором - не получим. Более того, т.к. память при освобождении не очищается, все те данные, которые в ней хранились, остаются на месте, так что создаётся иллюзия того, что объект не удалён - вызовы выполняются, данные лежат, какие надо. Но это именно иллюзия, и обращение к такой памяти - грубейшая ошибка, потому что, повторяю, неизвестно, когда менеджер памяти, считая этот кусок свободным, выделит его для других целей, и в нём окажутся совсем другие данные. Просто у автора статьи получилось так, что менеджер памяти вернул кусок, занятый объектом, системе, и обращение к такому объекту возвращает AV. А у вас - не вернул, и из-за того, что объект не стёрся из свободной памяти вам кажется, что всё работает как надо.


27-04-2007 07:20
>Прокомментируйте, пожалуйста,...

А зачем? В процедуре InitInterface ставим "брякпойнт" на строчку intfTest := objTest;. Запускаем, нажимаем, смотрим: objTest.VCLComObject =nil. Или я что-то не так делаю?


26-04-2007 07:51
сообщение от автора материала
>>> Почему же у меня работает?

Потому что врожденного упрямства экспериментатора бывает недостаточно для обнаружения этого типа ошибок ;-)

Проявление их зависит от случайного содержания памяти на месте уничтоженного объекта. На практике - от операционки, характера работы с программой и многих непредсказуемых вещей.

>>> function TComponent._Release: Integer; вызывается, но возвращает -1 и ничего не делает.

Прокомментируйте, пожалуйста, что будет, если при вызове этого метода для уничтоженного объекта, на месте, где был FVCLComObject, окажется не nil.


26-04-2007 06:44
>Нет. Потому что тогда ...

А Вы попробовали? Почему же у меня работает?
А по ссылочке сходили?

function TComponent._Release: Integer;
begin

  if FVCLComObject = nil then
    Result := -1   // -1 indicates no reference counting is taking place
  else
    Result := IVCLComObject(FVCLComObject)._Release;
end;



То есть, вызывается, но возвращает -1 и ничего не делает.

PS. Семь раз отмерь и ... ещё раз отмерь.


25-04-2007 01:24
сообщение от автора материала
>>> Если в Вашем примере в процедуре FreeInterface убрать intfTest := nil, то всё должно работать

Нет. Потому что тогда intfTest._Release будет вызван позже, при уничтожении формы, а объекта к тому моменту существовать уже не будет. AV.

PS. Так вот пообъясняешь раз десять - и сам понимать начинаешь ;-)


24-04-2007 04:08
1.

http://www.delphimaster.ru/cgi-bin/forum.pl?id=1155200245&n=12

здесь всё разложено по полочкам.

Моё предыдущее сообщение прошу считать неверным. Поскольку сам пользую TInterfaceObject, то сработал рефлекс (не смешивать ссылки). Виноват, погорячился.

2. То Fisher
Если в Вашем примере в процедуре FreeInterface убрать intfTest := nil, то всё должно работать


23-04-2007 04:54
сообщение от автора материала
Дело  в том, что Кэнту в данном предложении говорит о наследниках TInterfacedObject. И если бы Вы привели не только первое, а все четыре предложения врезки "ВНИМАНИЕ" из раздела "Использование интерфейсов" главы 2 "Язык программирования", это стало бы понятно. А у нас речь идет о наследниках TComponent. И управление памятью в этом случае осуществляется совершенно иначе. И без смешения ссылок здесь не обойтись, хотя бы потому, что во внутренних механизмах TComponent (Owner, FreeNotification и т.д.) компоненты известны своим коллегам именно под "обычными", объектными ссылками.


23-04-2007 03:14
>не хочу оставлять без комментариев явно неверные фрагменты кода

мне кажется, что вот так нельзя:


objTest := TTest.Create(nil);
intfTest := objTest;



Основание:
При использовании объектов, основанных на интерфейсах, обращатся к ним можно только с помощью объектных ссылок или только с помощью интерфейсных ссылок.
                 (упоминавшийся здесь Кенту)


23-04-2007 01:27
сообщение от автора материала
Подобные ошибки не связаны с утечками памяти, и потому не могут быть обнаружены утилитами контроля памяти. А AV может проявляться не всегда. Зависит от операционки, фазы луны и т.д.

Поставим вопрос иначе: какая задача у Вас не получается? Какие моменты в данной статье, и упомянутых главах Кэнту Вам непонятны?


23-04-2007 01:00
Извиняюсь, но не понял к чему относится последнее "нельзя". Пожалуйста конкретнее.

У меня все примеры работают, никаких AV или утечек памяти.


20-04-2007 03:27
сообщение от автора материала
>>> можно так:

Нельзя так. Потому что при нажатии кнопочки после "Hello" получаем Privileged Instruction (предмет обсуждения статьи в self.GetInterface.Hello), а при закрытии формы - Access violation (потому что вовремя не обнуляется intfTest).

Я не совсем понимаю, на какой вопрос Вы хотите получить ответ, просто не хочу оставлять без комментариев явно неверные фрагменты кода в обсуждении моей статьи - меня самого это собъет с толку, когда я загляну сюда через пару месяцев.


20-04-2007 03:24
В принципе, для наследника TComponent метод GetSelf можно и не прописывать, а пользовать интерфейс IInterfaceComponentReference:

   intfTest := TTest.Create(nil);
  self.GetInterface.Hello;  
  IInterfaceComponentReference(intfTest).GetComponent.Free;


Но мне кажется, что жить с GetSelf удобнее/нагляднее:

   intfTest := TTest.Create(nil);
  self.GetInterface.Hello;  
  intfTest.GetSelf.Free;



20-04-2007 01:54
Если для наследника TComponent нужен Owner = nil, то можно так:

type

  ITest = interface(IInterface)
    ['{57ACE49A-3DC2-46E6-815B-834444AAD793}']
    function GetSelf: TObject;
    procedure Hello;
  end;

  TTest = class(TComponent, ITest)
    function GetSelf: TObject;
    procedure Hello;
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    objTest: TTest;
    intfTest: ITest;
    function GetInterface: ITest;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TTest }

function TTest.GetSelf: TObject;
begin
  Result:=Self;
end;

procedure TTest.Hello;
begin
  ShowMessage('Hello');
end;

function TForm1.GetInterface: ITest;
begin
  Result := self.intfTest;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  intfTest := TTest.Create(nil);
  self.GetInterface.Hello;
  intfTest.GetSelf.Free; //подсчёт ссылок не работает => удаляем объект сами

  with TButton.Create(Application) do begin
    Parent := self;
  end;

end;

end.



20-04-2007 00:59
Я лишь хотел подчеркнуть разницу в подходах. Для компоненты, у которой Owner <>nil, Free, разумеется, можно не вызывать.


19-04-2007 07:07
сообщение от автора материала
>>> А Owner'ом для компоненты можно назначить и Application, зачем же сразу nil.

Назначить можно действительно все что угодно. Но я был убежден, что назначать нужно то что нужно ;-) Зачем назначать Application, если используется явный Free? (19-04-2007 01:11, "объектный" подход)


19-04-2007 04:44
to Fisher

>Марко Кэнту в разделе "Обращение к компонентам с помощью интенрфейсов" объясняет, зачем это нужно и насколько это замечательно.

Ну так там всё и описано. Действительно замечательно.

А Owner'ом для компоненты можно назначить и Application, зачем же сразу nil.


19-04-2007 03:39
сообщение от автора материала
Ну, Owner "управляет жизнью" своих подчиненых весьма прагматично: всего лишь убивает их перед тем, как умереть самому.

Задачи, собственно, в статье не обсуждается никакой. Есть баг компилятора (или недочет принципов управления памятью, как хотите). А сам вопрос возник при использовании интерфейсов для имитации множественного наследования в компонентах с обычным (не-интерфейсным) управлением жизнью. Марко Кэнту в разделе "Обращение к компонентам с помощью интенрфейсов" объясняет, зачем это нужно и насколько это замечательно.


19-04-2007 03:15
to Fisher
>А в некоторых случаях (когда Owner грохнет объект раньше, а явный Free придет позже) будет AV.

Как же Owner может "грохнуть" объект? Он ведь на то и Owner, чтобы управлять временем жизни объекта.
Алексей, уточните, пожалуйста, задачу, похоже я чего-то не понимаю.


19-04-2007 02:50
Если всё-таки хочется "перемешать" оба подхода, то можно так:

type

  ITest = interface(IInterface)
    ['{57ACE49A-3DC2-46E6-815B-834444AAD793}']
    function GetSelf: TObject;
    procedure Hello;
  end;

  TTest = class(TComponent, ITest)
  public
    constructor Create(AOwner: TComponent); override;
    function GetSelf: TObject;
    procedure Hello;
    procedure Release;
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    objTest: TTest;
    intfTest: ITest;
    function GetInterface: ITest;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TTest }

constructor TTest.Create(AOwner: TComponent);
begin
  inherited;
  _AddRef;  // искуственно увеличиваем RefCount
end;

function TTest.GetSelf: TObject;
begin
  Result:=Self;
end;

function TForm1.GetInterface: ITest;
begin
  Result := self.intfTest;
end;

procedure TTest.Hello;
begin
  ShowMessage('Hello');
end;

procedure TTest.Release;
begin
  _Release;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  objTest := TTest.Create(self);
  intfTest := objTest;
  self.GetInterface.Hello;
  objTest.Release;  // надо "подчистить" за собой

  with TButton.Create(self) do begin
    Parent := self;
  end;

end;

end.



19-04-2007 02:42
сообщение от автора материала
Не скажу за FastMM4, но все-таки нужен nil. В обоих примерах. Не забывайте, что кроме "объектного" и "интерфейсного" механизмов управления жизнью объектов есть еще "владельческий", за который отвечает тот самый параметр AOwner:TComponent, и который тоже домешивать нельзя.

Пакость в том, что может долги и верно работать и с self. А в некоторых случаях (когда Owner грохнет объект раньше, а явный Free придет позже) будет AV.


19-04-2007 01:11
У упоминавшегося здесь Кенту читаем:

При использовании объектов, основанных на интерфейсах, обращатся к ним можно только с помощью объектных ссылок или только с помощью интерфейсных ссылок. Смешение двух подходов нарушает реализуемую Delphi работу схемы учета ссылок, и может вызвать проблемы использования памяти, которые трудно отследить.

Если я правильно понимаю, то либо "объектный" подход:


  objTest := TTest.Create(self);
  objTest.Hello;
  objTest.Free;



либо "интерфейсный":


  intfTest := TTest.Create(self);  //если вместо self использовать nil, то будет утечка памяти (FastMM4 ругается)
  self.GetInterface.Hello;



Если из интерфейса нужно получить ссылку на объект, то можно так:


type

  ITest = interface(IInterface)
    ['{57ACE49A-3DC2-46E6-815B-834444AAD793}']
    function GetSelf: TObject;
    procedure Hello;
  end;

  TTest = class(TComponent, ITest)
    function GetSelf: TObject;
    procedure Hello;
  end;

  ...
function TTest.GetSelf: TObject;
begin
  Result:=Self;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  intfTest := TTest.Create(self);
  objTest:=TTest(intfTest.GetSelf);
  objTest.Hello;
end;



16-04-2007 04:59
сообщение от автора материала
лучше все-таки избегать использования функций, возвращающих интерфейсные ссылки.

Хороший совет на будущее, но любопытства ради я проанализировал все *.pas в своем подкаталоге D7. Найдено свыше 11000 (одиннадцати тысяч) функций, возвращающих интерфейсы. В основном речь идет о

Ocx/Servers/
Imports/MSHTML_TLB.pas
Source/Internet/MSHTML.pas
Source/WebSnap/
Source/Xml/
Demos/ActiveX

так что все равно остается большая вероятность столкнуться с использованием этих вещей, не своих, так чужих.

Обнаружил еще одну хитрость в книжке у Марко Кэнту. Он всячески хвалит интерфейсные свойства (property) у компонентов, приводит примеры. Но при этом его property выглядят так:


private
  FViewer: IViewer;
  procedure SetViewer(const Value: IViewer);
published
  property Viewer: IViewer read FViewer write SetViewer;



то есть в обрамлении property он избегает функции Get, давая просто доступ к переменной, хотя среда при автогенерации создает заготовки и Set и Get функций. Не знаю, случайно так он делает или сознательно, но это еще один повод не слишком доверять автогенерации кода, и побольше думать самому.





13-04-2007 05:54
Все сказанное в статье справедливо. Хочу лишь добавить, что лучше все-таки избегать использования функций, возвращающих интерфейсные ссылки. Правильнее возвращать их через out-параметры процедур.
Если в приведенном в статье примере переменную tempIntf из функции Button3Click сделать глобальной (или объявить в private-разделе формы) - получим тот-же AV или privileged instruction. Как я понял, связано это с тем, что интерфейс возвращается не как результат функции, а через неявный параметр, автоматически создаваемый компилятором. И в случае если переменная, которой этот результат присваивается, не является локальной, компилятор создает еще и неявную переменную, которая передается в качестве этого самого неявного параметра. При выходе же этой переменной из зоны видимости происходит... сами догадываетесь что.


Добавьте свое cообщение

Вашe имя:  [Войти]
Ваш адрес (e-mail):На Королевстве все адреса защищаются от спам-роботов
контрольный вопрос:
Кто съел Красную шапочку?
в качестве ответа на вопрос или загадку следует давать только одно слово в именительном падеже и именно в такой форме, как оно используется в оригинале.
Надоело отвечать на странные вопросы? Зарегистрируйтесь на сайте.
Текст:
Жирный шрифт  Наклонный шрифт  Подчеркнутый шрифт  Выравнивание по центру  Список  Заголовок  Разделительная линия  Код  Маленький шрифт  Крупный шрифт  Цитирование блока текста  Строчное цитирование
  • вопрос Круглого стола № XXX

  • вопрос № YYY в тесте № XXX Рыцарской Квинтаны

  • сообщение № YYY в теме № XXX Базарной площади
  • обсуждение темы № YYY Базарной площади
  •  
     Правила оформления сообщений на Королевстве
      
    Время на сайте: GMT минус 5 часов

    Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
    Функция может не работать в некоторых версиях броузеров.

    Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
    Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

     
    © При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
    Все используемые на сайте торговые марки являются собственностью их производителей.

    Яндекс цитирования