Проблема освобождения объекта "под интерфейсом" в FPC

Вопросы программирования на Free Pascal, использования компилятора и утилит.

Модератор: Модераторы

Ответить
Юрий
новенький
Сообщения: 11
Зарегистрирован: 03.04.2022 02:32:16

Проблема освобождения объекта "под интерфейсом" в FPC

Сообщение Юрий »

Добрый день, всем

Господа, можете подсказать, почему код ниже в Lazarus выдаёт ошибку а в Delphi (любой версии) нет ?

Вызов uTest3.Test;
В FPC на строке T.Free; будет Access violation (а точнее не сразу а после выхода из функции)
Соответственно если закоментить то всё работает но утечка...(

Код: Выделить всё

unit uTest3;

interface

uses
  Classes, SysUtils;

type
  IMyTable = interface
    function GetTableName: String;
    function GetImplementor: TObject;
  end;

  TMyXXXTable = class(TObject, IMyTable)
  private
    function GetTableName: String;
    function GetImplementor: TObject;
  protected
    function QueryInterface({$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} IID: TGUID; out Obj): Longint; virtual; stdcall;
    function _AddRef: Integer; virtual; stdcall;
    function _Release: Integer; virtual; stdcall;
  end;


function Test: String;


implementation

{ TMyXXXTable }

function TMyXXXTable.GetTableName: String;
begin
  Result := 'XXX';
end;

function TMyXXXTable.GetImplementor: TObject;
begin
  Result := Self;
end;

function TMyXXXTable.QueryInterface({$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} IID: TGUID; out Obj): Longint; stdcall;
begin
  if GetInterface(IID, Obj) then
    Result := S_OK
  else
    Result := E_NOINTERFACE;
end;

function TMyXXXTable._AddRef: Integer; stdcall;
begin
  Result := -1;
end;

function TMyXXXTable._Release: Integer; stdcall;
begin
  Result := -1;
end;


function GetTable: IMyTable;
begin
  Result := TMyXXXTable.Create;
end;

procedure ReleaseTable(var Table: IMyTable);
var
  T: TMyXXXTable;
begin
  if Table.GetImplementor is TMyXXXTable then
  begin
    T := Table.GetImplementor as TMyXXXTable;
    T.Free;       // FPC ???
  end;
end;

function Test: String;
var
  TT: IMyTable;
begin
  TT := GetTable;
  Result := TT.GetTableName;
  ReleaseTable(TT);
end;

end.
Аватара пользователя
BlackShark
новенький
Сообщения: 44
Зарегистрирован: 20.05.2019 11:52:15
Контактная информация:

Сообщение BlackShark »

Привет.
Убери ReleaseTable и отнаследуй от TInterfacedObject
wavebvg
постоялец
Сообщения: 355
Зарегистрирован: 28.02.2008 03:57:35

Сообщение wavebvg »

В ReleaseTable должно быть

Код: Выделить всё

Table := nil;
Но всё равно не работает. Я бы назвал такую ситуацию "ошибкой RTL", потому что нет причин не работать.

При выходе из блока

Код: Выделить всё

function Test: String;
var
  TT: IMyTable;
begin
  TT := GetTable;
  Result := TT.GetTableName;
  ReleaseTable(TT);
end;  
в RTL нужно проверять на nil при обращении к указателю на интерфейс

Код: Выделить всё

TT: IMyTable;
Аватара пользователя
BlackShark
новенький
Сообщения: 44
Зарегистрирован: 20.05.2019 11:52:15
Контактная информация:

Сообщение BlackShark »

"В ReleaseTable должно быть.."
Да ничего там не должно быть, как и самого ReleaseTable. Зачем пытаться перекрывать функционал RTL своими костылями? RTL сама освобождает объект закрытый в интерфейсе, ТС просто вызывает излишний Free для него, с учётом того что я отметил выше про наследование от TInterfacedObject.
wavebvg
постоялец
Сообщения: 355
Зарегистрирован: 28.02.2008 03:57:35

Сообщение wavebvg »

BlackShark писал(а):"В ReleaseTable должно быть.."
Да ничего там не должно быть, как и самого ReleaseTable. Зачем пытаться перекрывать функционал RTL своими костылями? RTL сама освобождает объект закрытый в интерфейсе, ТС просто вызывает излишний Free для него, с учётом того что я отметил выше про наследование от TInterfacedObject.
Вы ошибаетесь. Ничего RTL в данном случае не освобождает. Просто пытается вызвать Release для освобожденного Вами объекта. А т.к. Release виртуальный, то ничего не получается и падает с A/V.
А без

Код: Выделить всё

Table := nil;
на дельфах без двойной адресации памяти (привет D7) должно быть такое же A/V, поскольку будет такая же проблема с вызовом виртуального метода у освобожденного объекта.
Аватара пользователя
BlackShark
новенький
Сообщения: 44
Зарегистрирован: 20.05.2019 11:52:15
Контактная информация:

Сообщение BlackShark »

wavebvg писал(а): Вы ошибаетесь. Ничего RTL в данном случае не освобождает. Просто пытается вызвать Release для освобожденного Вами объекта. А т.к. Release виртуальный, то ничего не получается и падает с A/V.
Вот, работает как и ожидается, не пойму зачем вообще эти длинные рассуждения ни о чём:

Код: Выделить всё

program Project1;

uses
	SysUtils;

type
  ITable = interface
  ['{7FB67FEA-2FF7-45FD-9FF6-503B0B062134}']
  	function GetTableName: String;
  	function GetImplementor: TObject;
  end;

  { TTable }

  TTable = class(TInterfacedObject, ITable)
  private
    function GetTableName: String;
    function GetImplementor: TObject;
  public
  	destructor Destroy; override;
  end;

{ TTable }

function TTable.GetTableName: String;
begin
	Result := 'SomeTable';
end;

function TTable.GetImplementor: TObject;
begin
	Result := Self;
end;

destructor TTable.Destroy;
begin
  //raise Exception.Create('TTable.Destroy');
  inherited Destroy;
end;

var
  Table: ITable;
begin
  Table := TTable.Create;
  WriteLn(Table.GetTableName);
  WriteLn('Press "Enter" for exit');
  ReadLn;
end.
Последний раз редактировалось BlackShark 07.01.2024 14:52:35, всего редактировалось 1 раз.
sts
энтузиаст
Сообщения: 519
Зарегистрирован: 04.04.2008 12:15:44
Откуда: Тольятти

Сообщение sts »

BlackShark писал(а):
wavebvg писал(а): Вы ошибаетесь. Ничего RTL в данном случае не освобождает. Просто пытается вызвать Release для освобожденного Вами объекта. А т.к. Release виртуальный, то ничего не получается и падает с A/V.
Вот, работает как и ожидается, не пойму о чём вообще эти длинные рассуждения ни о чём:

Код: Выделить всё

program Project1;

uses
	SysUtils;

type
  ITable = interface
  ['{7FB67FEA-2FF7-45FD-9FF6-503B0B062134}']
  	function GetTableName: String;
  	function GetImplementor: TObject;
  end;

  { TTable }

  TTable = class(TInterfacedObject, ITable)
  private
    function GetTableName: String;
    function GetImplementor: TObject;
  public
  	destructor Destroy; override;
  end;

{ TTable }

function TTable.GetTableName: String;
begin
	Result := 'SomeTable';
end;

function TTable.GetImplementor: TObject;
begin
	Result := Self;
end;

destructor TTable.Destroy;
begin
  //raise Exception.Create('TTable.Destroy');
  inherited Destroy;
end;

var
  Table: ITable;
begin
  Table := TTable.Create;
  WriteLn(Table.GetTableName);
  WriteLn('Press "Enter" for exit');
  ReadLn;
end.
wavebvg все верно написал, компилятор добавляет Table.Release между RedaLn и end.
в примере Юрия между ReleaseTable(TT); и end; и так как TT уже Free то возникает ошибка.

Добавлено спустя 12 минут 25 секунд:
кстати, вроде в режиме обычных интерфейсов фрипаскаля (а не микрософтовских) он этого не делает и исходный код Юрия будет работать
Юрий
новенький
Сообщения: 11
Зарегистрирован: 03.04.2022 02:32:16

Сообщение Юрий »

Господа, приветствую! С праздниками вас.
Господа, спасибо за ответы.

Добавлено спустя 1 минуту 44 секунды:
wavebvg писал(а):В ReleaseTable должно быть

Код: Выделить всё
Table := nil;
Вы абсолютно правы, в оригинальном коде это есть…
Но так как я хотел минимальным кодом поймать суть ошибки, для тестового юнита uTest3, я удалил “всё лишнее”. Иначе кода было бы много, м не вообще бы никто не ответил…

Добавлено спустя 4 минуты 45 секунд:
BlackShark писал(а):"В ReleaseTable должно быть.."
Да ничего там не должно быть, как и самого ReleaseTable. Зачем пытаться перекрывать функционал RTL своими костылями? RTL сама освобождает объект закрытый в интерфейсе, ТС просто вызывает излишний Free для него, с учётом того что я отметил выше про наследование от TInterfacedObject.
BlackShark, приветствую ситуация такова, два фактора почему я не могу так сделать в ориг.коде
  • 1) Я портирую код. Не пишу… При изменении интерфейса, придётся изменять кучу зависимостей от него, а это внешние компоненты, это просто писец сколько работы…
    + изменение наследника TInterfacedObject, я даже не могу просчитать последствия
    2) В самом ReleaseTable есть код, который там что-то буферизует, куски всего содержания куда-то сохраняет, чтобы след. раз это не создавать… я для наглядности это выкинул из uTest3. Т.е. сделать автопотчёт ссылок и отдать эту логику на неё не получится… (вернее оно то получится, работы только много)
