Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > Delphi программирование > Общие вопросы Delphi
Регистрация

Восстановить пароль

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 05.09.2010, 23:52   #1
Altera
Старожил
 
Аватар для Altera
 
Регистрация: 29.01.2008
Сообщений: 2,406
По умолчанию Неприятная ситуация с самоуничтожающимеся интерфейсами

Всем привет!
Приведу прямо здесь небольшой код, который описывает проблемку с которой я вчера столкнулся.
Допустим имеет место следующая структура программы:
Код:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  IPrimaryObject = interface
    procedure SomeProcedure;
  end;

  TPrimaryObject = class(TInterfacedObject, IPrimaryObject)
  protected
    fList: tList;

    procedure SomeProcedure;
  private
    constructor create;
    destructor destroy; override;
  end;

  TSecondaryObject = class
  protected
    fPrimaryObject: IPrimaryObject;
  public
    procedure SetInterface(const aPrimaryObject: IPrimaryObject);

    procedure CallSomeProcedure;
  end;


  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    PrimaryObject: TPrimaryObject;
    SecondaryObject: TSecondaryObject;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  SecondaryObject.CallSomeProcedure;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  SecondaryObject := TSecondaryObject.create;
  PrimaryObject := TPrimaryObject.create;

  SecondaryObject.SetInterface(PrimaryObject);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  PrimaryObject.Free;
  SecondaryObject.Free;
end;

{ TPrimaryObject }

constructor TPrimaryObject.create;
begin
  fList := tList.Create;
end;

destructor TPrimaryObject.destroy;
begin
  fList.Free;
  inherited;
end;

procedure TPrimaryObject.SomeProcedure;
begin
  ShowMessage('Hello World!');
end;

{ TSecondaryObject }

procedure TSecondaryObject.CallSomeProcedure;
begin
  fPrimaryObject.SomeProcedure;
end;

procedure TSecondaryObject.SetInterface(const aPrimaryObject: IPrimaryObject);
begin
  fPrimaryObject := aPrimaryObject;
end;

end.
Есть Первостепенный и Второстепенный объекты.
Первостепенный, он у нас главный, имеет свой интерфейс управления, который, который передаётся второстепенным объектам. (В роле второстепенного объекта может быть скажем GUI модуль, а в роли первостепенного - исполнительное ядро)
Короче, TSecondaryObject принимает ссылку на интерфейт aka экземпляр объекта и хранит её в себе. Вся суть в том, что Delphi, по чьей-то милости, ведёт подсчёт ссылок на экземпляры объектов интерфейсов и уничтожает те, на которые никто не ссылается. Но я и сам их уничтожаю неплохо, паричём мне нужно уничтожить их не в тот момент, когда этого Delphi захочется, а когда это нужно. Вообщем при закрытии программы получаес AV, т.к. дважды вызывается TPrimaryObject.destroy (первый раз я его сам вызываю, второй раз он вызывается при уничтожени второстепенного объекта)
Есть 2 выхода:
Прописать в TPrimaryObject пустые _AddRef и _Release, ну или просто _Release, или не вызывать вручную TPrimaryObject.free;
Лично я реализовал первое.

Внимание, вопрос! Нету там случай-но каких-0нить директив компилятора, что-бы Delphi не добавляла бы ненужный код там где он как-бы не нужен?
Вложения
Тип файла: rar Ляп с интерфейсами.rar (179.1 Кб, 10 просмотров)
Altera вне форума Ответить с цитированием
Старый 06.09.2010, 00:14   #2
mutabor
Телепат с дипломом
Старожил
 
Аватар для mutabor
 
Регистрация: 10.06.2007
Сообщений: 4,929
По умолчанию

С интерфейсами не работал почти. Но ты не путаешь? Для простых объектов в Дельфи нет сборки мусора, откуда она будет для интерфейсных? А при закрытии программы и обычные объекты (у к-рых есть Owner) автоматом уничтожаются, Owner вызывает по цепочке Destroy методы, и если объект до этого вручную уничтожишь, точно так же получишь AV.

