在C#中关闭Excel应用程序进程后访问数据

98
我正在使用C#编写一个应用程序,该程序打开一个Excel模板文件进行读/写操作。我希望当用户关闭应用程序时,Excel应用程序进程已关闭,且不保存Excel文件。在多次运行应用程序后查看我的任务管理器。
我使用以下代码打开Excel文件:
public Excel.Application excelApp = new Excel.Application();
public Excel.Workbook excelBook;
excelBook = excelApp.Workbooks.Add(@"C:/pape.xltx");

我使用以下代码来访问数据:

Excel.Worksheet excelSheet = (Worksheet)(excelBook.Worksheets[1]);
excelSheet.DisplayRightToLeft = true;
Range rng;
rng = excelSheet.get_Range("C2");
rng.Value2 = txtName.Text;

我在stackoverflow上看到了类似的问题,比如这个问题这个问题,以及测试答案,但都不起作用。


1
Excel和Word非常慢,并且存在许多怪癖,例如您遇到的问题。这些文件是带有一些XML和其他内容的zip文件。还有Open XML SDK(或者可能是更近期的东西)可以打开文档。这样的代码也可以在本地没有安装Office的情况下工作。考虑不使用Excel。 - Sten Petrov
22个回答

103

试试这个:

excelBook.Close(0); 
excelApp.Quit();

关闭工作簿时,您有三个可选参数:

Workbook.close SaveChanges, filename, routeworkbook 

Workbook.Close(false)或者如果你使用的是迟绑定,有时使用零更容易:Workbook.Close(0)。这就是我在自动关闭工作簿时所做的方法。

此外,我查找了文档,并在这里找到了它:Excel Workbook Close


谢谢你,亲爱的Michael,它正常工作了:excelBook.Close(0)。 - Javad Yousefi
大家好,当您打开一个 Excel 文件以供阅读时,这段代码可能无法正常工作。以下是代码:var excelApp = new Application(); var workBooks = excelApp.Workbooks.Open@"c:\temp\myexcel.xlsx"); workBooks.Close(0);excelApp.Quit(); - user1131926
我使用了applicationClass...,并且我使用了上述代码来释放对象,但它不起作用... - Singaravelan
4
在多次尝试失败后,我选择了这个解决方案来获取我打开的进程ID并将其直接结束。 - UndeadBob
@Singaravelan:你有检查过David Clarke的回答吗? - The Anh Nguyen
显示剩余2条评论

26
xlBook.Save();
xlBook.Close(true);
xlApp.Quit();
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp);

试一下这个方法.. 对我有效... 你应该释放那个xl应用程序对象来停止进程。


我必须同时释放我的工作簿和工作表才能停止这个进程。我正在使用PowerShell,所以看起来应该是[System.Runtime.InteropServices.Marshal] :: ReleaseComObject($xlBook)。大约4秒后进程停止了。 - Prof Von Lemongargle

17

参考:https://dev59.com/cGQm5IYBdhLWcg3wwxLF#17367570

避免使用双点调用表达式,例如:

var workbook = excel.Workbooks.Open(/*params*/)

...因为这样你不仅会为工作簿创建RCW对象,还会为Workbooks创建RCW对象,而且你也需要释放它们(如果没有维护对象的引用,则无法释放)。

这对我解决了问题。你的代码将变成:

public Excel.Application excelApp = new Excel.Application();
public Excel.Workbooks workbooks;
public Excel.Workbook excelBook;
workbooks = excelApp.Workbooks;
excelBook = workbooks.Add(@"C:/pape.xltx");

...

Excel.Sheets sheets = excelBook.Worksheets;
Excel.Worksheet excelSheet = (Worksheet)(sheets[1]);
excelSheet.DisplayRightToLeft = true;
Range rng;
rng = excelSheet.get_Range("C2");
rng.Value2 = txtName.Text;

然后释放所有这些对象:

System.Runtime.InteropServices.Marshal.ReleaseComObject(rng);
System.Runtime.InteropServices.Marshal.ReleaseComObject(excelSheet);
System.Runtime.InteropServices.Marshal.ReleaseComObject(sheets);
excelBook .Save();
excelBook .Close(true);
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlBook);
System.Runtime.InteropServices.Marshal.ReleaseComObject(workbooks);
excelApp.Quit();
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp);

我将这个代码块放在 try {} finally {} 中,以确保即使出现问题(可能会发生什么问题?),也能释放所有资源,例如:

我将其包装在try {} finally {}中,以确保即使出现错误(有什么是可能会出错的呢?),一切都能得到释放,例如:

(Two possible translations with slightly different phrasing, but both convey the same meaning.)
public Excel.Application excelApp = null;
public Excel.Workbooks workbooks = null;
...
try
{
    excelApp = new Excel.Application();
    workbooks = excelApp.Workbooks;
    ...
}
finally
{
    ...
    if (workbooks != null) System.Runtime.InteropServices.Marshal.ReleaseComObject(workbooks);
    excelApp.Quit();
    System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp);
}

