如何从创建的“Excel.Application”对象中获取进程ID?

时间:2013-07-20 05:42:03

标签: vb.net excel

如何从正在运行的对象中获取进程ID?

Dim xlApp As Object  = CreateObject("Excel.Application")

我需要使用后期绑定,因为我无法保证我将使用哪个版本,因此使用Microsoft.Office.Interop.Excel将无效。

'do some work with xlApp

xlApp.Quit
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp)
xlApp = nothing

此时Excel仍在后台运行。我熟悉使用变量并释放它们的所有建议,然后使用:System.Runtime.InteropServices.Marshal.ReleaseComObject(o)。这不能可靠地工作。我正在做的工作非常复杂。我使用多个文件用于每个循环等。无法在Excel中释放所有资源。我需要一个更好的选择。

我想在Excel上使用Process.Kill,但我不知道如何从xlApp对象获取进程。我不想杀死所有Excel进程,因为用户可能打开了工作簿。

我尝试使用Dim xProc As Process = Process.Start(ExcelPath)然后使用xProc.Kill()这有时会起作用,但如果用户已经拥有Excel,则使用XLApp = GetObject("Book1").ApplicationXLApp = GetObject("", "Excel.Application")获取正确的Excel对象有点棘手窗户打开。我需要一个更好的选择。

我无法使用GetActiveObjectBindToMoniker来获取Excel对象,因为它们仅在使用早期绑定时才能使用。例如。 Microsoft.Office.Interop.Excel

如何从正在运行的对象中获取进程ID?

编辑:实际上我并没有真正对如何让Excel很好地退出的改变感兴趣。许多其他问题都解决了这个问题herehere我只想杀了它;干净,准确,直接。我想要杀死我开始的确切过程而不是其他过程。

6 个答案:

答案 0 :(得分:6)

使用Marshal.ReleaseComObject()或杀死Excel.exe进程是非常丑陋,容易出错并且对此问题不必要的创可贴。从长远来看,这是非常有害的,this question显示了可能发生的事情。正确的方法是调用GC.Collect(),但是阅读this answer以了解为什么在调试程序时这种方法无效。

解决方法很简单,您只需要确保在不同的方法中调用GC.Collect()。这可确保您的Excel对象引用不再在范围内。因此,执行此操作的程序的粗略轮廓将是:

Sub Main()
    DoOfficeStuff()
    GC.Collect()
    GC.WaitForPendingFinalizers()
    '' Excel.exe will now be gone
    '' Do more work
    ''...
End Sub

Sub DoOfficeStuff()
    Dim xlApp As Object = CreateObject("Excel.Application")
    '' etc..
End Sub

答案 1 :(得分:4)

其实没关系;我想到了。这是一个非常干净,精确定位的解决方案,可以杀死已启动的确切过程。它不会干扰用户可能已打开的任何其他进程或文件。根据我的经验,在关闭文件和退出Excel之后终止进程是处理Excel的最快速,最简单的方法。 Here is a knowledge Base article描述了问题和Microsoft推荐的解决方案。

请注意,此解决方案不会终止Excel应用程序。如果没有正确处理任何指针,它只会杀死空进程shell。 Excel本身在我们调用xlApp.quit()时实际上已退出。这可以通过尝试附加正在运行的Excel应用程序来确认,因为Excel根本没有运行,将会失败。

许多人不建议杀死这个过程;看到 How to properly clean up Excel interop objectsUnderstanding Garbage Collection in .net

另一方面,很多人不建议使用GC.Collect。见What's so wrong about using GC.Collect()?

确保关闭所有打开的工作簿,退出应用程序,释放xlApp对象。最后检查过程是否仍然存在,如果是,则将其杀死。

Private Declare Auto Function GetWindowThreadProcessId Lib "user32.dll" (ByVal hwnd As IntPtr, _
              ByRef lpdwProcessId As Integer) As Integer

Sub testKill()

    'start the application
    Dim xlApp As Object = CreateObject("Excel.Application")

    'do some work with Excel

    'close any open files

    'get the window handle
    Dim xlHWND As Integer = xlApp.hwnd

    'this will have the process ID after call to GetWindowThreadProcessId
    Dim ProcIdXL As Integer = 0

    'get the process ID
    GetWindowThreadProcessId(xlHWND, ProcIdXL)

    'get the process
    Dim xproc As Process = Process.GetProcessById(ProcIdXL)

    'Quit Excel
    xlApp.quit()

    'Release
    System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp)

    'set to nothing
    xlApp = Nothing

    'kill it with glee
    If Not xproc.HasExited Then
        xproc.Kill()
    End If

End Sub

一旦我意识到我可以从Excel获取窗口句柄,那么我只需要该函数从窗口句柄中获取进程ID。因此GetWindowThreadProcessId如果有人知道vb.net的方式,我会很感激。

答案 2 :(得分:1)

Public Declare Function GetWindowThreadProcessId Lib "user32" _
  (ByVal hwnd As Long, _
   ByRef lpdwProcessId As Long) As Long

Function KillProcess(hwnd As Long)
  Dim CurrentForegroundThreadID As Long
  Dim strComputer As String
  Dim objWMIService
  Dim colProcessList
  Dim objProcess
  Dim ProcIdXL As Long

  ProcIdXL = 0
  CurrentForegroundThreadID = GetWindowThreadProcessId(hwnd, ProcIdXL)

  strComputer = "."

  Set objWMIService = GetObject _
  ("winmgmts:\\" & strComputer & "\root\cimv2")
  Set colProcessList = objWMIService.ExecQuery _
    ("Select * from Win32_Process Where ProcessID =" & ProcIdXL)
  For Each objProcess In colProcessList
    objProcess.Terminate
  Next

End Function


KillProcess (ExcelApplication.hwnd)

答案 3 :(得分:0)

对于C#使用:

    [DllImport("user32.dll")]
    private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    uint iProcessId = 0;

    //Get the process ID of excel so we can kill it later.
    GetWindowThreadProcessId((IntPtr)ExcelObj.Hwnd, out iProcessId);

    try
    {
        Process pProcess = Process.GetProcessById((int)iProcessId);
        pProcess.Kill();
    }
    catch (System.Exception)
    {
        //just ignore any failure.
    }

答案 4 :(得分:0)

我找到了解决方法(VB源代码):

Private Declare Auto Function GetWindowThreadProcessId Lib "user32.dll" (ByVal hwnd As IntPtr, ByRef lpdwProcessId As Integer) As Integer

首先将Marshal.ReleaseComObject()的所有详细对象设置为Nothing。

关闭最后一个工作簿后,将Excel窗口移至顶部,然后等待1000毫秒(工作机会的价值):

exlBook.Close()
Dim exlProcId As Integer = Nothing
GetWindowThreadProcessId(exlApp.Hwnd, exlProcId)
AppActivate(exlProcId)
Threading.Thread.Sleep(1000)
Marshal.ReleaseComObject(exlBook)
exlBook = Nothing

最后:

exlApp.Quit()
exlApp = Nothing
GC.Collect()
GC.WaitForPendingFinalizers()
GC.Collect()
GC.WaitForPendingFinalizers()

我在关闭工作簿后停止程序,然后打开Excel窗口,然后继续执行程序并关闭Excel进程时发现了这一点。

亲切的问候: 德国汉诺威,托尔斯滕

答案 5 :(得分:0)

在开发无人值守的应用程序时,我每天都会遇到此问题,该应用程序每天创建50至100个Excel工作簿并通过电子邮件发送给他们。无论我做什么,有时甚至在GC.WaitForPendingFinalizers()之后,任务管理器中都会挂起一个Excel实例。

由于该应用程序无人值守,因此我不能冒用Excel吞噬机器的风险,因此我将所有方法组合为:

Private Declare Auto Function GetWindowThreadProcessId Lib "user32.dll" (ByVal hwnd As IntPtr, ByRef lpdwProcessId As Integer) As Integer

        Dim XLApp As Excel.Application
        Dim XLHwnd As Integer
        Dim XLProcID As Integer
        Dim XLProc As Process

        XLApp = New Excel.Application

        XLHwnd = XLApp.Hwnd
        GetWindowThreadProcessId(CType(XLHwnd, IntPtr), XLProcID)
        XLProc = Process.GetProcessById(XLProcID)

        DoStuffWithExcel()

        Try
            XLApp.Quit()
            System.Runtime.InteropServices.Marshal.ReleaseComObject(XLApp)
            XLApp = Nothing
            GC.Collect()
            GC.WaitForPendingFinalizers()

            If Not XLProc.HasExited Then
                XLProc.Kill()
            End If
        Catch ex As Exception
            XLApp = Nothing
        Finally
            GC.Collect()
        End Try

我尝试优雅地退出,但是如果失败,我将终止进程,因为必须这样做!

到目前为止,它本身就是行为;-)