p.s. Я мог тут ерунды написать, а может и нет, может идея какая появится.
The future is not a tablet with a 9" screen no more than the future was a 9" black & white screen in a box. It’s the paradigm that survives. (Kroc Camen)
Проверь себя! Онлайн тестирование | Мой блог

Последний раз редактировалось mutabor; 06.09.2010 в 00:19.
mutabor вне форума Ответить с цитированием
Старый 06.09.2010, 10:00   #3
Altera
Старожил
 
Аватар для Altera
 
Регистрация: 29.01.2008
Сообщений: 2,406
По умолчанию

Цитата:
Сообщение от mutabor Посмотреть сообщение
С интерфейсами не работал почти. Но ты не путаешь? Для простых объектов в Дельфи нет сборки мусора, откуда она будет для интерфейсных? А при закрытии программы и обычные объекты (у к-рых есть Owner) автоматом уничтожаются, Owner вызывает по цепочке Destroy методы, и если объект до этого вручную уничтожишь, точно так же получишь AV.

p.s. Я мог тут ерунды написать, а может и нет, может идея какая появится.
Есть такой класс: TInterfacedObject, который реализует IInterface

Код:
IInterface = interface
    ['{00000000-0000-0000-C000-000000000046}']
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  end;

TInterfacedObject = class(TObject, IInterface)
  protected
    FRefCount: Integer;
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  public
    procedure AfterConstruction; override;
    procedure BeforeDestruction; override;
    class function NewInstance: TObject; override;
    property RefCount: Integer read FRefCount;
  end;
...

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

function TInterfacedObject._Release: Integer;
begin
  Result := InterlockedDecrement(FRefCount);
  if Result = 0 then
    Destroy;
end;
Как только появляется новая ссылка на на интерфейс, сразу-же вызывается _AddRef, а как только ссылка пропадает - _Release

Т.е. вот в таком коде утечек памяти не будет:
Код:
procedure DoSomthing;
var
  BaseInterface: IInterface;
begin
  BaseInterface := TInterfacedObject.create;
end;
Возможно такой финт нужен при работе с ActiveX и т.п. Там наверное интерфейсы самоуничтожающиеся, но в обычном приложении...
Altera вне форума Ответить с цитированием
Старый 06.09.2010, 10:25   #4
Sanprof
Форумчанин
 
Аватар для Sanprof
 
Регистрация: 28.01.2008
Сообщений: 267
По умолчанию

Если вы читали внимательно документацию по интерфейсам, то там написано, что категорически запрещено освобождать ссылки на интерфейс методом Free, только присвоением ссылке nil.
Код:
ComInter: Variant;
.....
ComInter := CreateComObJect(CLASS_ВашКоКласс) as IВашКласс;
ComInter.Free; //<- так нельзя
ComInter := nil; // а так корректно
Не забываем говорить спасибо за помощь - это ведь так приятно
Sanprof вне форума Ответить с цитированием
Старый 06.09.2010, 11:32   #5
Altera
Старожил
 
Аватар для Altera
 
Регистрация: 29.01.2008
Сообщений: 2,406
По умолчанию

Цитата:
Сообщение от Sanprof Посмотреть сообщение
Если вы читали внимательно документацию по интерфейсам, то там написано, что категорически запрещено освобождать ссылки на интерфейс методом Free, только присвоением ссылке nil.
Код:
ComInter: Variant;
.....
ComInter := CreateComObJect(CLASS_ВашКоКласс) as IВашКласс;
ComInter.Free; //<- так нельзя
ComInter := nil; // а так корректно
Это я видел. А ты чем тут клаву топтать, взял бы и поробовал. Я же сказал что имеет большое значение порядок уничтожения объектов. Твоим способом порядок нарушается.

