簡體   English   中英

在 C# 中釋放 OLE IStorage 文件句柄

[英]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.ReleaseMarshal.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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM