【问题标题】:Weird exception when trying to automate MS Word from vb.net尝试从 vb.net 自动化 MS Word 时出现奇怪的异常
【发布时间】:2014-02-21 05:22:47
【问题描述】:

我使用以下vb.net函数纯粹保存Word文档(没有任何文本输入,我现在只对大量创建空word文档感兴趣):

Sub createDoc(ByVal cname As String, ByVal acctype As String)
    counter += 1
    wordDoc = wordApp.Documents.Add
    wordDoc.SaveAs(OFDD.SelectedPath & "\" & cname & "_" & acctype & ".docx")
    wordDoc.Close()
End Sub

OFDD 变量是文件夹浏览器 vb 组件的名称,它的 SelectedPath 属性结合 cname 和 acctype 参数为我提供了我要创建和保存的 Word 文档的名称。下面是 counter、wordDoc 和 wordApp 变量的声明:

 Private Shared counter As Integer = 0
 Private wordApp As New Word.Application
 Private wordDoc As Word.Document

使用子例程 createDoc 中的第二行代码将 wordDoc 变量分配给 Document 对象。但是,似乎在我第 83 次尝试检索文档对象并将其分配给 wordDoc 时,我收到一个异常,指出 "Command failed" 。我可以说这是我第 83 次进入该函数,因为在我的 catchblock 中,我在消息框中打印了 counter 的值,就在打印有关接收到的异常的详细信息之后,就在我释放我使用的资源并结束进程之前。

担心我的系统是否存在与 MS Word 自动化相关的限制,我创建了另一个 Visual Studio 项目(这次是控制台项目),引用了 Microsoft.Interop.Office.Word 命名空间并编写了以下简单模块:

Imports Word = Microsoft.Office.Interop.Word
Module Module1

Sub Main()
    Dim wordApp As New Word.Application
    Try
        For i As Integer = 0 To 150
            Dim document As Word.Document = wordApp.Documents.Add()
            document.SaveAs("C:\WordTester\" & i & ".docx")
            document.Close()
        Next
    Catch
        wordApp.Quit()
    End Try
    Console.WriteLine("Document objects left in memory: " & _ 
                       wordApp.Documents.Count) ' zero
    Console.Read()
    wordApp.Quit()
End Sub

End Module

效果很好。检查我的文件系统,我看到在“C:\WordTester”中创建了 150 个单词文件。考虑到我所做的所有这些努力,我真的很困惑为什么我编写的第一个代码在第 83 次创建和保存文档时卡住了,任何帮助都将不胜感激。

感谢您的宝贵时间,

杰森

编辑:这是我在下面的评论中引用的 createDoc 的编辑版本:

Sub createDoc(ByVal cname As String, ByVal acctype As String)
    counter += 1
    wordApp = New Word.Application
    wordDoc = New Word.Document
    wordDoc = wordApp.Documents.Add
    wordDoc.SaveAs(OFDD.SelectedPath & "\" & cname & "_" & acctype & ".docx")
    wordDoc.Close()
    wordApp.Quit()
End Sub

【问题讨论】:

    标签: vb.net ms-word


    【解决方案1】:

    试试这样的。显然我不得不插入一些假设的代码,但这几乎是你应该做的:

    Imports Word = Microsoft.Office.Interop.Word
    
    Public Class Class1
    
    Private pWordApp As Word.Application
    Private pintCounter As Integer = 0
    
    Public Sub CreateWordDocuments()
    
        '--instanciate word:
        pWordApp = New Word.Application
    
        Dim dt As DataTable = GetYourDataEtc    'replace with however you get the data you loop around
        Dim OFDD As Object = GetYourFolderPathEtc   'replace Object and folder path call and 
    
        Try
    
            For Each dr In dt.Rows
                Dim cName As String = dr("cname")  'for example
                Dim acctype As String = dr("acctype") #for example
    
                CreateDoc(OFDD, cName, acctype)
            Next
    
        Catch ex As Exception
            '--some error code
        Finally
            pWordApp.Quit()
            System.Runtime.InteropServices.Marshal.ReleaseComObject(pWordApp)
        End Try
    
    End Sub
    
    Private Sub CreateDoc(ByVal OFDD As Object, ByVal cname As String, ByVal accType As String)
    
        Dim document As Word.Document = pWordApp.Documents.Add()
        document.SaveAs(OFDD.SelectedPath & "\" & cname & "_" & accType & ".docx")
        document.Close()
    
    End Sub
    
    
    End Class
    

    【讨论】:

    • 是的,总是在第 83 次。我的输入来自一个包含大约 250 条记录的 SQL Server 数据库查询,因此我检查了第 83 行周围的输入是否可能包含 NULL 字段;不是这种情况。我现有的 catch 块具有以下消息框输出:MsgBox("Error : " & Err.Description & " from method: " & Err.Source & " in line " & Err.Erl & ".", vbCritical) MsgBox("Document Objects in memory: " & wordApp.Documents.Count) MsgBox("Counter variable value : " & counter) 我认为这可能足够口头?
    • 我的意思是在实际的 CreateDoc 例程中放入另一个 try catch 块并在错误时中断。然后询问单词 object 看看发生了什么。
    • Booji Boy 说得对。但是,您编辑的 CreateDoc 例程不是一个好主意,因为您正在为每个文档开始和结束单词。为什么不简单地将工作控制台代码复制到一个类中。在顶部创建对 word 的引用,将每个文档处理为关闭的 word 并进行处理?
    【解决方案2】:

    看起来像一个垃圾收集问题 - CLR 持有对单词对象的许多引用,并且在垃圾收集开始之前,单词耗尽了一些资源。第二个函数有效,因为所有内容都在本地范围内。您可以将代码更改为本地范围;此外,对 worddoc 对象调用 dispose 也会有所帮助。

    看起来没有 dispose。正确的方法是调用ReleaseComObject方法

    http://msdn.microsoft.com/en-us/library/aa159887(office.11).aspx

    【讨论】:

    • 嗯,恐怕我不太清楚您所说的“本地范围”是什么意思。为了确保垃圾收集不会搞砸,我应该将代码的性质更改为什么?
    • 另外,就在 worddoc 对象上调用“Dispose()”而言,我收到一个异常,指出“未找到类型为“DocumentClass”的公共成员 Dispose()。
    • wordapp 和 word doc 都在子(本地范围)中声明,因此它们最终都在堆而不是堆栈上。子程序完成运行后,堆栈将被清空。第一个代码在模块中声明了 wordapp 和 worddoc,因此(全局范围)并将最终在堆上,并且需要更长的时间才能被垃圾收集器清理
    • 这难道不是我想要的吗?我的意思是,我希望尽可能晚地清理对象,以免我的 wordDoc 和 wordApp 对象开始出现错误行为,就像现在一样。还需要注意的是,第一个代码的层次结构如下:class-> sub main -> central for loop,如果满足某些要求,则调用 sub createDoc()。
    • 是的,在本地声明所有内容会更慢,但它有助于垃圾清理。简单地将 wordapp 声明更改为 Dim wordApp As Word.Application 并在构造函数中实例化 wordApp = New Word.Application 可能会有所帮助。调用 close 后也设置 worddoc = nothing
    【解决方案3】:

    我宁愿在每次创建文档时都创建一个新的 Word 实例,而不是在循环之前创建一个实例以创建单个文档并在循环期间使用此应用程序对象 - 这是 dunc 提出的方法。

    注意:在循环之后,您使用 Marshal.ReleaseCOMObject 释放 COM 对象。我也会在 doc 对象的 CreateDoc 子例程中调用此函数。 请查看办公自动化中的this discussion about releasing COM objects

    【讨论】:

      【解决方案4】:

      在工作了一天的大部分时间后,我终于发现问题根本不在垃圾收集过程中。我也没有创建太多的文档对象。事实证明,我的数据库输入包含带双引号的记录,而 MS Word 不允许在文件名中使用双引号。

      感谢您的时间和兴趣。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-02-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-29
        • 1970-01-01
        • 2021-04-27
        • 2011-06-17
        相关资源
        最近更新 更多