Автоматизация VBA в Excel оставляет процесс в памяти после выхода

Я видел много предложений по этой проблеме и перепробовал их все, но, похоже, ни один из них не работает. Код VBA находится в продукте стороннего производителя (SAP Business Objects, что может быть проблемой). Создаю объект Excel:

Set oExcel = CreateObject("Excel.Application")

Загрузите содержимое из столбца 1 одной из рабочих таблиц в конкретную книгу, затем закройте Excel. Каждый раз он оставляет процесс в памяти, занимая более 5 МБ памяти.

Я попытался сделать объект oExcel видимым, чтобы, по крайней мере, я мог убить его, не прибегая к диспетчеру задач, но когда я вызываю Quit, пользовательский интерфейс завершает работу и все еще оставляет процесс.

Каждый раз, когда я запускаю код, он создает новый процесс. Поэтому я попытался повторно использовать любые существующие процессы Excel, вызвав

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

и создавать его только в том случае, если этот вызов ничего не возвращает,

Это не увеличивало количество процессов, но один процесс увеличивался каждый раз на 5+ МБ, так что, по сути, та же проблема.

В каждом случае я закрываю открытую книгу и устанавливаю для 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

person user3224542    schedule 22.01.2014    source источник
comment
Я думаю, что диспетчер задач заведомо ненадежен для такого рода вещей ... Я думаю, что здесь были другие вопросы по аналогичной теме, и все согласны с тем, что TM неверна. Дай мне посмотреть, смогу ли я это подтвердить.   -  person David Zemens    schedule 22.01.2014
comment
Попробуйте сменить m_oBook.Close SaveChanges:=False на m_oBook.Saved = True   -  person Dmitry Pavliv    schedule 22.01.2014
comment
хмммм, я играю с этим и Set m_oExcel = Nothing освобождает его из диспетчера задач каждый раз; даже если я этого не сделаю m_oExcel.Quit. Фактически, даже если я не установил для него значение «Ничего», диспетчер задач удалит процесс после выполнения.   -  person David Zemens    schedule 22.01.2014
comment
Set oExcel = CreateObject("Excel.Application") ‹- не создает процесс Excel.Exe в диспетчере задач. Попробуйте выполнить пошаговый код, чтобы увидеть, когда экземпляр действительно запускается, а затем посмотрите на свой код, проходя пошагово, и выясните, в какой момент вы хотите убить экземпляр (в диспетчере задач). Как только вы узнаете, какая строка не выполняет то, что вы хотите, отредактируйте сообщение, используя только соответствующий код.   -  person    schedule 23.01.2014
comment
Вы уверены, что смотрите на процессы в диспетчере задач, а не на приложения? Я прохожу через него, и сразу после вызова CreateObject () он появляется в Processes.   -  person user3224542    schedule 23.01.2014
comment
Что касается m_oBook.Saved = True vs. SaveChanges: = False, рабочая книга не изменяется, поэтому любой из них является лишним. Наблюдая за процессом в диспетчере задач, я вижу, что в обоих случаях размер процесса в конечном итоге изменяется с ~ 50 МБ до ~ 5, поэтому .Close имеет некоторый эффект. Могут помочь еще несколько деталей: я запускаю Office 2010 в 64-разрядной ОС Win7 Enterprise. Процесс читает EXCEL.EXE * 32.   -  person user3224542    schedule 23.01.2014
comment
Какое приложение люди используют для тестирования этого? Как я уже отмечал, это выполняется в приложении, отличном от Microsoft (SAP Business Objects). Если вы тестируете приложение Office, вы можете случайно избежать истинной проблемы.   -  person user3224542    schedule 23.01.2014
comment
Не вижу детали при загрузке в колонке? Возможно, у вас есть единый глобальный справочник - см. здесь   -  person brettdj    schedule 25.01.2014
comment
Столбцы загружаются в функцию DelimitedList (tabName), которая ссылается на переменную уровня модуля m_oBook. Первоначально я передал объект рабочего листа, но изменил его на переменные уровня модуля, чтобы на любой объект Excel всегда была только одна ссылка. Одно из предложений заключалось в том, чтобы сделать это таким образом, чтобы избежать неявных ссылок, которые оставляли бы счетчик ссылок ›1 при выходе. Не сработало.   -  person user3224542    schedule 27.01.2014
comment
Excel недавно изменил некоторые вещи (я думаю, в Office 2010?) В отношении нескольких документов в одном экземпляре. Интересно, закрывает ли .Quit только текущий документ, но не сам сеанс. Возможно, стоит попробовать позвонить .Quit несколько раз, чтобы посмотреть, может ли это быть проблемой.   -  person Chris Rae    schedule 06.03.2014


Ответы (5)


В большинстве случаев это происходит из-за того, что Excel сохраняет открытой надстройку COM. Попробуйте воспользоваться ссылкой ниже, чтобы получить помощь по удалению надстройки COM.

Добавить или удалить надстройки

Особое утешение нахожу в записке:

Примечание. При этом надстройка удаляется из памяти, но ее имя остается в списке доступных надстроек. Он не удаляет надстройку с вашего компьютера.

person Dave Excel    schedule 14.12.2014
comment
Используйте oExcel.COMAddIns([Index]).Connect = False, чтобы отключить любые надстройки COM, которые у вас могут быть активными. Это решило эту проблему для меня. - person 110SidedHexagon; 09.02.2017

Добавление ответа на основе комментария Дэвида Земенса. Работает на меня.

m_oExcel.Quit            '<- Still in Task Manager after this line
Set m_oExcel = Nothing   '<- Gone after this line
person Q---ten    schedule 11.10.2017
comment
У меня тоже сработало, спасибо за исправление Q --- ten - person Riley Carney; 28.08.2018

На этот вопрос уже ответил Acantud в ответ на последующее сообщение: https://stackoverflow.com/questions/25147242 Полностью уточните свои ссылки на объекты в книге Excel, которую вы открываете, чтобы избежать создания потерянных процессов в диспетчере задач. В этом случае решение состоит в том, чтобы префикс DelimitedList с m_oBook, например

ThisDocument.Variables("Agent(s)").Value = m_oBook.DelimitedList("Agents")
person lonestorm    schedule 06.06.2016

Хотя этого не должно происходить, вы можете отправить 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
person Uri Goren    schedule 17.02.2014
comment
Это тоже не сработало - процесс EXCEL.EXE * 32 все еще находится в списке процессов после того, как все будет завершено, включая приложение, на котором размещен VBA. Мне пришлось изменить Public в FindWindow на Private, поскольку VBA пожаловался на публичное объявление в модуле, и я изменил PROCESS_ALL_ACCESS на PROCESS_TERMINATE, потому что не смог найти определение PROCESS_ALL_ACCESS. Поскольку все, что я делаю, это завершение работы, я полагаю, это не повлияет на ситуацию. - person user3224542; 19.02.2014

Нужно использовать только:

Private Sub Workbook_BeforeClose(Cancel As Boolean)

    Excel.Application.Quit

End Sub
person TomazVDSN    schedule 24.06.2016