【问题标题】:VBA automation of Excel leaves a process in memory after QuitExcel 的 VBA 自动化在退出后将进程留在内存中
【发布时间】:2017-10-11 13:19:19
【问题描述】:

我已经看到了很多关于这个问题的建议,并且我都尝试了它们,但似乎没有一个有效。 VBA 代码位于非 Microsoft 产品中(SAP Business Objects,这可能是问题所在)。我创建了一个 Excel 对象:

Set oExcel = CreateObject("Excel.Application")

从特定工作簿中的一个工作表的第 1 列加载内容,然后关闭 Excel。每次,它都会在内存中留下一个进程,占用 5+ mb 的内存。

我尝试让 oExcel 对象可见,这样至少我可以在不借助任务管理器的情况下将其杀死,但是当我调用 Quit 时,UI 退出,并且仍然离开进程。

每次我运行代码时,它都会创建一个新进程。所以我尝试通过调用

来重用任何现有的 Excel 进程

Set m_oExcel = GetObject(, "Excel.Application")

并且仅在该调用不返回任何内容时创建它,

这并没有增加进程,但是单个进程每次增长 5+ mb,所以本质上是相同的问题。

在每种情况下,我都会关闭我打开的工作簿并将 DisplayAlerts 设置为 False,然后再退出:

m_oBook.Close SaveChanges:=False
m_oExcel.DisplayAlerts = False
m_oExcel.Quit

这段代码至少已经使用了五年,但直到我们迁移到 Windows 7 后这个问题才出现。

这是完整的代码,以防万一。请注意,根据一个建议,所有 Excel 对象都是模块级变量(“m_”前缀),并且我根据另一个建议使用了“单点”规则。我也尝试过使用通用对象(即后期绑定),但这也没有解决问题:

Private Function GetVariablesFromXLS(ByVal sFile As String) As Boolean
    On Error GoTo SubError

    If Dir(sFile) = "" Then
        MsgBox "File '" & sFile & "' does not exist.  " & _
               "The Agent and Account lists have not been updated."
    Else
        Set m_oExcel = CreateObject("Excel.Application")
        Set m_oBooks = m_oExcel.Workbooks
        Set m_oBook = m_oBooks.Open(sFile)

        ThisDocument.Variables("Agent(s)").Value = DelimitedList("Agents")
        ThisDocument.Variables("Account(s)").Value = DelimitedList("Accounts")
    End If

    GetVariablesFromXLS = True

SubExit:

    On Error GoTo ResumeNext
    m_oBook.Close SaveChanges:=False
    Set m_oBook = Nothing
    Set m_oBooks = Nothing

    m_oExcel.DisplayAlerts = False
    m_oExcel.Quit

    Set m_oExcel = Nothing

    Exit Function

SubError:
    MsgBox Err.Description
    GetVariablesFromXLS = False
    Resume SubExit

ResumeNext:
    MsgBox Err.Description
    GetVariablesFromXLS = False
    Resume Next

End Function

【问题讨论】:

  • 我认为任务管理器对于这类事情是出了名的不可靠......我认为这里还有其他关于类似主题的 Q,并且一致认为 TM 是不正确的。让我看看能不能确认一下。
  • 尝试将m_oBook.Close SaveChanges:=False改为m_oBook.Saved = True
  • hmmmm 我正在玩这个,Set m_oExcel = Nothing每次都会从任务管理器中释放它;即使我不做m_oExcel.Quit。事实上,即使我不将其设置为 Nothing,任务管理器也会在运行时删除该进程。
  • Set oExcel = CreateObject("Excel.Application") 在任务管理器中)。一旦你知道哪一行没有做你想做的事,然后只用相关代码编辑你的帖子。
  • 您确定您正在查看任务管理器中的进程而不是应用程序吗?我正在单步执行它并立即调用 CreateObject() 它出现在 Processes 中。

标签: excel vba


【解决方案1】:

大多数情况下发生这种情况是因为 Excel 使 COM 加载项保持打开状态。尝试使用以下链接获取有关删除 COM 插件的帮助。

Add or remove add-ins

我在笔记中找到了特别的安慰:

注意这将从内存中删除加载项,但将其名称保留在可用加载项列表中。它不会从您的计算机中删除加载项。

【讨论】:

  • 使用 oExcel.COMAddIns([Index]).Connect = False 关闭您可能已激活的任何 COM 加载项。这为我解决了这个问题。
【解决方案2】:

根据 David Zemens 的评论添加答案。对我有用。

m_oExcel.Quit            '<- Still in Task Manager after this line
Set m_oExcel = Nothing   '<- Gone after this line

【讨论】:

  • 也为我工作,感谢修补程序 Q---10
【解决方案3】:

Acantud 已在后续帖子中回答了此问题: https://stackoverflow.com/questions/25147242 完全限定对您打开的 Excel 工作簿中对象的引用,以避免在任务管理器中创建孤立进程。这种情况下,解决方法是在DelimitedList前加上m_oBook,比如

ThisDocument.Variables("Agent(s)").Value = m_oBook.DelimitedList("Agents")

【讨论】:

    【解决方案4】:

    虽然这不应该发生,但您可以向 excel 发送“WindowClose”消息以强制关闭。

    您将需要这些 API 函数

    Private Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
    Private Declare Function TerminateProcess Lib "kernel32" (ByVal hProcess As Long, ByVal uExitCode As Long) As Long
    Private Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
    Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As Long, lpdwProcessId As Long) As Long
    Public Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
    

    它应该看起来像这样:

    // First, get the handle
    hWindow = FindWindow(vbNullString, "Excel")
    //Get proccess ID
    GetWindowThreadProcessId(hWindow, ProcessValueID)
    //Kill the process
    ProcessValue = OpenProcess(PROCESS_ALL_ACCESS, CLng(0), ProcessValueID)
    TerminateProcess(ProcessValue, CLng(0))
    CloseHandle ProcessValueID
    

    【讨论】:

    • 这也不起作用 - 一切完成后,“EXCEL.EXE *32”进程仍在进程列表中 - 包括托管 VBA 的应用程序。我不得不将 FindWindow 上的“Public”更改为“Private”,因为 VBA 抱怨模块中的公共声明,并且我将 PROCESS_ALL_ACCESS 更改为 PROCESS_TERMINATE,因为我找不到 PROCESS_ALL_ACCESS 的定义。由于我所做的只是终止,我认为这不会有什么不同。
    【解决方案5】:

    只需要使用:

    Private Sub Workbook_BeforeClose(Cancel As Boolean)
    
        Excel.Application.Quit
    
    End Sub
    

    【讨论】:

      猜你喜欢
      • 2011-06-18
      • 2012-02-18
      • 2012-06-08
      • 2021-01-25
      • 1970-01-01
      • 1970-01-01
      • 2016-04-10
      • 1970-01-01
      • 2012-02-05
      相关资源
      最近更新 更多