为什么FinalReleaseComObject会导致“(InteropProgram)停止工作”?

时间:2013-10-17 18:53:02

标签: c# com ms-word interop

我试图从Word文档中读取文本和图像并将其关闭。问题是尝试关闭它而不会遇到任何问题或创建多个WINWORD.exe实例。我的问题是,当我在Marshal.FinalReleaseComObject(app);上调用Word.ApplicationClass时,Word会触发Windows提供的一般异常(" Word已停止工作")。我已经阅读了How do I properly clean up Excel interop objects?中的许多解决方案并实施了建议,但我仍然遇到了问题。

这是我的代码。我只阅读一个带有一个页面的Word文件(您可能希望跳到" //清理:"发生异常的地方)。

    private byte[] GetDocumentText(byte[] wordBytes, string path)
    {
        // Save bytes to word file in temp dir, open, copy info. Then delete the temp file after.

        object x = Type.Missing;
        string ext = Path.GetExtension(path).ToLower();
        string tmpPath = Path.ChangeExtension(Path.GetTempFileName(), ext);
        File.WriteAllBytes(tmpPath, wordBytes);

        // Open temp file with Excel Interop:
        Word.ApplicationClass app = new Word.ApplicationClass();
        Word.Documents docs = app.Documents;
        Word.Document doc = docs.Open(tmpPath, x, x, x, x, x, x, x, x, x, x, x, x, x, x);

        doc.ActiveWindow.Selection.WholeStory();
        doc.ActiveWindow.Selection.Copy();
        IDataObject data = Clipboard.GetDataObject();
        string documentText = data.GetData(DataFormats.Text).ToString();

        // Add text to pages.
        byte[] wordDoc = null;
        using (MemoryStream myMemoryStream = new MemoryStream())
        {
            Document myDocument = new Document();
            PdfWriter myPDFWriter = PdfWriter.GetInstance(myDocument, myMemoryStream); // REQUIRED.
            PdfPTable table = new PdfPTable(1);
            myDocument.Open();

            // Create a font that will accept unicode characters.
            BaseFont bfArial = BaseFont.CreateFont(@"C:\Windows\Fonts\Arial.ttf", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
            Font arial = new Font(bfArial, 12);

            // If Hebrew character found, change page direction of documentText.
            PdfPCell page = new PdfPCell(new Paragraph(documentText, arial)) { Colspan = 1 };
            Match rgx = Regex.Match(documentText, @"\p{IsArabic}|\p{IsHebrew}");
            if (rgx.Success) page.RunDirection = PdfWriter.RUN_DIRECTION_RTL;

            table.AddCell(page);

            // Add image to document (Not in order with text...)
            foreach (Word.InlineShape ils in doc.InlineShapes)
            {
                if (ils != null && ils.Type == Word.WdInlineShapeType.wdInlineShapePicture)
                {
                    PdfPCell imageCell = new PdfPCell();
                    ils.Select();
                    doc.ActiveWindow.Selection.Copy();
                    System.Drawing.Image img = Clipboard.GetImage();
                    byte[] imgb = null;
                    using (MemoryStream ms = new MemoryStream())
                    {
                        img.Save(ms, System.Drawing.Imaging.ImageFormat.Jpeg);
                        imgb = ms.ToArray();
                    }

                    Image wordPic = Image.GetInstance(imgb);
                    imageCell.AddElement(wordPic);
                    table.AddCell(imageCell);
                }
            }

            myDocument.Add(table);
            myDocument.Close();
            myPDFWriter.Close();
            wordDoc = myMemoryStream.ToArray();
        }

        // Cleanup:
        Clipboard.Clear();

        (doc as Word._Document).Close(Word.WdSaveOptions.wdDoNotSaveChanges, x, x);
        Marshal.FinalReleaseComObject(doc);
        Marshal.FinalReleaseComObject(docs);
        (app as Word._Application).Quit(x, x, x);
        Marshal.FinalReleaseComObject(app); // Word encounters exception here.

        doc = null;
        docs = null;
        app = null;
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        GC.WaitForPendingFinalizers();

        try { File.Delete(tmpPath); }
        catch { }

        return wordDoc;
    }

这并不总是在我第一次阅读文件时发生。当我第二次或第三次读它时,我通常会得到错误。

有什么方法可以阻止错误显示?

3 个答案:

答案 0 :(得分:1)

看到这次崩溃是相当不寻常的,Word通常知道如何处理这种内存管理的大锤方法。然而,这是一个非常糟糕的做法。最好由Visual Studio团队的this blog post描述。值得一读,“沉默的刺客”部分是最相关的。

调用GC.Collect足以释放所有COM引用,无需其他帮助。但是,如果您运行附带调试器的程序,则无效。 This answer解释了原因。

要使GC.Collect()也在调试器中工作,您需要在单独的方法中移动它,以便调试器无法使引用保持活动状态。这样做最简单:

private byte[] GetDocumentText(byte[] wordBytes, string path) {
   var retval = GetDocumentTextImpl(wordBytes, path);
   GC.Collect();
   GC.WaitForPendingFinalizers();
   return retval;
}

private byte[] GetDocumentTextImpl(byte[] wordBytes, string path) {
   // etc...
}

将原始代码移动到GetDocumentTextImpl()方法中。只需从代码中删除所有Marshal和GC调用,因为它们完全没必要。而且很危险。

答案 1 :(得分:0)

您可以在调用FinalReleaseComObject之前尝试检查IsObjectValid

答案 2 :(得分:0)

你根本不应该使用FinalReleaseComObject,这是一个锤子来释放/删除你肯定知道的RCW 你是唯一的推荐者(在.NET中)。

在这种情况下,您完全减少每个RCW的引用次数docdocsapp,而不仅仅是来自您的参考文献。

请尝试ReleaseComObject,但请注意,如果是还有一个.NET枚举器正在使用中,并且附加到您从Word集合中释放的一个对象。

关闭文档,退出Word,将变量设置为null和GC'应该就足够了。根据编译器的不同,它可能会丢弃堆栈中的变量,并消除将它们设置为null的代码。