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

时间:2010-04-22 03:27:51

标签: c# winapi com ole istorage

我正在尝试使用此处描述的OLE技术将PDF文件嵌入到Word文档中: http://blogs.msdn.com/brian_jones/archive/2009/07/21/embedding-any-file-type-like-pdf-in-an-open-xml-file.aspx

我试图实现C#中提供的C ++代码,这样整个项目就在一个地方,除了一个包版之外几乎就在那里。当我尝试将生成的OLE对象二进制数据提供给Word文档时,我得到一个IOException。

  

IOException:进程无法访问文件'C:\ Wherever \ Whatever.pdf.bin',因为它正由另一个进程使用。

有一个文件句柄打开.bin文件(下面的“oleOutputFileName”),我不知道如何摆脱它。关于COM,我不知道有多少 - 我在这里wing - 我不知道文件句柄在哪里或者如何释放它。

这是我的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对我来说是外语。)

4 个答案:

答案 0 :(得分: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包装器并且将在其终结器中正确释放其引用计数,否则GC不会收集COM对象。

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来正确处理整个结构,包括那个指针。

我太累了,无法继续查看代码。我明天会回来,并试图找到其他可能的ref count泄漏。同时,您检查MSDN文档中所有正在调用的接口,结构和API,并尝试找出您可能错过的任何其他引用计数。

答案 1 :(得分:0)

我找到了答案,这很简单。 (可能太简单了 - 感觉就像是一个黑客,但因为我对COM编程知之甚少,所以我只会选择它。)

存储对象上有多个引用,所以只要它们全部消失就继续:

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

答案 2 :(得分: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);

答案 3 :(得分: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);
                }
            }
        }
    }

您传递要释放的对象,例如在finally语句中,它“通过将其引用计数设置为0来释放对运行时可调用包装器(RCW)的所有引用。”

如果您想要发布最后创建的引用但保留之前创建的引用,则不适用。

它对我有用,没有内存泄漏。