0

我正在尝试从 Word 文档中读取文本和图像并将其关闭。问题是试图在 Word 没有遇到任何问题或创建多个 WINWORD.exe 实例的情况下关闭它。我的问题是,当我调用Marshal.FinalReleaseComObject(app);Word.ApplicationClass,Word 会触发 Windows 提供的一般异常(“Word 已停止工作”)。我已阅读如何正确清理 Excel 互操作对象中的许多解决方案?并实施了建议,但我仍然有问题。

这是我的代码。我只阅读一个一页的 Word 文件(您可能想跳到发生异常的“// Cleanup:”)。

    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;
    }

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

有什么办法可以防止错误显示?

4

3 回答 3

1

看到这种崩溃是相当不寻常的,Word 通常知道如何处理这种大锤式的内存管理方法。然而,这是一个非常糟糕的做法。Visual Studio 团队的这篇博文对此进行了最佳描述。值得一读,“沉默的刺客”部分是最相关的。

调用 GC.Collect 就足以释放所有的 COM 引用,不需要额外的帮助。但是,如果您在附加调试器的情况下运行程序,那将不起作用。 这个答案解释了原因。

要让 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 调用,因为它们完全没有必要。而且很危险。

于 2013-10-18T11:01:37.183 回答
0

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

于 2013-10-17T22:38:53.630 回答
0

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

在这种情况下,您可以完全减少每个 RCW、 和 上的引用计数docdocsapp不仅仅是您拥有的引用。

尝试ReleaseComObject改为尝试,但请注意,如果仍有一个 .NET 枚举器处于活动状态、正在使用并附加到您从 Word 集合之一释放的对象之一,则这可能同样糟糕。

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

于 2013-10-18T10:38:09.447 回答