2
这对我很有用,将工作簿分配给变量,以便可以清除它。还有一个观察结果,我在Web应用程序和控制台应用程序中使用完全相同的代码来清除Excel应用程序。在去掉双点引用之前,控制台应用程序中的代码运行良好,但在Web应用程序中运行时未清除EXCEL.EXE。我不确定它们为什么会表现不同,但是在Web应用程序中消除双点引用就解决了这个问题。 - IronHide
对我来说是有效的。我认为要释放com对象,我们必须释放所有相关的对象。我曾经遇到过关于solidworks程序的同样问题。 - Gs.
1
关闭工作簿,退出Excel应用程序,避免双点调用,并释放创建的每个COM对象。这对我很有用。救命之策。谢谢。 - Sinan ILYAS

13

想象一下,它会终止这个进程:

System.Diagnostics.Process[] process=System.Diagnostics.Process.GetProcessesByName("Excel");
foreach (System.Diagnostics.Process p in process)
{
    if (!string.IsNullOrEmpty(p.ProcessName))
    {
        try
        {
            p.Kill();
        }
        catch { }
    }
}

此外,你有没有尝试正常关闭它?
myWorkbook.SaveAs(@"C:/pape.xltx", missing, missing, missing, missing, missing, Microsoft.Office.Interop.Excel.XlSaveAsAccessMode.xlNoChange, missing, missing, missing, missing, missing);
excelBook.Close(null, null, null);                 // close your workbook
excelApp.Quit();                                   // exit excel application
excel = null;                                      // set to NULL

谢谢你的回答。你的第一个解决方案会关闭所有打开的Excel文件。当我使用“excelBook.Close”时,Excel保存对话框会出现,而我不想要它:( - Javad Yousefi
1
我进行了编辑,并在代码中添加了一行saveAs操作,这将通过代码替换对话框,希望能有所帮助。:) - Morris Miao
1
我只是使用了一个初始检查的方法来查找正在运行的Excel进程,并将其ID保存在列表中。然后启动了一个新的Excel进程,进行必要的编辑操作,并关闭了任何未包含在ID列表中的Excel进程。 - 182764125216
释放Excel不应该通过任务管理器来进行。你需要释放所有被创建的对象,包括那些在后台创建的对象,例如工作簿(Workbooks)。 - ehh
这样做的话,你会关闭每个正在运行的 Excel 进程。问题是,可能会有其他用户或应用程序同时使用 Excel。请牢记是否愿意这样做。 - Sinan ILYAS

7
杀死Excel并不总是容易的;请参阅此文章:50种杀死Excel的方法 本文摘自微软(MS Knowledge Base文章)中关于如何使Excel正常退出的最佳建议,但如果需要的话,还会通过杀死进程来确保它。我喜欢有第二个降落伞。
确保关闭任何打开的工作簿,退出应用程序并释放xlApp对象。最后检查进程是否仍然存在,如果是,则杀死它。
本文还确保我们不杀死所有Excel进程,而只杀死启动的确切进程。
另请参见从窗口句柄获取进程 这是我使用的代码:(每次都有效)
Sub UsingExcel()

    'declare process; will be used later to attach the Excel process
    Dim XLProc As Process

    'call the sub that will do some work with Excel
    'calling Excel in a separate routine will ensure that it is 
    'out of scope when calling GC.Collect
    'this works better especially in debug mode
    DoOfficeWork(XLProc)

    'Do garbage collection to release the COM pointers
    'http://support.microsoft.com/kb/317109
    GC.Collect()
    GC.WaitForPendingFinalizers()

    'I prefer to have two parachutes when dealing with the Excel process
    'this is the last answer if garbage collection were to fail
    If Not XLProc Is Nothing AndAlso Not XLProc.HasExited Then
        XLProc.Kill()
    End If

End Sub

'http://msdn.microsoft.com/en-us/library/ms633522%28v=vs.85%29.aspx
<System.Runtime.InteropServices.DllImport("user32.dll", SetLastError:=True)> _
    Private Shared Function GetWindowThreadProcessId(ByVal hWnd As IntPtr, _
    ByRef lpdwProcessId As Integer) As Integer
End Function

Private Sub ExcelWork(ByRef XLProc As Process)

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

    'or use early binding
    'Dim xlApp As Microsoft.Office.Interop.Excel

    '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
    XLProc = Process.GetProcessById(ProcIdXL)


    'do some work with Excel here using xlApp

    'be sure to save and close all workbooks when done

    'release all objects used (except xlApp) using NAR(x)


    'Quit Excel 
    xlApp.quit()

    'Release
    NAR(xlApp)

End Sub

Private Sub NAR(ByVal o As Object)
    'http://support.microsoft.com/kb/317109
    Try
        While (System.Runtime.InteropServices.Marshal.ReleaseComObject(o) > 0)
        End While
    Catch
    Finally
        o = Nothing
    End Try
