2014-04-03 56 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,它一個IPersistStream和正確行爲。爲什麼需要明確的nil分配來強制撥打_Release?這是Delphi 7的一個已知問題嗎?是因爲鏈接器/編譯器開關?

+0

這本來是真的很容易做出的SSCCE。現在我必須這樣做。其他任何想要運行代碼的人也是如此。事實上,我不確定我會不會受到困擾。你不能這樣做嗎?其實,我知道答案沒有任何運行。你好幸運啊! –

+0

請參閱我在每個SO問題中所學到的東西。不瞭解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是的,在'Save'功能中刪除'const'可以解決問題。然後,當「保存」開始時將引用一個引用,然後當「保存」返回時該引用將被釋放。在這一點上對象將沒有引用,並將被銷燬。 –

+1

也許這對於具體的第一個OP代碼片段是正確的,但是通常Delphi編譯器只保證當它們超出範圍時會拋棄垃圾接口引用,並且如果通過移除「const」指示符I來產生解決問題的SSCCE相信我可以很容易地創建一個計數器SSCCE,它在使用和不使用'const'指示符時仍然具有相同的OP問題。 – kludg

+0

@ user246408我不反對。我只是回答了問題,並考慮了我面前的兩個代碼片段。 –