Т.е. сейчас я теряю 64байта на каждый вызов, и это как бы не критично для x64. Чисто идеологически напрягает, непонятно почему…

Добавлено спустя 13 минут 14 секунд:
sts писал(а):wavebvg все верно написал, компилятор добавляет Table.Release между RedaLn и end.
в примере Юрия между ReleaseTable(TT); и end; и так как TT уже Free то возникает ошибка.

Вот посмотрите вызов ниже не выдаё ошибку

Код: Выделить всё

function Test: String;
var
  TT: IMyTable;
  T: TObject;
begin
  TT := GetTable;
  Result := TT.GetTableName;

{$IFnDEF FPC}
  ReleaseTable(TT);
{$ELSE}
  T := TT.GetImplementor;
  TT := nil;
  T.Free;
{$ENDIF}
end;    
А ведь все одно и тоже... что и в ReleaseTable

кстати если закоментить TT := nil;

Код: Выделить всё

  T := TT.GetImplementor;
  //TT := nil;
  T.Free;
То снова выдает (

Я думаю что в FPC гдето то косяк...

Добавлено спустя 16 минут 55 секунд:
И вот ещё интересная ситуация

Если Ексепшен выловить

Код: Выделить всё

  try
    xxx := uTest3.Test;
  except
  end;  
То это сработает и утечки не будет…

А вот так Ексепшен не ловится :shock:

Код: Выделить всё

function Test: String;
var
  TT: IMyTable;
begin
  try
    TT := GetTable;
    Result := TT.GetTableName;
    ReleaseTable(TT);
  except
  end;
end;    
при этом несмотря на Ексепшен утечки тоже не будет…

т.е. FPC что то не понятное(нелогичное) мутит…
wavebvg
постоялец
Сообщения: 355
Зарегистрирован: 28.02.2008 03:57:35

Сообщение wavebvg »

Попробуйте убрать virtual вот тут

Код: Выделить всё

    function QueryInterface({$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} IID: TGUID; out Obj): Longint; virtual; stdcall;
    function _AddRef: Integer; virtual; stdcall;
    function _Release: Integer; virtual; stdcall;
Должно помочь. Но я не знаю, как вызов stdcall в asm реализован.
Юрий
новенький
Сообщения: 11
Зарегистрирован: 03.04.2022 02:32:16

Сообщение Юрий »

wavebvg писал(а):Попробуйте убрать virtual вот тут

Код: Выделить всё

    function QueryInterface({$IFDEF FPC_HAS_CONSTREF}constref{$ELSE}const{$ENDIF} IID: TGUID; out Obj): Longint; virtual; stdcall;
    function _AddRef: Integer; virtual; stdcall;
    function _Release: Integer; virtual; stdcall;
Должно помочь. Но я не знаю, как вызов stdcall в asm реализован.
Попробовал - также Access violation
Попробовал virtual; на dynamic; заменить, тоже не помогло...

stdcall заменить/удалить не даёт в они IUnknown определены
sts
энтузиаст
Сообщения: 519
Зарегистрирован: 04.04.2008 12:15:44
Откуда: Тольятти

Сообщение sts »

Юрий писал(а):
Вот посмотрите вызов ниже не выдаё ошибку

Код: Выделить всё

    function Test: String;
    var
      TT: IMyTable;
      T: TObject;
    begin
      TT := GetTable;
      Result := TT.GetTableName;

    {$IFnDEF FPC}
      ReleaseTable(TT);
    {$ELSE}
      T := TT.GetImplementor;
      TT := nil;
      T.Free;
    {$ENDIF}
    end;   

А ведь все одно и тоже... что и в ReleaseTable

кстати если закоментить TT := nil;

Код: Выделить всё

      T := TT.GetImplementor;
      //TT := nil;
      T.Free;
То снова выдает
все верно работает, также как и на делфе, он вставляет TT.Release перед TT := nil; а если TT := nil; закомментить то между T.Free; и end;

Добавлено спустя 9 минут 2 секунды:
т.е. перед выходом из блока с var, в данном случае функции Test компилятор зануляет (:= nil) переменные с типом интерфейс а перед этим вызывает .Release (в случае если включен режим микрософтовские интерфейсы у которых есть минимум два метода AddRef и Release которые он вызывает каждый раз когда присваивается переменная, когда конкретным значением то AddRef, когда nil Release, в делфе только такие и есть)
Ответить