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

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

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

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

Сообщение Юрий » 06.01.2024 11:40:58

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

Господа, можете подсказать, почему код ниже в 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.
Юрий
новенький
 
Сообщения: 11
Зарегистрирован: 03.04.2022 03:32:16

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

Сообщение BlackShark » 06.01.2024 12:18:58

Привет.
Убери ReleaseTable и отнаследуй от TInterfacedObject
Аватара пользователя
BlackShark
новенький
 
Сообщения: 44
Зарегистрирован: 20.05.2019 12:52:15

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

Сообщение wavebvg » 06.01.2024 18:13:15

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

Код: Выделить всё
Table := nil;


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

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

Код: Выделить всё
function Test: String;
var
  TT: IMyTable;
begin
  TT := GetTable;
  Result := TT.GetTableName;
  ReleaseTable(TT);
end; 


в RTL нужно проверять на nil при обращении к указателю на интерфейс
Код: Выделить всё
TT: IMyTable;
wavebvg
постоялец
 
Сообщения: 354
Зарегистрирован: 28.02.2008 04:57:35

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

Сообщение BlackShark » 06.01.2024 22:36:25

"В ReleaseTable должно быть.."
Да ничего там не должно быть, как и самого ReleaseTable. Зачем пытаться перекрывать функционал RTL своими костылями? RTL сама освобождает объект закрытый в интерфейсе, ТС просто вызывает излишний Free для него, с учётом того что я отметил выше про наследование от TInterfacedObject.
Аватара пользователя
BlackShark
новенький
 
Сообщения: 44
Зарегистрирован: 20.05.2019 12:52:15

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

Сообщение wavebvg » 07.01.2024 04:20:00

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


Вы ошибаетесь. Ничего RTL в данном случае не освобождает. Просто пытается вызвать Release для освобожденного Вами объекта. А т.к. Release виртуальный, то ничего не получается и падает с A/V.
А без
Код: Выделить всё
Table := nil;

на дельфах без двойной адресации памяти (привет D7) должно быть такое же A/V, поскольку будет такая же проблема с вызовом виртуального метода у освобожденного объекта.
wavebvg
постоялец
 
Сообщения: 354
Зарегистрирован: 28.02.2008 04:57:35

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

Сообщение BlackShark » 07.01.2024 15:11:51

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 15:52:35, всего редактировалось 1 раз.
Аватара пользователя
BlackShark
новенький
 
Сообщения: 44
Зарегистрирован: 20.05.2019 12:52:15

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

Сообщение sts » 07.01.2024 15:36:49

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 секунд:
кстати, вроде в режиме обычных интерфейсов фрипаскаля (а не микрософтовских) он этого не делает и исходный код Юрия будет работать
sts
постоялец
 
Сообщения: 430
Зарегистрирован: 04.04.2008 12:15:44
Откуда: Тольятти

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

Сообщение Юрий » 08.01.2024 00:45:29

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

Добавлено спустя 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 что то не понятное(нелогичное) мутит…
Юрий
новенький
 
Сообщения: 11
Зарегистрирован: 03.04.2022 03:32:16

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

Сообщение wavebvg » 08.01.2024 12:51:06

Попробуйте убрать 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 реализован.
wavebvg
постоялец
 
Сообщения: 354
Зарегистрирован: 28.02.2008 04:57:35

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

Сообщение Юрий » 08.01.2024 14:25:00

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 определены
Юрий
новенький
 
Сообщения: 11
Зарегистрирован: 03.04.2022 03:32:16

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

Сообщение sts » 09.01.2024 11:24:28

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

Код: Выделить всё
    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, в делфе только такие и есть)
sts
постоялец
 
Сообщения: 430
Зарегистрирован: 04.04.2008 12:15:44
Откуда: Тольятти


Вернуться в Free Pascal Compiler

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 1

Рейтинг@Mail.ru