2010-04-22 60 views
3

我試圖嵌入PDF文件到使用OLE技術,一個Word文檔說明如下: http://blogs.msdn.com/brian_jones/archive/2009/07/21/embedding-any-file-type-like-pdf-in-an-open-xml-file.aspx釋放在C#中的OLE的IStorage文件句柄

我試圖實現在C#這樣提供的C++代碼整個項目在一個地方,除了一個路障外幾乎都在那裏。當我嘗試將生成的OLE對象二進制數據提供給Word文檔時,我得到一個IOException。

IOException:進程無法訪問文件'C:\ Wherever \ Whatever.pdf.bin',因爲它正在被另一個進程使用。

有一個文件句柄打開.bin文件(下面的「oleOutputFileName」),我不知道如何擺脫它。我不知道有關COM的大量數據 - 我在這裏展示它 - 我不知道文件句柄在哪裏或者如何釋放它。

下面是我的C#化代碼的外觀。我錯過了什麼?

public void ExportOleFile(string oleOutputFileName, string emfOutputFileName) 
    { 
     OLE32.IStorage storage; 
     var result = OLE32.StgCreateStorageEx(
      oleOutputFileName, 
      OLE32.STGM.STGM_READWRITE | OLE32.STGM.STGM_SHARE_EXCLUSIVE | OLE32.STGM.STGM_CREATE | OLE32.STGM.STGM_TRANSACTED, 
      OLE32.STGFMT.STGFMT_DOCFILE, 
      0, 
      IntPtr.Zero, 
      IntPtr.Zero, 
      ref OLE32.IID_IStorage, 
      out storage 
     ); 

     var CLSID_NULL = Guid.Empty; 

     OLE32.IOleObject pOle; 
     result = OLE32.OleCreateFromFile(
      ref CLSID_NULL, 
      _inputFileName, 
      ref OLE32.IID_IOleObject, 
      OLE32.OLERENDER.OLERENDER_NONE, 
      IntPtr.Zero, 
      null, 
      storage, 
      out pOle 
     ); 

     result = OLE32.OleRun(pOle); 

     IntPtr unknownFromOle = Marshal.GetIUnknownForObject(pOle); 
     IntPtr unknownForDataObj; 
     Marshal.QueryInterface(unknownFromOle, ref OLE32.IID_IDataObject, out unknownForDataObj); 
     var pdo = Marshal.GetObjectForIUnknown(unknownForDataObj) as IDataObject; 

     var fetc = new FORMATETC(); 
     fetc.cfFormat = (short)OLE32.CLIPFORMAT.CF_ENHMETAFILE; 
     fetc.dwAspect = DVASPECT.DVASPECT_CONTENT; 
     fetc.lindex = -1; 
     fetc.ptd = IntPtr.Zero; 
     fetc.tymed = TYMED.TYMED_ENHMF; 

     var stgm = new STGMEDIUM(); 
     stgm.unionmember = IntPtr.Zero; 
     stgm.tymed = TYMED.TYMED_ENHMF; 
     pdo.GetData(ref fetc, out stgm); 

     var hemf = GDI32.CopyEnhMetaFile(stgm.unionmember, emfOutputFileName); 
     storage.Commit((int)OLE32.STGC.STGC_DEFAULT); 

     pOle.Close(0); 
     GDI32.DeleteEnhMetaFile(stgm.unionmember); 
     GDI32.DeleteEnhMetaFile(hemf); 
    } 

更新1:闡明瞭我的意思是「.bin文件」。
更新2:我不使用「使用」塊,因爲我想擺脫的東西不是一次性的。 (而且是完全誠實,我不知道我需要釋放刪除的文件句柄,COM是一門外語對我來說)。

+0

哪一個文件是「.bin」文件? 'oleOutputFileName'或'_inputFileName'?它看起來像一個先前的電話乍一看留下了文件句柄不明顯,但。 ('使用'塊是你的朋友。) – 2010-05-31 05:01:31

回答

0

我找到了答案,它非常簡單。 (可能太簡單了 - 感覺像是黑客,但是因爲我對COM編程知之甚少,所以我只想跟它一起去。)

存儲對象有多個引用,所以只要繼續下去,全部消失了:

