C # Interop: Excel-Prozess verlass nicht nach neuer Arbeitsblatt vorhandener Datei hinzufügen [Duplikat]

StackOverflow https://stackoverflow.com/questions/350454

  •  20-08-2019
  •  | 
  •  

Frage

  

Mögliche Duplizieren:
   Wie aufzuräumen richtig Excel Interop-Objekte in C #

Ich habe viele der anderen Threads hier über die Verwaltung von COM Referenzen lesen, während des .Net-Excel-Interop mit dem Excel-Prozess, um sicherzustellen, verläßt richtig beim Austritt, und so weit die Techniken wurden sehr gut funktioniert, aber ich vor kurzem über ein Problem kam, als neue Arbeitsblatt zu einer vorhandenen Arbeitsmappe-Datei hinzufügen.

Der folgende Code lässt einen Zombie-Excel-Prozess.

Wenn ich ein Arbeitsblatt in eine neu erstellte Arbeitsmappe Datei hinzufügen, tritt es in Ordnung. Wenn ich den Code mit Ausnahme der .Add() Zeile ausführen, tritt es in Ordnung. (Die vorhandene Datei lese ich aus ist eine leere Datei mit dem Kommentar gesetzt Code erstellt)

Irgendwelche Ideen?

//using Excel = Microsoft.Office.Interop.Excel;
//using System.Runtime.InteropServices;
public static void AddTest()
{
  string filename = @"C:\addtest.xls";
  object m = Type.Missing;
  Excel.Application excelapp = new Excel.Application();
  if (excelapp == null) throw new Exception("Can't start Excel");
  Excel.Workbooks wbs = excelapp.Workbooks;

  //if I create a new file and then add a worksheet,
  //it will exit normally (i.e. if you uncomment the next two lines
  //and comment out the .Open() line below):
  //Excel.Workbook wb = wbs.Add(Excel.XlWBATemplate.xlWBATWorksheet);
  //wb.SaveAs(filename, m, m, m, m, m, 
  //          Excel.XlSaveAsAccessMode.xlExclusive,
  //          m, m, m, m, m);

  //but if I open an existing file and add a worksheet,
  //it won't exit (leaves zombie excel processes)
  Excel.Workbook wb = wbs.Open(filename,
                               m, m, m, m, m, m,
                               Excel.XlPlatform.xlWindows,
                               m, m, m, m, m, m, m);

  Excel.Sheets sheets = wb.Worksheets;

  //This is the offending line:
  Excel.Worksheet wsnew = sheets.Add(m, m, m, m) as Excel.Worksheet; 

  //N.B. it doesn't help if I try specifying the parameters in Add() above

  wb.Save();
  wb.Close(m, m, m);

  //overkill to do GC so many times, but shows that doesn't fix it
  GC();
  //cleanup COM references
  //changing these all to FinalReleaseComObject doesn't help either
  while (Marshal.ReleaseComObject(wsnew) > 0) { } 
  wsnew = null;
  while (Marshal.ReleaseComObject(sheets) > 0) { }
  sheets = null;
  while (Marshal.ReleaseComObject(wb) > 0) { }
  wb = null;
  while (Marshal.ReleaseComObject(wbs) > 0) { }
  wbs = null;
  GC();
  excelapp.Quit();
  while (Marshal.ReleaseComObject(excelapp) > 0) { }
  excelapp = null;
  GC();
}

public static void GC()
{
  System.GC.Collect();
  System.GC.WaitForPendingFinalizers();
  System.GC.Collect();
  System.GC.WaitForPendingFinalizers();
}
War es hilfreich?

Lösung

Ich habe den Code nicht zur Hand, aber ich habe in ein ähnliches Problem auftreten. Wenn ich mich richtig erinnere, ich am Ende des Prozesses-ID der Excel-Instanz zu erhalten, und es zu töten (nach einer geeigneten Wartezeit, und wenn die anderen Verfahren nicht).

Ich glaube, ich verwende:

GetWindowThreadProcessId (über P / Invoke) auf der Objekt Hwnd Eigenschaft excel die Prozess-ID zu bekommen, und dann Process.GetProcessById verwendet, um ein Prozess-Objekt zu erhalten. Nachdem ich das getan hätte, würde ich Kill auf dem Prozess nennen.

EDIT: Ich muss zugeben, das ist nicht die ideale Lösung, aber wenn man nicht den Schurken Schnittstelle finden kann, der nicht freigegeben wird, dann wird dies in echten Eischale / Vorschlaghammer Art und Weise beheben. ;)

EDIT2: Sie haben nicht Kill auf den Prozess-Objekt rufen Sie uns sofort ... Sie zuerst Close versuchen könnte Aufruf vor dem Kill greifen

.

Andere Tipps

