2014-04-03 4 views
4

Сравните эти два фрагмента:Почему этот TStreamAdapter не выпущен?

(d as IPersistStream).Save(
    TStreamAdapter.Create(
    TFileStream.Create('test.bin',fmCreate),soOwned),true); 
(d as IPersistStream).Load(
    TStreamAdapter.Create(
    TFileStream.Create('test.bin',fmOpenRead),soOwned)); 

Это терпит неудачу на втором TFileStream.Create, потому что первый не разрушается. Это странно, так как параметр имеет единственную ссылку, я думал, что он будет уничтожен при закрытии вызова Save. Поэтому я пробовал это:

var 
    x:IStream; 
begin 
    x:=TStreamAdapter.Create(
    TFileStream.Create('test.bin',fmCreate),soOwned); 
    (d as IPersistStream).Save(x,true); 
    x:=nil; 
    x:=TStreamAdapter.Create(
    TFileStream.Create('test.bin',fmOpenRead),soOwned); 
    (d as IPersistStream).Load(x); 
    x:=nil; 

Который работает нормально. (Но снова сработает без x:=nil;) Так что не волнуйтесь о d, это is a IPersistStream и ведет себя правильно. Почему для принудительного вызова _Release требуется явное назначение nil? Это проблема с Delphi 7? Это из-за переключателя компоновщика/компилятора?

+0

Было бы действительно очень легко сделать SSCCE. Теперь я должен это сделать. И так есть кто-то еще, кто хочет запустить код. На самом деле, я не уверен, что меня даже могут беспокоить. Не могли бы вы это сделать? На самом деле, я знаю ответ, ничего не работая. Тебе повезло! –

+0

Смотрите, я узнаю что-то новое и неожиданное с каждым вопросом, который я задал. Не знал о SSCCE. Мой на основе https://github.com/stijnsanders/TMongoWire/blob/master/bsonDoc.pas см. Здесь https://gist.github.com/stijnsanders/9960826 –

+0

Я добавил SSCCE к моему ответу. –

ответ

5

Вот декларация IPersistStream.Save:

function Save(const stm: IStream; fClearDirty: BOOL): HResult; stdcall; 

Ключевым моментом является то, что параметр потока передается как const. Это означает, что функция Save не принимает ссылки на интерфейс IStream. Его счетчик ссылок не увеличивается и не уменьшается. И так как это не происходит, оно никогда не разрушается.

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

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

x := TStreamAdapter.Create(
    TFileStream.Create('test.bin',fmOpenRead),soOwned 
); 

Это происходит в следующем порядке:

  1. TFileStream.Create.
  2. TStreamAdapter.Create.
  3. x._Release очистить старую ссылку.
  4. Отправьте ссылку на новый IStream.

И это явно не в порядке. Перед вызовом TFileStream.Create необходимо очистить x.


По словам бывшего инженера компилятора Embarcadero, Барри Келли, the issue regarding the interface passed to a const parameter is a bug. Это никогда не было исправлено, и я отказался от надежды на это.

Мой SSCCE показать вопрос здесь:

program SO22846335; 

{$APPTYPE CONSOLE} 

type 
    TMyInterfaceObject = class(TObject, IInterface) 
    FRefCount: Integer; 
    FName: string; 
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall; 
    function _AddRef: Integer; stdcall; 
    function _Release: Integer; stdcall; 
    constructor Create(const Name: string); 
    destructor Destroy; override; 
    end; 

constructor TMyInterfaceObject.Create(const Name: string); 
begin 
    inherited Create; 
    FName := Name; 
    Writeln(FName + ' created'); 
end; 

destructor TMyInterfaceObject.Destroy; 
begin 
    Writeln(FName + ' destroyed'); 
    inherited; 
end; 

function TMyInterfaceObject.QueryInterface(const IID: TGUID; out Obj): HResult; 
begin 
    Result := E_NOINTERFACE; 
end; 

function TMyInterfaceObject._AddRef: Integer; 
begin 
    Writeln(FName + ' _AddRef'); 
    Result := AtomicIncrement(FRefCount); 
end; 

function TMyInterfaceObject._Release: Integer; 
begin 
    Writeln(FName + ' _Release'); 
    Result := AtomicDecrement(FRefCount); 
    if Result = 0 then 
    Destroy; 
end; 

procedure Foo(const Intf: IInterface); 
begin 
    Writeln('Foo'); 
end; 

procedure Bar(Intf: IInterface); 
begin 
    Writeln('Bar'); 
end; 

begin 
    Foo(TMyInterfaceObject.Create('Instance1')); 
    Bar(TMyInterfaceObject.Create('Instance2')); 
    Readln; 
end. 

Выход

 
Instance1 created 
Foo 
Instance2 created 
Instance2 _AddRef 
Bar 
Instance2 _Release 
Instance2 destroyed 
+0

@ user246408 Да, удаление 'const' в функции' Save' решит проблему. Затем будет сделана ссылка, когда начнется «Сохранить», и тогда эта ссылка будет выпущена при возврате «Сохранить». В этот момент объект не будет иметь ссылок и будет уничтожен. –

+1

Возможно, это верно для конкретного 1-го фрагмента OP, но, как правило, компилятор Delphi гарантирует, что ссылки на интерфейс мусора заполняются, когда они выходят из области видимости, и если вы создаете SSCCE, который решает проблему, удалив спецификатор 'const' I верю, что я могу легко создать счетчик SSCCE, который все еще имеет одинаковую проблему с OP как с конструктором 'const', так и без него. – kludg

+0

@ user246408 Я не согласен. Я просто отвечал на вопрос, как задавал вопрос, и рассматривал два фрагмента кода перед собой. –

Смежные вопросы