[英]Releasing an OLE IStorage file handle in C#
我正在嘗試使用此處描述的 OLE 技術將 PDF 文件嵌入到 Word 文檔中: https : //docs.microsoft.com/en-us/archive/blogs/brian_jones/embedding-any-file-type-like- pdf-in-an-open-xml 文件
我試圖實現 C# 中提供的 C++ 代碼,以便整個項目都在一個地方,除了一個障礙外幾乎就在那里。 當我嘗試將生成的 OLE 對象二進制數據提供給 Word 文檔時,我得到一個 IOException。
IOException:進程無法訪問文件“C:\\Wherever\\Whatever.pdf.bin”,因為它正被另一個進程使用。
有一個文件句柄打開 .bin 文件(下面的“oleOutputFileName”),我不知道如何擺脫它。 我對 COM 知之甚少——我只是在這里討論一下——我不知道文件句柄在哪里或如何釋放它。
這是我的 C#-ised 代碼的樣子。 我錯過了什么?
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 對我來說是一門外語。)
我在您的代碼中看到至少四個潛在的 refcount 泄漏:
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 對象的指針。 COM 對象不會被 GC 收集,除非 .Net 類型持有對 RCW 包裝器的引用並且將在其終結器中正確釋放其引用計數。
IntPtr
不是這樣的類型,你的var
也是IntPtr
(來自Marshal.GetObjectForIUnknown
調用的返回類型),所以是三個。
您應該對所有IntPtr
變量調用Marshal.Release
。
我不確定OLE32.IStorage
。 這個可能需要Marshal.Release
或Marshal.ReleaseComPointer
。
更新:我剛剛注意到我錯過了至少一個參考計數。 var
不是IntPtr
,而是IDataObject
。 as
將執行隱式QueryInterface
並添加另一個引用計數。 盡管GetObjectForIUnknown
返回一個 RCW,但這個 RCW 會延遲到 GC 啟動。您可能希望using
塊來激活其上的IDisposable
來執行此using
。
同時, STGMEDIUM
結構也有一個您沒有釋放的IUnknown
指針。 您應該調用ReleaseStgMedium
以正確處理整個結構,包括該指針。
我現在太累了,無法繼續查看代碼。 我明天會回來並嘗試找到其他可能的引用計數泄漏。 同時,您檢查 MSDN 文檔以了解您正在調用的所有接口、結構和 API,並嘗試找出您可能遺漏的任何其他引用計數。
我找到了答案,而且很簡單。 (可能太簡單了 - 感覺就像一個黑客,但由於我對 COM 編程知之甚少,所以我打算繼續使用它。)
存儲對象上有多個引用,所以繼續下去,直到它們都消失了:
var storagePointer = Marshal.GetIUnknownForObject(storage);
int refCount;
do
{
refCount = Marshal.Release(storagePointer);
} while (refCount > 0);
我知道這個問題很老,但由於這給我帶來了一些麻煩,我覺得我需要分享對我有用的東西。
起初,我嘗試使用 Bernard Darnton 自己的回答:
var storagePointer = Marshal.GetIUnknownForObject(storage); int refCount; do { refCount = Marshal.Release(storagePointer); } while (refCount > 0);
然而,盡管該解決方案起初有效,但最終導致了一些附帶問題。
因此,按照 Franci Penov 的回答,我在代碼中添加了以下內容:
OLE32.ReleaseStgMedium(ref stgm);
Marshal.Release(unknownForDataObj);
Marshal.Release(unknownFromOle);
Marshal.ReleaseComObject(storage);
我寫這個是為了釋放 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);
}
}
}
}
您傳遞要釋放的對象,例如在 finally 語句中,它“通過將引用計數設置為 0 來釋放對運行時可調用包裝器 (RCW) 的所有引用”。
如果您想釋放最后創建的引用但保留之前創建的引用,則不適合。
它對我有用,沒有內存泄漏。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.