Алексей Михайличенко дата публикации 13-04-2007 02:16 КАТЕГОРИЯ | | КОМПИЛЯТОР.Интерфейсы.Access Violation в _IntfClear для TComponent | ПРОДУКТ | | Delphi 7 | ПЛАТФОРМА | | Windows |
Речь идет об использовании интерфейсных ссылок на объекты — потомки TComponent. В отличие от потомков TInterfacedObject, уничтожение которых происходит автоматически по подсчету ссылок, потомки TComponent уничтожаются другими методами, обычно через владельца (Owner) или вручную, а использование интерфейсных ссылок на них применяется как аналог множественного наследования.
Однако известно, что для интерфейсных ссылок на потомков TComponent все
равно происходит вызов _Release и _IntfClear при присваивании nil или выходе локальных переменных из зоны видимости. При этом, если интерфейсная ссылка является "мусорной" (указывает на уже разрушенный объект), то происходит Access Violation. Общепринятой практикой является присваивание nil всем интерфейсным ссылкам на объект до его разрушения,чтобы не допустить появления "мусорных" ссылок, примерно по такому алгоритму:
- Инициализация объектов-реализаторов и интерфейсных ссылок
- Использование интерфейсов
- Присвоение nil всем интерфейсным ссылкам
- Уничтожение объектов
Оказывается, что даже соблюдение этого правила не всегда спасает от
Access Violation. Дело в том, что если на шаге 2 (Использование
интерфейсов) используются конструкция, подобная следующей:
procedure Test
begin
...
FuncGetInterface.DoInterfaceMethod(...);
...
end; <<-- здесь Access Violation
|
|
то есть, если есть функция, возвращающая интерфейс для вызова какого-либо метода интерфейса, то при завершении процедуры Test мы получим Access Violation в System._IntfClear. Дело в том, что при вызове функции, возвращающей интерфейс, создается "невидимая" переменная - интерфейсная ссылка, для которой в конце процедуры Test, в связи с выходом ее из зоны видимости, вызывается _IntfClear. А объект к этому моменту уже разрушен.
Пример для воспроизведения проблемы:
(на форме Form1 две кнопки - Button2 и Button3)
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
ITest = interface
procedure Hello;
end;
TTest = class(TComponent, ITest)
procedure Hello;
end;
TForm1 = class(TForm)
Button2: TButton;
Button3: TButton;
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
private
public
objTest: TTest;
intfTest: ITest;
procedure InitInterface;
procedure FreeInterface;
function GetInterface: ITest;
возвращающая интерфейс
end;
var
Form1: TForm1;
implementation
procedure TTest.Hello;
begin
ShowMessage('Hello');
end;
procedure TForm1.InitInterface;
begin
objTest := TTest.Create(nil);
intfTest := objTest;
end;
procedure TForm1.FreeInterface;
begin
intfTest := nil;
objTest.Free;
end;
function TForm1.GetInterface: ITest;
begin
Result := self.intfTest;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
self.InitInterface;
self.GetInterface.Hello;
self.FreeInterface;
with TButton.Create(self) do begin
Parent := self;
end;
end;
procedure TForm1.Button3Click(Sender: TObject);
var tempIntf: ITest;
begin
self.InitInterface;
tempIntf := self.GetInterface;
tempIntf.Hello;
tempIntf := nil;
self.FreeInterface;
with TButton.Create(self) do begin
Parent := self;
end;
end;
end.
|
|
Для обхода проблемы можно создавать временную переменную — интерфейсную ссылку, и присваивать ей nil явно, до уничтожения объекта.
var TempIntf: MyInterface;
TempIntf := FuncGetInterface;
TempIntf.DoInterfaceMethod(...);
TempIntf := nil;
|
|
[TComponent] [Жизненный цикл] [Интерфейсы]
Обсуждение материала [ 15-06-2008 15:08 ] 50 сообщений |