【问题标题】:Releasing an OLE IStorage file handle in C#在 C# 中释放 OLE IStorage 文件句柄
【发布时间】:2011-02-10 21:31:23
【问题描述】:

我正在尝试使用此处描述的 OLE 技术将 PDF 文件嵌入 Word 文档: https://docs.microsoft.com/en-us/archive/blogs/brian_jones/embedding-any-file-type-like-pdf-in-an-open-xml-file

我已经尝试实现 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 对我来说是一门外语。)

【问题讨论】:

  • “.bin”文件是哪个文件? oleOutputFileName 还是 _inputFileName?不过,乍一看,以前的调用似乎使文件句柄未处理。 (using 块是你的朋友。)

标签: c# winapi com ole istorage


【解决方案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 对象的指针。 COM 对象不会被 GC 收集,除非 .Net 类型持有指向 RCW 包装器的引用并会在其终结器中正确释放其引用计数。

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,并尝试找出您可能遗漏的任何其他引用计数。

【讨论】:

  • 我在方法的末尾添加了以下内容,但仍然得到 IOException。 Marshal.ReleaseComObject(pdo); Marshal.Release(unknownForDataObj); Marshal.Release(unknownFromOle); Marshal.ReleaseComObject(存储);顺序重要吗?运行时返回的引用计数不为零,它们是 1(对于 pdo)、4(对于 unknownForDataObj)、3(对于 unknownFromOle)和 0(对于存储)。
  • 我要问你为什么不直接做var pdo = pOle as IDataObject;? RCW 的类型转换使用 IUnknown.QueryInterface()
【解决方案2】:

我找到了答案,而且非常简单。 (可能太简单了 - 感觉就像是一个 hack,但由于我对 COM 编程知之甚少,所以我将继续使用它。)

存储对象上有多个引用,所以继续直到它们都消失:

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

【讨论】:

  • 这似乎不起作用.. 恕我直言,您在这里发布太多了。
【解决方案3】:

我知道这个问题很老了,但由于这给我带来了一些麻烦,我觉得我需要分享对我有用的东西。

起初,我尝试使用 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);

【讨论】:

    【解决方案4】:

    我写这个是为了释放 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 来释放对 Runtime Callable Wrapper (RCW) 的所有引用。”

    如果要释放最后创建的引用但保留之前创建的引用,则不适合。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-02-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多