var storagePointer = Marshal.GetIUnknownForObject(storage); 
int refCount; 
do 
{ 
    refCount = Marshal.Release(storagePointer); 
} while (refCount > 0); 
+0

這似乎不工作..你在這裏釋放太多恕我直言。 – aerkain 2013-07-31 07:46:25

1

我看到你的代碼至少有四個可能的引用計數泄漏:

OLE32.IStorage storage; // ref counted from OLE32.StgCreateStorageEx(
IntPtr unknownFromOle = Marshal.GetIUnknownForObject(pOle); // ref counted 
IntPtr unknownForDataObj; // re counted from Marshal.QueryInterface(unknownFromOle 
var pdo = Marshal.GetObjectForIUnknown(unknownForDataObj) as IDataObject; // ref counted 

請注意,所有這些都是指向COM對象的指針。除非將持有引用點的.Net類型指向RCW包裝並且將在其終結器中正確釋放其引用計數,否則COM對象不會由GC收集。

IntPtr是不是這樣的類型和var也是IntPtr(從Marshal.GetObjectForIUnknown調用的返回類型),這樣就使得三人。

您應該在所有的IntPtr變量上致電Marshal.Release。我不確定OLE32.IStorage。這可能需要Marshal.ReleaseMarshal.ReleaseComPointer

更新:我只注意到我錯過了至少一個參考點數。 var不是IntPtr,它是IDataObjectas強制轉換將執行一個隱含的QueryInterface並添加另一個參考計數。雖然GetObjectForIUnknown會返回RCW,但這個延遲時間會延遲到GC啓動。您可能需要在using區塊中執行此操作,以激活其上的IDisposable

同時,STGMEDIUM結構也有一個IUnknown指針,你不釋放。您應該調用ReleaseStgMedium以正確處理整個結構,包括該指針。

我太累了,無法繼續查看代碼。我明天會回來,並嘗試找到其他可能的參考計數泄漏。同時,您可以查看MSDN文檔中所有正在調用的接口,結構和API,並嘗試找出可能錯過的其他引用計數。

+0

我已經添加了下面的方法結束,但仍然得到IOException。 Marshal.ReleaseComObject(pdo); Marshal.Release(unknownForDataObj); Marshal.Release(unknownFromOle); Marshal.ReleaseComObject(storage); 命令是否重要? 運行時,返回的refcounts不爲0,它們分別爲1(對於pdo),4(對於unknownForDataObj),3(對於unknownFromOle)和0(對於存儲)。 – 2010-04-22 04:01:00

+0

我不得不問,爲什麼你不只是'var pdo = pOle as IDataObject;'?用於RCW的類型轉換使用'IUnknown.QueryInterface()'。 – 2010-05-31 04:50:51

0

我知道這個問題很舊,但由於這已經給我帶來了一些麻煩,我覺得我需要分享一下爲我工作的東西。

起初,我試圖用伯納德·丹頓的自己的答案:

var storagePointer = Marshal.GetIUnknownForObject(storage); 
int refCount; 
do 
{ 
    refCount = Marshal.Release(storagePointer); 
} while (refCount > 0); 

然而,儘管該解決方案曾在第一,它結束了導致一些附帶的問題。

所以,以下弗郎佩諾夫答案,我說什麼如下的代碼:

  OLE32.ReleaseStgMedium(ref stgm); 
      Marshal.Release(unknownForDataObj); 
      Marshal.Release(unknownFromOle); 
      Marshal.ReleaseComObject(storage); 
0

我寫了這個釋放COM對象:

public static void ReleaseComObjects(params object[] objects) 
    { 
     if (objects == null) 
     { 
      return; 
     } 

     foreach (var obj in objects) 
     { 
      if (obj != null) 
      { 
       try 
       { 
        Marshal.FinalReleaseComObject(obj); 
       } 
       catch (Exception ex) 
       { 
        System.Diagnostics.Debug.WriteLine(ex.Message); 
       } 
      } 
     } 
    } 

你傳遞你要釋放的對象例如在最終語句中,它「通過將引用計數設置爲0來釋放對運行時可調用包裝器(RCW)的所有引用。」

如果您想釋放上次創建的引用,但保留之前創建的引用,則不適用。

它已經爲我工作,沒有內存泄漏。