End Sub

这是唯一对我有效的解决方案。谢谢。 - Jon Vote

4

我发现在While循环中使用Marshal.ReleaseComObject非常重要,并且最后需要进行垃圾回收

static void Main(string[] args)
{
    Excel.Application xApp = new Excel.Application();
    Excel.Workbooks xWbs = xApp.Workbooks;
    Excel.Workbook xWb = xWbs.Open("file.xlsx");

    Console.WriteLine(xWb.Sheets.Count);

    xWb.Close();
    xApp.Quit();

    while (Marshal.ReleaseComObject(xWb) != 0);
    while (Marshal.ReleaseComObject(xWbs) != 0);
    while (Marshal.ReleaseComObject(xApp) != 0);

    GC.Collect();
    GC.WaitForPendingFinalizers();
}

不必在 while 循环中调用 ReleaseComObject(),您也可以一次调用 FinalReleaseComObject()。这将在不需要循环的情况下将引用计数器重置为 0。请确保仅在您确定没有其他对 COM 对象的引用时才执行此操作。 - Mark

4

我遇到了相同的问题,并尝试了许多方法来解决它,但都没有成功。最终,我找到了自己的方法。一些参考资料在此输入链接说明

希望我的代码能帮助未来的某个人。我已经花费了两天以上的时间来解决它。 以下是我的代码:

//get current in useing excel
            Process[] excelProcsOld = Process.GetProcessesByName("EXCEL");
            Excel.Application myExcelApp = null;
            Excel.Workbooks excelWorkbookTemplate = null;
            Excel.Workbook excelWorkbook = null;
try{
    //DO sth using myExcelApp , excelWorkbookTemplate, excelWorkbook
}
catch (Exception ex ){
}
finally
            {
                //Compare the EXCEL ID and Kill it 
                Process[] excelProcsNew = Process.GetProcessesByName("EXCEL");
                foreach (Process procNew in excelProcsNew)
                {
                    int exist = 0;
                    foreach (Process procOld in excelProcsOld)
                    {
                        if (procNew.Id == procOld.Id)
                        {
                            exist++;
                        }
                    }
                    if (exist == 0)
                    {
                        procNew.Kill();
                    }        
                }
            }

2

您可以使用自己的COM对象Excel PID来结束进程。

请在导入DLL代码下方添加。

[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowThreadProcessId(IntPtr hwnd, ref int lpdwProcessId);

并使用

 if (excelApp != null)
            {
                int excelProcessId = -1;
                GetWindowThreadProcessId(new IntPtr(excelApp.Hwnd), ref excelProcessId);

                Process ExcelProc = Process.GetProcessById(excelProcessId);
                if (ExcelProc != null)
                {
                    ExcelProc.Kill();
                }
            }

1
         wb.Close();
         app.Quit();

         System.Diagnostics.Process[] process = System.Diagnostics.Process.GetProcessesByName("Excel");
         foreach (System.Diagnostics.Process p in process)
         {
             if (!string.IsNullOrEmpty(p.ProcessName) && p.StartTime.AddSeconds(+10) > DateTime.Now)
             {
                 try
                 {
                     p.Kill();
                 }
                 catch { }
             }
         }

它关闭了最后10秒钟名称为“Excel”的进程。


1

对于每个Excel对象使用一个变量,并且必须循环Marshal.ReleaseComObject >0。如果没有循环,Excel进程仍然保持活动状态。

public class test{
        private dynamic ExcelObject;
        protected dynamic ExcelBook;
        protected dynamic ExcelBooks;
        protected dynamic ExcelSheet;

public void LoadExcel(string FileName)
        {
            Type t = Type.GetTypeFromProgID("Excel.Application");
            if (t == null) throw new Exception("Excel non installato");
            ExcelObject = System.Activator.CreateInstance(t);
            ExcelObject.Visible = false;
            ExcelObject.DisplayAlerts = false;
            ExcelObject.AskToUpdateLinks = false;
            ExcelBooks = ExcelObject.Workbooks;
            ExcelBook = ExcelBooks.Open(FileName,0,true);
            System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");
            ExcelSheet = ExcelBook.Sheets[1];
        }
 private void ReleaseObj(object obj)
        {
            try
            {
                int i = 0;
             while(   System.Runtime.InteropServices.Marshal.ReleaseComObject(obj) > 0)
                {
                    i++;
                    if (i > 1000) break;
                }
                obj = null;
            }
            catch 
            {
                obj = null;
            }
            finally
            {
                GC.Collect();
            }
        }
        public void ChiudiExcel() {
            System.Threading.Thread.CurrentThread.CurrentCulture = ci;

            ReleaseObj(ExcelSheet);
            try { ExcelBook.Close(); } catch { }
            try { ExcelBooks.Close(); } catch { }
            ReleaseObj(ExcelBooks);
            try { ExcelObject.Quit(); } catch { }
            ReleaseObj(ExcelObject);
        }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接