Попробуй своим способом сначала уничтожить первый объект, а потом уже второй

Код:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  IPrimaryObject = interface
    procedure SomeProcedure;
  end;

  TPrimaryObject = class(TInterfacedObject, IPrimaryObject)
  protected
    fList: tList;

    procedure SomeProcedure;
  private
    constructor create;
    destructor destroy; override;
  end;

  TSecondaryObject = class
  protected
    fPrimaryObject: IPrimaryObject;
  public
    destructor destroy; override;

    procedure SetInterface(const aPrimaryObject: IPrimaryObject);

    procedure CallSomeProcedure;
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
  private
    { Private declarations }
    PrimaryObject: TPrimaryObject;
    SecondaryObject: TSecondaryObject;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  SecondaryObject.CallSomeProcedure;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  try

  PrimaryObject := nil;
  SecondaryObject.Free;

  finally
    halt;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  SecondaryObject := TSecondaryObject.create;
  PrimaryObject := TPrimaryObject.create;

  SecondaryObject.SetInterface(PrimaryObject);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin

end;

{ TPrimaryObject }

constructor TPrimaryObject.create;
begin
  fList := tList.Create;
end;

destructor TPrimaryObject.destroy;
begin
  showMessage('Скачало уничтожаем TPrimaryObject');
  fList.Free;
  inherited;
end;

procedure TPrimaryObject.SomeProcedure;
begin
  ShowMessage('Hello World!');
end;

{ TSecondaryObject }

procedure TSecondaryObject.CallSomeProcedure;
begin
  fPrimaryObject.SomeProcedure;
end;

destructor TSecondaryObject.destroy;
begin
  showMessage('А уже потом TSecondaryObject');
  inherited;
end;

procedure TSecondaryObject.SetInterface(const aPrimaryObject: IPrimaryObject);
begin
  fPrimaryObject := aPrimaryObject;
end;

end.
И это ещё простая ситуация. А представь, если несколько объектов обмениваются друг с другом своими интерфейсами?
+++++++++++++++++
Очень плохой способ. Вообще не годится. Хрен поймёшь когда он что уничтожает. Совершенно не в том порядке в котором нужно. Если ничего более существенное предложить не можете....

Последний раз редактировалось Altera; 06.09.2010 в 11:49.
Altera вне форума Ответить с цитированием
Старый 06.09.2010, 11:47   #6
Sanprof
Форумчанин
 
Аватар для Sanprof
 
Регистрация: 28.01.2008
Сообщений: 267
По умолчанию

Код:
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  try

  SecondaryObject := nil;//не уверен но походу надо так
  PrimaryObject := nil;
  //SecondaryObject.Free; <-я так понимаю на этой строке AV
  
  finally
    halt;
  end;
end;
Не забываем говорить спасибо за помощь - это ведь так приятно
Sanprof вне форума Ответить с цитированием
Старый 06.09.2010, 12:02   #7
Sibedir
Тот ещё
Старожил
 
Аватар для Sibedir
 
Регистрация: 14.11.2007
Сообщений: 2,242
По умолчанию

Во-первых, Altera, я категорически одобряю ваше негодование. Вообще, чем больше занимаюсь ООП, тем меньше оно мне нравится. И это не веяния моды или понты. Сам до этого дошел, а уж потом стали и статейки соответствующие в нэте попадаться.
Вот из-за таких "генеальных" разработчиков технологий программирования по настоящему толковых прогеров по пальцам пересчитать. Понапихают ненужого кода в мою же программу, а я мож и не хочу. Так нет же блин.
А еще после таких вот приколов и начинаешь замечать в диспетчере задачь давно закрытые Word'ы, AcrobatReader'ы и иже с ними...
Я чё это все вслух сказал?
Так с гневными мыслями покончено, к делу.

Во-вторых:
Не понял, чем вам не нравится := nil вместо .Free. Я в вашем первом коде, по совету Sanprof, всего одну строчку подправил
Код:
  PrimaryObject := nil;