Ich habe ein ähnliches getan. Ich erstelle einen vorhandenen eine Excel-Datei oder öffnen. Ich lösche alle Blätter und meine eigenen hinzufügen. hier ist der Code, den ich alle Verweise geschlossen werden, um sicherzustellen, verwenden:

            workbook.Close(true, null, null);
            excelApp.Quit();

            if (newSheet != null)
            {
                System.Runtime.InteropServices.Marshal.ReleaseComObject(newSheet);
            }
            if (rangeSelection != null)
            {
            System.Runtime.InteropServices.Marshal.ReleaseComObject(rangeSelection);
            }
            if (sheets != null)
            {
                System.Runtime.InteropServices.Marshal.ReleaseComObject(sheets);
            }
            if (workbook != null)
            {
                System.Runtime.InteropServices.Marshal.ReleaseComObject(workbook);
            }
            if (excelApp != null)
            {
                System.Runtime.InteropServices.Marshal.ReleaseComObject(excelApp);
            }

            newSheet = null;
            rangeSelection = null;
            sheets = null;
            workbook = null;
            excelApp = null;

            GC.Collect();

ich dies mit vielen verschiedenen Optionen getestet haben und nicht hatte es noch auf mich nicht.

hier ist mein vollständiger Code die Excel Dich mit der Office12 .NET-Interop-Bibliothek erstellt zu töten: Genießen, -Alan.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;
using Microsoft.Office.Interop.Excel;

class Program
{

    /// <summary> 
    /// Win32 API import for getting the process Id. 
    /// The out param is the param we are after. I have no idea what the return value is. 
    /// </summary> 
    [DllImport("user32.dll")]
    private static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out IntPtr ProcessId); 

    static void Main(string[] args)
    {
        var app = new Application();
        IntPtr hwnd = new IntPtr(app.Hwnd);
        IntPtr processId;
        IntPtr foo = GetWindowThreadProcessId(hwnd, out processId);
        Process proc = Process.GetProcessById(processId.ToInt32());
        proc.Kill(); // set breakpoint here and watch the Windows Task Manager kill this exact EXCEL.EXE
        app.Quit(); // should give you a "Sorry, I can't find this Excel session since you killed it" Exception.
    }
}

Dies ist funktioniert bei mir sehr gut, ohne Ausnahmen.

Public Class ExcelHlpr

    Declare Function EndTask Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal ShutDown As Boolean, ByVal Force As Boolean) As Integer

    Dim cXlApp As Microsoft.Office.Interop.Excel.Application

    Public Function GetExcel() As Microsoft.Office.Interop.Excel.Application
        cXlApp = New Microsoft.Office.Interop.Excel.Application
        Return cXlApp
    End Function

    Public Function EndExcel() As Integer
        Dim xlHwnd As New IntPtr(cXlApp.Hwnd)
        Return EndTask(xlHwnd, False, True)
    End Function

End Class

Nicht sehr konstruktiv Ich weiß, aber ich getestet, um den Code genau wie oben und mein Excel-Prozess beendet gezeigt, wie erwartet, mein C: \ addtest.xls mit 8 neuen Blättern sitzt und kein Excel-Prozess ausgeführt werden
Könnte die. Interop-Version sein, die Ursache frage ich mich? Getestet habe ich mit 11 & 12.

Ich verwende VB.NET 3.5 SP1 und der folgende Code lässt noch EXCEL.EXE offen:

        xlWorkbook.Close(SaveChanges:=False)
        xlApplication.Quit()

        System.Runtime.InteropServices.Marshal.ReleaseComObject(xlRange)
        System.Runtime.InteropServices.Marshal.ReleaseComObject(xlWorksheet)
        System.Runtime.InteropServices.Marshal.ReleaseComObject(xlSheets)
        System.Runtime.InteropServices.Marshal.ReleaseComObject(xlWorkbook)
        System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApplication)

        xlRange = Nothing
        xlWorksheet = Nothing
        xlSheets = Nothing
        xlWorkbook = Nothing
        xlApplication = Nothing

        GC.GetTotalMemory(False)
        GC.Collect()
        GC.WaitForPendingFinalizers()

        GC.Collect()
        GC.WaitForPendingFinalizers()
        GC.Collect()
        GC.GetTotalMemory(True)

Andrew, hier ist der Code, den ich gefunden habe, das funktioniert. Ich dachte, ich post it hier posten für andere, die über gekommen:

namespace WindowHandler
{
using System;
using System.Text;
using System.Collections;
using System.Runtime.InteropServices;

/// <summary>
/// Window class for handling window stuff.
/// This is really a hack and taken from Code Project and mutilated to this small thing.
/// </summary>
public class Window
{
    /// <summary>
    /// Win32 API import for getting the process Id.
    /// The out param is the param we are after. I have no idea what the return value is.
    /// </summary>
    [DllImport("user32.dll")]
    private static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, out IntPtr ProcessId);

    /// <summary>
    /// Gets a Window's process Id.
    /// </summary>
    /// <param name="hWnd">Handle Id.</param>
    /// <returns>ID of the process.</returns>
    public static IntPtr GetWindowThreadProcessId(IntPtr hWnd)
    {
        IntPtr processId;
        IntPtr returnResult = GetWindowThreadProcessId(hWnd, out processId);

        return processId;
    }
}
}
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top