Killing EXCEL.exe Process from C# in a Windows Service

我怕爱的太早我们不能终老 提交于 2019-11-27 15:16:49

问题


I have a windows service that opens up an Excel spreadsheet via the Microsoft.Office.Interop.Excel.Application object.

Application xlApp = new Application();
Workbook workbook = xlApp.Workbooks.Open(fileName, 2, false);
...
...
workbook.Close();
xlApp.Quit();

I would like to kill the EXCEL.exe process that is left running after it is done working with the workbook.

I've tried the following with no success...

// This returns a processId of 0
IntPtr processId;
GetWindowThreadProcessId(new IntPtr(xlApp.Hwnd), out processId);
Process p = Process.GetProcessById(processId.ToInt32());   
p.Kill();

Anyone have any ideas as to how I can do this via a Windows Service?


回答1:


Properly closing the open Excel workbook and quitting the app is extremely difficult. If I can find the links I'll post them, but essentially you must clean up all references to any COM object that you create. This includes everything from ODBCConnections (data connections), Worksheets, Workbooks, and the Excel application. A combination I got to work involved garbage collection and the System.Runtime.InteropServices.Marshal object:

// Garbage collecting
GC.Collect();
GC.WaitForPendingFinalizers();
// Clean up references to all COM objects
// As per above, you're just using a Workbook and Excel Application instance, so release them:
workbook.Close(false, Missing.Value, Missing.Value);
xlApp.Quit();
Marshal.FinalReleaseComObject(workbook);
Marshal.FinalReleaseComObject(xlApp);

Like you mentioned, looping through and killing each Excel process is usually not a good idea, since if you're running this as a Windows app you may close Excel on your user, or in a service also close an instance of Excel that is running via some other program.

Edit: See this question for more info.




回答2:


You need to check file handles and get PID, that are opened by process and then kill it. It worked for me.