и все заработало.

В-третьих:
Вы вроди не маленький уже. Я вас как форумчанина давно знаю, самого первого прихода (Ну да-да, каюсь. Были у меня приходы на форуме. Но я ж не один такой). А наступаете, мне так по крайней мере кажется, на грабли новичков (Надо будет, кстати, Alar'у сказать, чтоб он грабли подписал. А то бирут все подряд, скоро замутызгают). Расскажите нам, как на духу, в чем ваша проблема. Может она вкорни по другому решается.

Последний раз редактировалось Sibedir; 06.09.2010 в 12:04.
Sibedir вне форума Ответить с цитированием
Старый 06.09.2010, 12:18   #8
evg_m
Старожил
 
Регистрация: 20.04.2008
Сообщений: 5,543
По умолчанию

Код:
TSecondaryObject = class
  protected
    fPrimaryObject: IPrimaryObject;
  public
    procedure SetInterface(const aPrimaryObject: IPrimaryObject);

    procedure CallSomeProcedure;
  end;
если мы используем интерфейс
Код:
  fPrimaryObject: IPrimaryObject;
  public
    procedure SetInterface(const aPrimaryObject: IPrimaryObject);
то мы его должны и освободить (например при разрушении объекта)

Код:
procedure TSecondaryObject.Destroy;
begin 
  fPrimaryObject:=nil; 
end;
а уж TPrimary сам разрушится в СООТВЕТСТВИИ с логикой и правилами интерфейсных объектов

Код:
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  try

  PrimaryObject := nil; //освободили одну ссылку 
  SecondaryObject.Free; // освободили другую ссылку (см. Destoy);
// есои переставить ничего не изменииться 

  finally
    halt;
  end;
end;
программа — запись алгоритма на языке понятном транслятору
evg_m на форуме Ответить с цитированием
Старый 06.09.2010, 12:28   #9
Altera
Старожил
 
Аватар для Altera
 
Регистрация: 29.01.2008
Сообщений: 2,406
По умолчанию

У меня программа разделена на модули, которые общаются между собой по средствам интерфейсов. //Тут же всё понятно?
Т.к. модуль А пользуется интерфейсом модуля Б, поэтому надо сначала модуль А уничтожать, а потом уже Б. А тут получается, если nil присваивать, что сначала Б уничтожается, а потом уже А. При уничтожении А вызываются функции которые используют тот самый интерфейс модуля Б и там возникает AV //Что здесь не понятно. Да. в вышепреведённом коде AV не возникает, но это просто модель.

Вот попробуйте скомпилить код в посте #5. Увидете, что сообщения показываются не в нужном порядке.

Я рушил это проблему просто описав пустые методы _addRef и _Release. Меня и так устраивает потому что утечек памяти нету. Но я спрашиваю, более красивого способа нету?
Altera вне форума Ответить с цитированием
Старый 06.09.2010, 12:46   #10
Sanprof
Форумчанин
 
Аватар для Sanprof
 
Регистрация: 28.01.2008
Сообщений: 267
По умолчанию

Цитата:
Сообщение от Altera Посмотреть сообщение
Увидете, что сообщения показываются не в нужном порядке.
а по другому и не получится, так как сначала вызывается деструктор TSecondaryObject, а уже потом TPrimaryObject, а происходит это потому, что TPrimaryObject держит ссылку на интерфейс.
Не забываем говорить спасибо за помощь - это ведь так приятно
Sanprof вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц



Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
статья - Взаимодействие с сетевыми интерфейсами в Linux Pblog Обсуждение статей 0 23.06.2010 05:13
Ситуация: Ekspert Операционные системы общие вопросы 4 28.03.2010 11:47
исключительная ситуация Psicheja Помощь студентам 1 18.03.2010 23:40
Исключительная ситуация Delphi VadEr Помощь студентам 1 20.09.2009 20:22