3

我正在尝试使用此处描述的 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# 化代码的样子。我错过了什么?

    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

4 回答 4

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调用的返回类型),所以是三个。

您应该调用Marshal.Release所有IntPtr变量。

我不确定OLE32.IStorage。这个可能需要Marshal.ReleaseMarshal.ReleaseComPointer

更新:我刚刚注意到我错过了至少一个参考计数。var不是一个IntPtr,它是一个IDataObject。演员将as做一个隐式QueryInterface并添加另一个参考计数。虽然GetObjectForIUnknown返回一个 RCW,但这个会延迟到 GC 启动。您可能希望在using块中执行此操作以激活IDisposable它。

同时,该STGMEDIUM结构也有一个IUnknown您没有释放的指针。您应该调用ReleaseStgMedium以正确处理整个结构,包括该指针。

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

于 2010-04-22T03:39:08.467 回答
0

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

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

var storagePointer = Marshal.GetIUnknownForObject(storage);
int refCount;
do
{
    refCount = Marshal.Release(storagePointer);
} while (refCount > 0);
于 2010-06-02T05:09:27.843 回答
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);
于 2016-12-19T12:25:51.127 回答
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 来释放对 Runtime Callable Wrapper (RCW) 的所有引用”。

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

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

于 2017-03-23T16:43:28.240 回答