private void genExcel(
{
   int pid = -1;
   //Get PID
   xlApp = new Excel.Application();
   HandleRef hwnd = new HandleRef(xlApp, (IntPtr)xlApp.Hwnd);
   GetWindowThreadProcessId(hwnd, out pid);
   .
   .
   .
   .
   //Finally
   KillProcess(pid,"EXCEL");
}

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowThreadProcessId(HandleRef handle, out int processId);

private void KillProcess(int pid, string processName)
{
    // to kill current process of excel
    System.Diagnostics.Process[] AllProcesses = System.Diagnostics.Process.GetProcessesByName(processName);
    foreach (System.Diagnostics.Process process in AllProcesses)
    {
       if (process.Id == pid)
       {
         process.Kill();
       }
    }
    AllProcesses = null;
}



回答3:


After much reading and frustration I've found a solution!

All credit goes to dotNetkow, nightcoder and Mike Rosenblum for their solutions on this post: How do I properly clean up Excel interop objects?

Here is what I did...
1. Changed build mode of the project to "Release" (in DEBUG mode, COM objects have a hard time disposing of their references.
2. Removed all double dot expressions (all COM objects should be tied to a variable so they can be released)
3. Calling GC.Collect(), GC.WaitForPendingFinalizers(), and Marshal.FinalReleaseComObject() explicitly in a finally block

Here is the acutal code I am using:

Application xlApp = null;
Workbooks workbooks = null;
Workbook workbook = null;
Worksheet sheet = null;
Range r = null;
object obj = null;

try
{
    xlApp = new Application();
    xlApp.DisplayAlerts = false;
    xlApp.AskToUpdateLinks = false;
    workbooks = xlApp.Workbooks;
    workbook = workbooks.Open(fileName, 2, false);
    sheet = workbook.Worksheets[1];

    r = sheet.get_Range("F19");
    obj = r.get_Value(XlRangeValueDataType.xlRangeValueDefault);
}
finally
{
    GC.Collect();
    GC.WaitForPendingFinalizers();
    if (value != null) Marshal.FinalReleaseComObject(value);
    if (r != null) Marshal.FinalReleaseComObject(r);
    if (sheet != null) Marshal.FinalReleaseComObject(sheet);
    if (workbooks != null) Marshal.FinalReleaseComObject(workbooks);
    if (workbook != null)
    {
        workbook.Close(Type.Missing, Type.Missing, Type.Missing);
        Marshal.FinalReleaseComObject(workbook);
    }
    if (xlApp != null)
    {
        xlApp.Quit();
        Marshal.FinalReleaseComObject(xlApp);
    }
}



回答4:


I don't know if my answer is not what you're searching for... if so tell me and i gonna delete it. Anyway I used this:

Application xlApp = new Application();
xlApp.DisplayAlerts = false;
xlApp.Visible = true; // Only for debug purposes
Workbook workbook = xlApp.Workbooks.Open(filename, 2, false);
...
...
workbook.Close();
xlApp.Quit();

Closing workbook and quitting xlApp removes EXCEL.EXE from memory in my pc.
I'm using Windows XP 32bit and Microsoft Office 2007.

I've also tried to open another excel file before working with this test-app: a second EXCEL.EXE gets opened and (using Quit) closed at the end, leaving the first instance untouched.




回答5:


I have used a simple but effective solution

finally   { 
GC.Collect();
GC.WaitForPendingFinalizers();           
        if (xlApp != null)
            {
                xlApp .Quit();
                int hWnd = xlApp .Application.Hwnd;
                uint processID;GetWindowThreadProcessId((IntPtr)hWnd, out processID);
                Process[] procs = Process.GetProcessesByName("EXCEL");
                foreach (Process p in procs)
                {
                    if (p.Id == processID)
                        p.Kill();
                }
                Marshal.FinalReleaseComObject(xlApp );
            } 
        }

Find all Excell.exe processes . then get the process ID of my excelApplication . kill only the process whose id match. Use to declare GetWindowThreadProcessId in the class :

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



回答6:


My solution

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

private void GenerateExcel()
{
    var excel = new Microsoft.Office.Interop.Excel.Application();
    int id;
    // Find the Process Id
    GetWindowThreadProcessId(excel.Hwnd, out id);
    Process excelProcess = Process.GetProcessById(id);

try
{
    // Your code
}
finally
{
    excel.Quit();

    // Kill him !
    excelProcess.Kill();
}



回答7:


Below is the code which open and deletes the Excel instance. We just need to make sure all objects related to Excel are closed.

    string strFilePath = @"C:\Sample.xlsx";
        try
        {
            Excel.Application excelApp = null;
        Excel.Workbook excelWorkbook = null;
        Excel.Sheets excelSheets = null;
        Excel.Worksheet excelWorksheet = null;
        Excel.Workbooks excelWorkbooks = null;
        Excel.Range excelUsedRange = null;



            excelApp = new Microsoft.Office.Interop.Excel.Application();
            int nData = excelApp.Hwnd;
            // excelApp = new Excel.ApplicationClass();
            //excelApp.Visible = true;
            excelWorkbooks = excelApp.Workbooks;
            excelWorkbook = excelWorkbooks.Add(System.Reflection.Missing.Value);

            excelWorkbook = excelApp.Workbooks.Open(strFilePath, 2, false);
            //excelWorkbook = excelApp.Workbooks.Open(strFilePath,
            //                                                                                       
               Type.Missing, Type.Missing,
            //                                                                                    
               Type.Missing, Type.Missing,
            //                                                                                    
               Type.Missing, Type.Missing,
            //                                                                                    
               Type.Missing, Type.Missing,
            //                                                                                    
               Type.Missing, Type.Missing,
            //                                                                                    
               Type.Missing, Type.Missing,
            //                                                                                    
               Type.Missing, Type.Missing);


            excelSheets = excelWorkbook.Worksheets;
           // excelWorksheet = (Excel.Worksheet)excelSheets.get_Item(1);
            excelWorksheet = (Excel.Worksheet)excelWorkbook.Worksheets["Dem0_1"];



            excelUsedRange = excelWorksheet.UsedRange;


            //Excel.Range lastCell = usedRange.SpecialCells(Excel.XlCellType.xlCellTypeLastCell, Type.Missing);
            //int lastRow = lastCell.Row;
            //int lastCol = lastCell.Column;
            //int rowMin = lastRow + 1;
            //int colMin = lastCol + 1;

            int nRowsCount = excelUsedRange.Rows.Count;
            int nColCount = excelUsedRange.Columns.Count;



             int N_Quality_Header = -1;
             int N_Measurement_Name = -1;
             int N_Lower_Tolerance = -1;
             int N_Upper_Tolerance = -1;


             //Read the Columns Index 
             for (int nColIndex = 1; nColIndex <= nColCount; nColIndex++)
             {
                 Excel.Range cell = usedRange.Cells[1, nColIndex] as Excel.Range;
                 String strCellValue = cell.Value2.ToString();
                 if (strCellValue == "Quality Header")
                     N_Quality_Header = nColIndex;

                 else if (strCellValue.IndexOf("Measurement Name", StringComparison.OrdinalIgnoreCase) > -1)
                     N_Measurement_Name = nColIndex;
                 else if (strCellValue.IndexOf("Lower Tolerance", StringComparison.OrdinalIgnoreCase) > -1)
                     N_Lower_Tolerance = nColIndex;
                 else if (strCellValue.IndexOf("Upper Tolerance", StringComparison.OrdinalIgnoreCase) > -1)
                     N_Upper_Tolerance = nColIndex;
             }

             //Read all rows to get the values
             for (int nRowIndex = 2; nRowIndex <= nRowsCount; nRowIndex++)
             {
                 Excel.Range cellQualityHeader = usedRange.Cells[nRowIndex, N_Quality_Header] as Excel.Range;
                 String strValue = cellQualityHeader.Value2.ToString();
                 if (strValue == String_Empty)
                     continue;


             }


        }
        catch (Exception oException)
        {


        }
        finally
        {
            excelUsedRange.Clear();
            //excelWorkbook.Save();
            excelWorkbook.Close(false, System.Reflection.Missing.Value, System.Reflection.Missing.Value);

            excelWorkbooks.Close();
            excelApp.Quit();

            Marshal.ReleaseComObject(excelUsedRange);
            Marshal.ReleaseComObject(excelWorksheet);
            Marshal.ReleaseComObject(excelSheets);
            Marshal.ReleaseComObject(excelWorkbooks);
            Marshal.ReleaseComObject(excelWorkbook);
            Marshal.ReleaseComObject(excelApp);


            excelUsedRange = null;
            excelWorksheet = null;
            excelSheets = null;
            excelWorkbooks = null;
            excelWorkbook = null;
            excelApp = null;

            GC.GetTotalMemory(false);
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
            GC.GetTotalMemory(true);



        }



回答8:


I would use Process.GetProcess and look for the exe there I wouldn't trust anything having to do with the window in the service world as I think the windows get created on a deskstation you don't have access to.




回答9:


I am using:

Process[] AllProcesses = Process.GetProcessesByName("EXCEL.EXE");

to have the process killed.




回答10:


Maybe it's not so very elegant, but I ended up with a combination of the accepted solution and the one of Safrin. So first I try to do it the elegant way and if it fails I use brute force. The reason is that the code is part of a batch procedure which must be able to continue even when one Excel refresh operation failes. My problem was that some failures had to do with faults in the PowerPivot model which brought up a dialog with an error message. This dialog was not visible because it runs as background process and it seemed that Excel wouldn't close, and my process wouldn't continue, untill the dialog was closed (?!). So starting the proces in a separate thread with an time out mechanism and kill Excel on disposale of my work object if quiting doesn't work was the only solution I could think of (that works)...

    public void Dispose()
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        if (workbook != null)
        {
            try
            {
                workbook.Close(false);
                Marshal.FinalReleaseComObject(workbook);
            }
            catch { }
        }
        if (excel != null)
        {
            try { excel.Quit(); }
            catch {
                int hWnd = excel.Application.Hwnd;
                uint processID; 
                GetWindowThreadProcessId((IntPtr)hWnd, out processID);
                Process[] procs = Process.GetProcessesByName("EXCEL");
                foreach (Process p in procs)
                {
                    if (p.Id == processID) p.Kill();
                }

            }
            Marshal.FinalReleaseComObject(excel);
        }
    }



回答11:


This is my code to kill all unused Excel Process

Process[] process = Process.GetProcessesByName("excel");
        foreach (Process excel in process)
        {
            if (excel.HasExited)
            {
                excel.Kill();
            }
        }
        process = null;


来源:https://stackoverflow.com/questions/5627560/killing-excel-exe-process-from-c-sharp-in-a-windows-service

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!