Hvordan kan jeg rydde skikkelig opp Excel interoperabilitets gjenstander?

stemmer
677

Jeg bruker Excel Interop i C # ( ApplicationClass) og har satt følgende kode i min endelig klausul:

while (System.Runtime.InteropServices.Marshal.ReleaseComObject(excelSheet) != 0) { }
excelSheet = null;
GC.Collect();
GC.WaitForPendingFinalizers();

Selv om denne typen verk, Excel.exeer prosessen fortsatt i bakgrunnen, selv etter at jeg lukker Excel. Det er kun frigitt når søknaden min er manuelt lukket.

Hva gjør jeg feil, eller er det et alternativ for å sikre interoperabilitets objekter er tilstrekkelig kastes?

Publisert på 01/10/2008 klokken 15:18
kilden bruker
På andre språk...                            


41 svar

stemmer
34

Dette fungerte for et prosjekt jeg jobbet på:

excelApp.Quit();
Marshal.ReleaseComObject (excelWB);
Marshal.ReleaseComObject (excelApp);
excelApp = null;

Vi lærte at det var viktig å sette hver referanse til en Excel-COM-objektet til null når du var ferdig med det. Dette inkluderte Cells, Ark, og alt.

Svarte 01/10/2008 kl. 15:30
kilden bruker

stemmer
645

Excel avsluttes ikke fordi søknaden er fremdeles holder referanser til COM-objekter.

Jeg antar du påkalle minst ett medlem av et COM-objekt uten å tilordne den til en variabel.

For meg var det de excelApp.Worksheets objekt som jeg brukt direkte uten å tilordne den til en variabel:

Worksheet sheet = excelApp.Worksheets.Open(...);
...
Marshal.ReleaseComObject(sheet);

Jeg visste ikke at internt C # skapt en wrapper for regneark COM-objektet som ikke blir utgitt av koden min (fordi jeg ikke var klar over det) og var årsaken til at Excel ikke ble losset.

Jeg fant løsningen på problemet mitt på denne siden , som også har en fin regel for bruk av COM-objekter i C #:

Bruk aldri to prikker med COM-objekter.


Så med denne kunnskapen riktig måte å gjøre det over er:

Worksheets sheets = excelApp.Worksheets; // <-- The important part
Worksheet sheet = sheets.Open(...);
...
Marshal.ReleaseComObject(sheets);
Marshal.ReleaseComObject(sheet);
Svarte 01/10/2008 kl. 15:30
kilden bruker

stemmer
29

Alt som er i Excel-navnerom trenger å bli utgitt. Periode

Du kan ikke gjøre:

Worksheet ws = excel.WorkBooks[1].WorkSheets[1];

Du må være å gjøre

Workbooks books = excel.WorkBooks;
Workbook book = books[1];
Sheets sheets = book.WorkSheets;
Worksheet ws = sheets[1];

etterfulgt av frigjøring av gjenstandene.

Svarte 01/10/2008 kl. 15:45
kilden bruker

stemmer
1

Jeg tror at noe av det er bare slik at rammeverket håndterer Office-programmer, men jeg kan ta feil. På noen dager, noen programmer rydde opp prosessene umiddelbart, og andre dager det synes å vente til programmet lukkes. Generelt, sluttet jeg å betale oppmerksomhet til detaljer og bare sørge for at det ikke er noen ekstra prosesser flyter rundt på slutten av dagen.

Også, og kanskje jeg er over å forenkle ting, men jeg tror du kan bare ...

objExcel = new Excel.Application();
objBook = (Excel.Workbook)(objExcel.Workbooks.Add(Type.Missing));
DoSomeStuff(objBook);
SaveTheBook(objBook);
objBook.Close(false, Type.Missing, Type.Missing);
objExcel.Quit();

Som jeg sa tidligere, jeg har en tendens til å ta hensyn til detaljene når Excel prosessen synes eller forsvinner, men som vanligvis fungerer for meg. Jeg liker ikke å holde Excel prosesser rundt etter noe annet enn minimalt med tid, men jeg er nok bare å være paranoid på det.

Svarte 01/10/2008 kl. 15:47
kilden bruker

stemmer
5

Som andre har påpekt, må du opprette en eksplisitt referanse for hver Excel objekt du bruker, og kaller Marshal.ReleaseComObject på at referansen, som beskrevet i denne KB-artikkelen . Du må også bruke prøve / endelig å sikre ReleaseComObject er alltid kalt, selv når et unntak. Dvs. i stedet for:

Worksheet sheet = excelApp.Worksheets(1)
... do something with sheet

du trenger å gjøre noe sånt som:

Worksheets sheets = null;
Worksheet sheet = null
try
{ 
    sheets = excelApp.Worksheets;
    sheet = sheets(1);
    ...
}
finally
{
    if (sheets != null) Marshal.ReleaseComObject(sheets);
    if (sheet != null) Marshal.ReleaseComObject(sheet);
}

Du må også ringe Application.Quit før du slipper Application objekt hvis du vil Excel å lukke.

Som du kan se, blir dette fort ekstremt uhåndterlig så snart du prøver å gjøre noe selv moderat kompleks. Jeg har utviklet .NET applikasjoner med en enkel wrapper klasse som brytes noen enkle manipulasjoner av Excel objektmodellen (åpner en arbeidsbok, skrive til en Range, lagre / lukker arbeidsboken etc). Wrapper klassen implementerer IDisposable, nøye gjennom Marshal.ReleaseComObject på hvert objekt den bruker, og ikke pubicly avsløre noen Excel-dokument til resten av programmet.

Men denne tilnærmingen ikke skalere godt for mer komplekse krav.

Dette er en stor mangel på NET COM Interop. For mer komplekse scenarier, ville jeg seriøst vurdere å skrive en ActiveX DLL i VB6 eller annen uovervåkede språk som du kan delegere all interaksjon med out-proc COM objekter, for eksempel Office. Du kan deretter referere dette ActiveX DLL fra NET applikasjon, og ting vil bli mye lettere som du trenger bare å slippe dette en referanse.

Svarte 01/10/2008 kl. 16:26
kilden bruker

stemmer
263

Du kan faktisk slippe Excel-programmet objektet renslig, men du må ta vare.

Råd for å opprettholde en navngitt referanse for absolutt alle COM-objektet du få tilgang til og deretter eksplisitt slipper den via Marshal.FinalReleaseComObject()er riktig i teorien, men dessverre svært vanskelig å håndtere i praksis. Hvis man noen gang glir overalt og bruker "to prikker", eller gjentar celler via en for eachsløyfe, eller andre lignende type kommando, så har du unreferenced COM-objekter og risikere en henger. I dette tilfellet ville det ikke være noen måte å finne årsaken i koden; du må vurdere alle koden ved øyet og forhåpentligvis finne årsaken, en oppgave som kan være nesten umulig for et stort prosjekt.

Den gode nyheten er at du ikke egentlig har til å opprettholde en navngitt variabel referanse til hver COM-objektet du bruker. I stedet ringe GC.Collect()og deretter GC.WaitForPendingFinalizers()å frigjøre alle de (som regel mindre) gjenstander som du ikke holder en referanse, og deretter eksplisitt slipper objektene som du holder en navngitt variabel referanse.

Du bør også gi ut dine navngitte referanser i motsatt rekkefølge av betydning: range objekter først, deretter regneark, arbeidsbøker, og så til slutt Excel Application objekt.

For eksempel, forutsatt at du hadde en Range objekt variabel kalt xlRng, et regneark variabel ved navn xlSheet, en arbeidsbok variabel kalt xlBookog en Excel Application variabel kalt xlApp, så din opprydding kode kan se ut omtrent som følgende:

// Cleanup
GC.Collect();
GC.WaitForPendingFinalizers();

Marshal.FinalReleaseComObject(xlRng);
Marshal.FinalReleaseComObject(xlSheet);

xlBook.Close(Type.Missing, Type.Missing, Type.Missing);
Marshal.FinalReleaseComObject(xlBook);

xlApp.Quit();
Marshal.FinalReleaseComObject(xlApp);

I de fleste kodeeksempler vil du se for å rydde opp COM-objekter fra NET, de GC.Collect()og GC.WaitForPendingFinalizers()er samtaler to ganger som i:

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

Dette bør ikke være nødvendig, men med mindre du bruker Visual Studio Tools for Office (VSTO), som bruker finalizers som forårsaker en hel graf over objekter som skal fremmes i ferdigstillelsen køen. Slike gjenstander vil ikke bli frigitt før neste søppelrydding. Men hvis du ikke bruker VSTO, bør du være i stand til å ringe GC.Collect()og GC.WaitForPendingFinalizers()bare én gang.

Jeg vet at eksplisitt ringer GC.Collect()er en no-no (og sikkert gjør det to ganger høres veldig smertefullt), men det er ingen vei rundt det, for å være ærlig. Gjennom normal drift vil du generere skjulte objekter som du holder ingen referanse som du kan derfor ikke slippe gjennom andre enn å ringe midler GC.Collect().

Dette er et komplekst tema, men dette virkelig er alt som skal til. Når du opprette denne malen for opprydding prosedyren kan du koden som normalt, uten behov for wrappers, osv :-)

Jeg har en veiledning om dette her:

Automatisere Office-programmer med VB.Net / COM Interop

Det er skrevet for VB.NET, men ikke bli skremt av at prinsippene er nøyaktig de samme som ved bruk av C #.

Svarte 01/10/2008 kl. 17:54
kilden bruker

stemmer
6

Du må være klar over at Excel er svært følsomme for kulturen du kjører under også.

Du kan finne det du trenger for å sette kultur til EN-US før du ringer Excel-funksjoner. Dette gjelder ikke for alle funksjoner - men noen av dem.

    CultureInfo en_US = new System.Globalization.CultureInfo("en-US"); 
    System.Threading.Thread.CurrentThread.CurrentCulture = en_US;
    string filePathLocal = _applicationObject.ActiveWorkbook.Path;
    System.Threading.Thread.CurrentThread.CurrentCulture = orgCulture;

Dette gjelder selv om du bruker VSTO.

For detaljer: http://support.microsoft.com/default.aspx?scid=kb;en-us;Q320369

Svarte 06/11/2008 kl. 13:21
kilden bruker

stemmer
18

Jeg fant en nyttig generisk mal som kan bidra til å implementere korrekt avhending mønster for COM-objekter, som trenger Marshal.ReleaseComObject heter når de går ut av omfang:

bruk:

using (AutoReleaseComObject<Application> excelApplicationWrapper = new AutoReleaseComObject<Application>(new Application()))
{
    try
    {
        using (AutoReleaseComObject<Workbook> workbookWrapper = new AutoReleaseComObject<Workbook>(excelApplicationWrapper.ComObject.Workbooks.Open(namedRangeBase.FullName, false, false, missing, missing, missing, true, missing, missing, true, missing, missing, missing, missing, missing)))
        {
           // do something with your workbook....
        }
    }
    finally
    {
         excelApplicationWrapper.ComObject.Quit();
    } 
}

Mal:

public class AutoReleaseComObject<T> : IDisposable
{
    private T m_comObject;
    private bool m_armed = true;
    private bool m_disposed = false;

    public AutoReleaseComObject(T comObject)
    {
        Debug.Assert(comObject != null);
        m_comObject = comObject;
    }

#if DEBUG
    ~AutoReleaseComObject()
    {
        // We should have been disposed using Dispose().
        Debug.WriteLine("Finalize being called, should have been disposed");

        if (this.ComObject != null)
        {
            Debug.WriteLine(string.Format("ComObject was not null:{0}, name:{1}.", this.ComObject, this.ComObjectName));
        }

        //Debug.Assert(false);
    }
#endif

    public T ComObject
    {
        get
        {
            Debug.Assert(!m_disposed);
            return m_comObject;
        }
    }

    private string ComObjectName
    {
        get
        {
            if(this.ComObject is Microsoft.Office.Interop.Excel.Workbook)
            {
                return ((Microsoft.Office.Interop.Excel.Workbook)this.ComObject).Name;
            }

            return null;
        }
    }

    public void Disarm()
    {
        Debug.Assert(!m_disposed);
        m_armed = false;
    }

    #region IDisposable Members

    public void Dispose()
    {
        Dispose(true);
#if DEBUG
        GC.SuppressFinalize(this);
#endif
    }

    #endregion

    protected virtual void Dispose(bool disposing)
    {
        if (!m_disposed)
        {
            if (m_armed)
            {
                int refcnt = 0;
                do
                {
                    refcnt = System.Runtime.InteropServices.Marshal.ReleaseComObject(m_comObject);
                } while (refcnt > 0);

                m_comObject = default(T);
            }

            m_disposed = true;
        }
    }
}

Henvisning:

http://www.deez.info/sengelha/2005/02/11/useful-idisposable-class-3-autoreleasecomobject/

Svarte 08/12/2008 kl. 09:10
kilden bruker

stemmer
46

OPPDATERING : Lagt C # -kode, og lenke til Windows Jobs

Jeg brukte en gang å prøve å finne ut av dette problemet, og på den tiden XtremeVBTalk var den mest aktive og responsive. Her er en link til mitt opprinnelige innlegg, lukker en Excel Interop prosess rent, selv om programkrasj . Nedenfor er et sammendrag av innlegget, og koden kopieres til dette innlegget.

  • Lukke Interop prosessen med Application.Quit()og Process.Kill()jobber for det meste, men mislykkes hvis programmene krasjer katastrofalt. Dvs hvis app krasjer, Excel prosessen vil fortsatt kjøre løs.
  • Løsningen er å la OS håndtere opprydding av prosesser gjennom Windows Jobb objekter ved hjelp av Win32 samtaler. Når hoved søknad dør, vil de tilhørende prosesser (dvs. Excel) blir avsluttet også.

Jeg fant dette å være en ren løsning fordi OS gjør virkelige arbeidet med å rydde opp. Alt du trenger å gjøre er å registrere Excel prosessen.

Windows jobbkode

Wraps Win32 API-kall til å registrere Interop prosesser.

public enum JobObjectInfoType
{
    AssociateCompletionPortInformation = 7,
    BasicLimitInformation = 2,
    BasicUIRestrictions = 4,
    EndOfJobTimeInformation = 6,
    ExtendedLimitInformation = 9,
    SecurityLimitInformation = 5,
    GroupInformation = 11
}

[StructLayout(LayoutKind.Sequential)]
public struct SECURITY_ATTRIBUTES
{
    public int nLength;
    public IntPtr lpSecurityDescriptor;
    public int bInheritHandle;
}

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_BASIC_LIMIT_INFORMATION
{
    public Int64 PerProcessUserTimeLimit;
    public Int64 PerJobUserTimeLimit;
    public Int16 LimitFlags;
    public UInt32 MinimumWorkingSetSize;
    public UInt32 MaximumWorkingSetSize;
    public Int16 ActiveProcessLimit;
    public Int64 Affinity;
    public Int16 PriorityClass;
    public Int16 SchedulingClass;
}

[StructLayout(LayoutKind.Sequential)]
struct IO_COUNTERS
{
    public UInt64 ReadOperationCount;
    public UInt64 WriteOperationCount;
    public UInt64 OtherOperationCount;
    public UInt64 ReadTransferCount;
    public UInt64 WriteTransferCount;
    public UInt64 OtherTransferCount;
}

[StructLayout(LayoutKind.Sequential)]
struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION
{
    public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
    public IO_COUNTERS IoInfo;
    public UInt32 ProcessMemoryLimit;
    public UInt32 JobMemoryLimit;
    public UInt32 PeakProcessMemoryUsed;
    public UInt32 PeakJobMemoryUsed;
}

public class Job : IDisposable
{
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
    static extern IntPtr CreateJobObject(object a, string lpName);

    [DllImport("kernel32.dll")]
    static extern bool SetInformationJobObject(IntPtr hJob, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength);

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process);

    private IntPtr m_handle;
    private bool m_disposed = false;

    public Job()
    {
        m_handle = CreateJobObject(null, null);

        JOBOBJECT_BASIC_LIMIT_INFORMATION info = new JOBOBJECT_BASIC_LIMIT_INFORMATION();
        info.LimitFlags = 0x2000;

        JOBOBJECT_EXTENDED_LIMIT_INFORMATION extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
        extendedInfo.BasicLimitInformation = info;

        int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
        IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length);
        Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false);

        if (!SetInformationJobObject(m_handle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length))
            throw new Exception(string.Format("Unable to set information.  Error: {0}", Marshal.GetLastWin32Error()));
    }

    #region IDisposable Members

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    #endregion

    private void Dispose(bool disposing)
    {
        if (m_disposed)
            return;

        if (disposing) {}

        Close();
        m_disposed = true;
    }

    public void Close()
    {
        Win32.CloseHandle(m_handle);
        m_handle = IntPtr.Zero;
    }

    public bool AddProcess(IntPtr handle)
    {
        return AssignProcessToJobObject(m_handle, handle);
    }

}

Merknad om Constructor kode

  • I konstruktøren, den info.LimitFlags = 0x2000;kalles. 0x2000er den JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSEenum verdi, og denne verdien er definert ved MSDN som:

Årsaker alle prosesser knyttet til jobben å si når den siste håndtaket til jobben er lukket.

Ekstra API Call Win32 å få Process ID (PID)

    [DllImport("user32.dll", SetLastError = true)]
    public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

Bruke koden

    Excel.Application app = new Excel.ApplicationClass();
    Job job = new Job();
    uint pid = 0;
    Win32.GetWindowThreadProcessId(new IntPtr(app.Hwnd), out pid);
    job.AddProcess(Process.GetProcessById((int)pid).Handle);
Svarte 20/08/2009 kl. 14:00
kilden bruker

stemmer
12

Vanlige utviklere, ingen av løsningene fungerte for meg, så jeg velger å implementere et nytt triks .

Først la spesifisere "Hva er vårt mål?" => "Ikke se excel objekt etter vår jobb i Oppgavebehandling"

Ok. La ingen å utfordre og begynne å ødelegge det, men anser ikke å ødelegge andre forekomsten os Excel som kjører parallelt.

Så, få en liste over aktuelle prosessorer og hente PID av Excel prosesser, så når jobben er gjort, har vi en ny gjest i prosesser liste med en unik PID, finne og ødelegge nettopp det en.

<Huske noen ny excel prosess under excel jobb vil bli oppdaget som ny og ødela> <En bedre løsning er å fange PID ny opprettet excel objekt og bare ødelegge det>

Process[] prs = Process.GetProcesses();
List<int> excelPID = new List<int>();
foreach (Process p in prs)
   if (p.ProcessName == "EXCEL")
       excelPID.Add(p.Id);

.... // your job 

prs = Process.GetProcesses();
foreach (Process p in prs)
   if (p.ProcessName == "EXCEL" && !excelPID.Contains(p.Id))
       p.Kill();

Dette løser problemet mitt, håper din også.

Svarte 16/10/2009 kl. 09:01
kilden bruker

stemmer
199

Forord: mitt svar inneholder to løsninger, så vær forsiktig når du leser, og ikke gå glipp av noe.

Det er forskjellige måter og råd om hvordan å lage Excel eksempel losse, for eksempel:

  • Frigjøring HVER com objekt eksplisitt med Marshal.FinalReleaseComObject () (ikke glemme implisitt opprettet com-objekter). Å frigjøre alle skapt com objekt, kan du bruke regelen om 2 prikker nevnt her:
    Hvordan kan jeg rydde skikkelig opp Excel interoperabilitets gjenstander?

  • Ringe GC.Collect () og GC.WaitForPendingFinalizers () for å gjøre CLR utslipp ubrukte com-objektene (Egentlig det fungerer, se min andre løsning for detaljer)

  • Sjekke om com-tjener-applikasjon kanskje viser en meldingsboks venter for brukeren å svare på (selv om jeg ikke er sikker på at det kan hindre Excel lukkes, men jeg har hørt om det et par ganger)

  • Sende WM_CLOSE melding til hoved Excel-vinduet

  • Utfører funksjonen som arbeider med Excel i en egen AppDomain. Noen mennesker tror Excel eksempel vil bli stengt når AppDomain losses.

  • Killing alle Excel tilfeller som ble startes etter vår excel-interoping kode startet.

MEN! Noen ganger er alle disse alternativene bare ikke hjelpe eller ikke kan være riktig!

For eksempel, i går fant jeg ut at i en av mine funksjoner (som fungerer med excel) Excel holder kjører etter at funksjonen slutter. Jeg prøvde alt! Jeg grundig sjekket hele funksjonen 10 ganger og lagt Marshal.FinalReleaseComObject () for alt! Jeg hadde også GC.Collect () og GC.WaitForPendingFinalizers (). Jeg sjekket for skjulte meldingsbokser. Jeg prøvde å sende WM_CLOSE melding til hoved Excel-vinduet. Jeg henrettet min funksjon i en egen AppDomain og losset det domenet. Ingenting hjalp! Alternativet med å lukke alle utmerke tilfeller er upassende, fordi hvis brukeren starter en annen Excel eksempel manuelt, under utførelsen av min funksjon som fungerer også med Excel, så dette tilfellet vil også bli stengt av min funksjon. Jeg vedder på at brukeren ikke vil bli fornøyd! Så ærlig, dette er en halt alternativ (ingen krenkelser gutta). Så jeg brukte et par timer før jeg fant en god (i min ydmyke mening) løsning : Kill excel prosessen ved hWnd av sin hovedvinduet (det er en første løsning).

Her er en enkel kode:

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

/// <summary> Tries to find and kill process by hWnd to the main window of the process.</summary>
/// <param name="hWnd">Handle to the main window of the process.</param>
/// <returns>True if process was found and killed. False if process was not found by hWnd or if it could not be killed.</returns>
public static bool TryKillProcessByMainWindowHwnd(int hWnd)
{
    uint processID;
    GetWindowThreadProcessId((IntPtr)hWnd, out processID);
    if(processID == 0) return false;
    try
    {
        Process.GetProcessById((int)processID).Kill();
    }
    catch (ArgumentException)
    {
        return false;
    }
    catch (Win32Exception)
    {
        return false;
    }
    catch (NotSupportedException)
    {
        return false;
    }
    catch (InvalidOperationException)
    {
        return false;
    }
    return true;
}

/// <summary> Finds and kills process by hWnd to the main window of the process.</summary>
/// <param name="hWnd">Handle to the main window of the process.</param>
/// <exception cref="ArgumentException">
/// Thrown when process is not found by the hWnd parameter (the process is not running). 
/// The identifier of the process might be expired.
/// </exception>
/// <exception cref="Win32Exception">See Process.Kill() exceptions documentation.</exception>
/// <exception cref="NotSupportedException">See Process.Kill() exceptions documentation.</exception>
/// <exception cref="InvalidOperationException">See Process.Kill() exceptions documentation.</exception>
public static void KillProcessByMainWindowHwnd(int hWnd)
{
    uint processID;
    GetWindowThreadProcessId((IntPtr)hWnd, out processID);
    if (processID == 0)
        throw new ArgumentException("Process has not been found by the given main window handle.", "hWnd");
    Process.GetProcessById((int)processID).Kill();
}

Som du kan se jeg gitt to metoder, ifølge Try-Parse mønster (jeg tror det er riktig her): en metode kaster ikke noe unntak hvis prosessen ikke kunne bli drept (for eksempel prosessen ikke eksisterer lenger), og en annen metode kaster unntak hvis prosessen ikke ble drept. Bare svak sted i denne koden er sikkerhetstillatelser. Teoretisk sett kan brukeren ikke har tillatelse til å drepe prosessen, men i 99,99% av alle tilfeller brukeren har slike tillatelser. Jeg har også testet den med en gjestekonto - det fungerer perfekt.

Så, din kode, som arbeider med Excel, kan se slik ut:

int hWnd = xl.Application.Hwnd;
// ...
// here we try to close Excel as usual, with xl.Quit(),
// Marshal.FinalReleaseComObject(xl) and so on
// ...
TryKillProcessByMainWindowHwnd(hWnd);

Voila! Excel avsluttes! :)

Ok, la oss gå tilbake til den andre løsningen, som jeg lovet i begynnelsen av innlegget. Den andre løsningen er å ringe GC.Collect () og GC.WaitForPendingFinalizers (). Ja, de faktisk fungerer, men du må være forsiktig her!
Mange sier (og jeg sa) at ringer GC.Collect () ikke hjelper. Men grunnen til at det ikke ville hjelpe er om det fortsatt er referanser til COM-objekter! En av de mest populære grunner for GC.Collect () ikke å være nyttig kjører prosjektet i Debug-modus. I debug-modus gjenstander som egentlig ikke er referert lenger vil ikke bli søppel samles til slutten av metoden.
Så, hvis du prøvde GC.Collect () og GC.WaitForPendingFinalizers (), og det hjalp ikke, prøv å gjøre følgende:

1) Prøv å kjøre prosjektet i Utløsermodus og sjekk om Excel lukket riktig

2) Pakk den metode som arbeider med Excel i en separat fremgangsmåte. Så i stedet for noe som dette:

void GenerateWorkbook(...)
{
  ApplicationClass xl;
  Workbook xlWB;
  try
  {
    xl = ...
    xlWB = xl.Workbooks.Add(...);
    ...
  }
  finally
  {
    ...
    Marshal.ReleaseComObject(xlWB)
    ...
    GC.Collect();
    GC.WaitForPendingFinalizers();
  }
}

du skriver:

void GenerateWorkbook(...)
{
  try
  {
    GenerateWorkbookInternal(...);
  }
  finally
  {
    GC.Collect();
    GC.WaitForPendingFinalizers();
  }
}

private void GenerateWorkbookInternal(...)
{
  ApplicationClass xl;
  Workbook xlWB;
  try
  {
    xl = ...
    xlWB = xl.Workbooks.Add(...);
    ...
  }
  finally
  {
    ...
    Marshal.ReleaseComObject(xlWB)
    ...
  }
}

Nå vil Excel stenge =)

Svarte 12/12/2009 kl. 12:50
kilden bruker

stemmer
9

Den aksepterte svaret her er riktig, men også ta oppmerksom på at ikke bare "to prikk" referanser må unngås, men også gjenstander som hentes via indeksen. Du trenger heller ikke å vente til du er ferdig med programmet for å rydde opp i disse stedene, er det best å opprette funksjoner som vil rense dem opp så snart du er ferdig med dem, når det er mulig. Her er en funksjon jeg laget som tildeler noen av egenskapene til en stil objekt kalt xlStyleHeader:

public Excel.Style xlStyleHeader = null;

private void CreateHeaderStyle()
{
    Excel.Styles xlStyles = null;
    Excel.Font xlFont = null;
    Excel.Interior xlInterior = null;
    Excel.Borders xlBorders = null;
    Excel.Border xlBorderBottom = null;

    try
    {
        xlStyles = xlWorkbook.Styles;
        xlStyleHeader = xlStyles.Add("Header", Type.Missing);

        // Text Format
        xlStyleHeader.NumberFormat = "@";

        // Bold
        xlFont = xlStyleHeader.Font;
        xlFont.Bold = true;

        // Light Gray Cell Color
        xlInterior = xlStyleHeader.Interior;
        xlInterior.Color = 12632256;

        // Medium Bottom border
        xlBorders = xlStyleHeader.Borders;
        xlBorderBottom = xlBorders[Excel.XlBordersIndex.xlEdgeBottom];
        xlBorderBottom.Weight = Excel.XlBorderWeight.xlMedium;
    }
    catch (Exception ex)
    {
        throw ex;
    }
    finally
    {
        Release(xlBorderBottom);
        Release(xlBorders);
        Release(xlInterior);
        Release(xlFont);
        Release(xlStyles);
    }
}

private void Release(object obj)
{
    // Errors are ignored per Microsoft's suggestion for this type of function:
    // http://support.microsoft.com/default.aspx/kb/317109
    try
    {
        System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
    }
    catch { } 
}

Legg merke til at jeg måtte sette xlBorders[Excel.XlBordersIndex.xlEdgeBottom]en variabel for å rydde det opp (Ikke på grunn av de to prikkene, som henviser til en oppregning som ikke trenger å bli løslatt, men fordi objektet jeg henviser til er faktisk en Border objekt som trenger å bli frigitt).

Denne typen ting er egentlig ikke nødvendig i standard applikasjoner, som gjør en god jobb med å rydde opp etter seg, men i ASP.NET-applikasjoner, hvis du går glipp av enda en av disse, uansett hvor ofte du kaller søppel samleren, vil Excel fortsatt kjører på serveren din.

Det krever mye oppmerksomhet på detaljer og mange test mens overvåking Oppgavebehandling når du skriver inn denne koden, men dette sparer deg bryet med desperat søker gjennom sider med kode for å finne ett tilfelle du gikk glipp av henrettelser. Dette er spesielt viktig når du arbeider i sløyfer, der du trenger å frigjøre hver forekomst av et objekt, selv om den bruker samme variabel navn hver gang den går.

Svarte 17/12/2009 kl. 18:15
kilden bruker

stemmer
15

Jeg kan ikke tro dette problemet har hjemsøkt verden i 5 år .... Hvis du har opprettet et program, må du slå den av før du fjerner koblingen.

objExcel = new Excel.Application();  
objBook = (Excel.Workbook)(objExcel.Workbooks.Add(Type.Missing)); 

når du lukker

objBook.Close(true, Type.Missing, Type.Missing); 
objExcel.Application.Quit();
objExcel.Quit(); 

Når du nye en excel program, åpnes det et excel program i bakgrunnen. Du må kommandere som excel program å slutte før du slipper lenken fordi det excel-programmet er ikke en del av din direkte kontroll. Derfor vil det være åpen om koblingen er lansert!

God programmering alle ~~

Svarte 21/11/2010 kl. 01:41
kilden bruker

stemmer
9

For å legge til årsakene til at Excel ikke lukke, selv når du oppretter direkte refrences på hvert objekt på lese-, skapelse, er 'for' loop.

For Each objWorkBook As WorkBook in objWorkBooks 'local ref, created from ExcelApp.WorkBooks to avoid the double-dot
   objWorkBook.Close 'or whatever
   FinalReleaseComObject(objWorkBook)
   objWorkBook = Nothing
Next 

'The above does not work, and this is the workaround:

For intCounter As Integer = 1 To mobjExcel_WorkBooks.Count
   Dim objTempWorkBook As Workbook = mobjExcel_WorkBooks.Item(intCounter)
   objTempWorkBook.Saved = True
   objTempWorkBook.Close(False, Type.Missing, Type.Missing)
   FinalReleaseComObject(objTempWorkBook)
   objTempWorkBook = Nothing
Next
Svarte 06/12/2010 kl. 11:08
kilden bruker

stemmer
4

Når alle ting ovenfor ikke virker, prøv å gi Excel litt tid til å lukke sine ark:

app.workbooks.Close();
Thread.Sleep(500); // adjust, for me it works at around 300+
app.Quit();

...
FinalReleaseComObject(app);
Svarte 18/06/2011 kl. 10:02
kilden bruker

stemmer
-1

Excel er ikke laget for å være programmert via C ++ eller C #. COM API er spesielt utviklet for bruk med Visual Basic, VB.NET, og VBA.

Også alle kodeeksempler på denne siden ikke er optimal for den enkle grunn at hver samtale må krysse en administrert / uovervåkede grensen og videre ignorere det faktum at Excel COM API er fri til å mislykkes en samtale med en kryptisk HRESULT indikerer RPC-serveren er travelt.

Den beste måten å automatisere Excel etter min mening er å samle data i så stor en matrise som mulig / gjennomførbart og sende dette over til en VBA funksjon eller sub (via Application.Run) som deretter utfører eventuelle nødvendige behandling. Dessuten - når du ringer Application.Run- sørg for å se etter unntakene som indikerer excel er opptatt og prøv å ringe Application.Run.

Svarte 18/06/2011 kl. 22:16
kilden bruker

stemmer
9

Dette virker som at det har vært over-komplisert. Fra min erfaring, er det bare tre viktige ting for å få Excel å lukke riktig:

1: Pass på at det ikke er noen gjenværende referanser til excel programmet du opprettet (du bør ha kun én likevel; sette den til null)

2: samtale GC.Collect()

3: Excel må være lukket, enten ved at brukeren manuelt lukke programmet, eller ved at du ringer Quitpå Excel objekt. (Merk at Quitvil fungere akkurat som om brukeren prøvde å lukke programmet, og vil presentere en dialogboks bekreftelse hvis det er ulagrede endringer, selv om Excel er ikke synlig. Brukeren kan trykke avbryte, og deretter Excel vil ikke ha blitt stengt. )

1 må skje før to, men tre kan skje når som helst.

En måte å gjennomføre dette på er å vikle Interop Excel objekt med din egen klasse, skape Interop eksempel i konstruktøren, og implementere IDisposable med Kast ser noe sånt

if (!mDisposed) {
   mExcel = null;
   GC.Collect();
   mDisposed = true;
}

Det vil rydde opp excel fra programmets side av ting. Når Excel er lukket (manuelt av brukeren, eller ved at du ringer Quit) prosessen vil gå bort. Hvis programmet allerede er stengt, så prosessen vil forsvinne på GC.Collect()samtalen.

(Jeg er ikke sikker på hvor viktig det er, men det kan være lurt en GC.WaitForPendingFinalizers()samtale etter GC.Collect()samtale, men det er strengt tatt ikke nødvendig å kvitte seg med Excel prosessen.)

Dette har fungert for meg uten problem i årevis. Husk skjønt at mens dette fungerer, du faktisk nødt til å stenge grasiøst for den å arbeide. Du vil fortsatt få akkumulere EXCEL.EXE prosesser hvis du avbryte programmet før Excel er ryddet opp (vanligvis ved å trykke "stopp" når programmet blir feilsøkt).

Svarte 31/08/2011 kl. 19:18
kilden bruker

stemmer
2

Du bør være svært forsiktig med å bruke Word / Excel interoperabilitets applikasjoner. Etter å ha prøvd alle løsningene vi fortsatt hadde mye "winword" prosess igjen åpen på server (med mer enn 2000 brukere).

Etter å ha jobbet med problemet i flere timer, jeg innså at hvis jeg åpner mer enn et par dokumenter ved hjelp Word.ApplicationClass.Document.Open()av forskjellige tråder samtidig, IIS arbeidsprosessen (W3Wp.Exe) ville krasje forlate alle winword prosesser åpen!

Så jeg antar det er ingen absolutt løsning på dette problemet, men å bytte til andre metoder som Office Open XML utvikling.

Svarte 14/03/2012 kl. 12:47
kilden bruker

stemmer
6

"Aldri bruke to prikker med COM-objekter" er et flott tommelfingerregel for å unngå lekkasje av COM referanser, men Excel PIA kan føre til lekkasje på flere måter enn åpenbar ved første blikk.

En av disse måtene er å abonnere på enhver hendelse avslørt av noen av Excel objekt modellens COM-objekter.

For eksempel å abonnere på Application klassens WorkbookOpen arrangement.

Noen teori om COM hendelser

COM klasser eksponere en gruppe av hendelser gjennom ringe tilbake grensesnitt. For å abonnere på aktiviteter, kan klientkoden bare registrere et objekt implementere anrops-tilbake-grensesnittet og den COM klassen kaller på dens metoder som respons på spesifikke hendelser. Siden tilbakeringing grensesnitt er et COM-grensesnitt, er det en plikt for implementering av gjenstanden for å dekrementere referansetelle av enhver COM-objekt den mottar (som en parameter) for et hvilket som helst av de hendelseshåndterere.

Hvordan Excel PIA utsette COM Hendelser

Excel PIA eksponerer COM hendelsene i Excel Application klasse som konvensjonelle NET hendelser. Når klientkoden abonnerer på en NET hendelse (vekt på 'a'), danner PIA en forekomst av en klasse implementere anropstilbake grensesnitt og registrerer den med Excel.

Derfor blir en rekke call-back gjenstander registrert med Excel som svar på ulike abonnements forespørsler fra NET-kode. En samtale-back objekt per hendelse abonnement.

En samtale-tilbake-grensesnitt for hendelsen håndtering betyr at PIA har å abonnere på alle grensesnitt arrangementer for enhver .NET abonnement arrangement forespørsel. Det kan ikke velge og vrake. Ved mottak av en hendelse ringe tilbake, de anrops tilbake objekt sjekker om den tilknyttede .NET hendelseshåndterer er interessert i den aktuelle hendelsen eller ikke, og deretter enten påkaller behandleren eller stille ignorerer ringe tilbake.

Effekt på COM eksempelvis referanse tellinger

Alle disse call-back objekter ikke minske referansen greven av noen av COM-objekter de mottar (som parametere) for noen av de ringe tilbake metoder (selv for de som er stille ignorert). De stoler utelukkende på CLR søppelinnsamler for å frigjøre de COM-objekter.

Siden GC kjøringen er ikke-deterministisk, kan dette føre til at holde av av Excel-prosessen for en lengre varighet enn ønskelig, og skaper et inntrykk av en "hukommelse lekkasjen.

Løsning

Den eneste løsningen som nå er å unngå PIA arrangement leverandør for COM klassen og skriv ditt eget arrangement leverandør som deterministisk utgivelser COM-objekter.

For program klasse, kan dette gjøres ved å implementere AppEvents grensesnittet og deretter registrerer gjennomføringen med Excel ved hjelp IConnectionPointContainer grensesnitt . Søknaden klasse (og for den saks skyld alle COM-objekter som eksponerer hendelser ved hjelp av tilbakeringings mekanisme) implementerer IConnectionPointContainer grensesnitt.

Svarte 25/06/2012 kl. 04:52
kilden bruker

stemmer
3

Pass på at du slipper alle gjenstander knyttet til Excel!

Jeg brukte et par timer ved å prøve flere måter. Alle er gode ideer, men jeg endelig funnet min feil: Hvis du ikke slipper alle objekter, kan ingen av alternativene ovenfor hjelpe deg som i mitt tilfelle. Pass på at du slipper alle objekter inkludert rekkevidde ett!

Excel.Range rng = (Excel.Range)worksheet.Cells[1, 1];
worksheet.Paste(rng, false);
releaseObject(rng);

Alternativene er sammen her .

Svarte 08/08/2012 kl. 16:38
kilden bruker

stemmer
2

En stor artikkel på å slippe COM-objekter er 2,5 Releasing COM-objekter (MSDN).

Metoden som jeg vil argumentere er å nulle dine Excel.Interop referanser hvis de er ikke-lokale variabler, og deretter ringe GC.Collect()og GC.WaitForPendingFinalizers()to ganger. Lokalt scoped Interop variabler vil bli tatt vare på automatisk.

Dette fjerner behovet for å holde en navngitt referanse for hvert COM-objektet.

Her er et eksempel hentet fra artikkelen:

public class Test {

    // These instance variables must be nulled or Excel will not quit
    private Excel.Application xl;
    private Excel.Workbook book;

    public void DoSomething()
    {
        xl = new Excel.Application();
        xl.Visible = true;
        book = xl.Workbooks.Add(Type.Missing);

        // These variables are locally scoped, so we need not worry about them.
        // Notice I don't care about using two dots.
        Excel.Range rng = book.Worksheets[1].UsedRange;
    }

    public void CleanUp()
    {
        book = null;
        xl.Quit();
        xl = null;

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

Disse ordene er rett fra artikkelen:

I nesten alle situasjoner, nulling av RCW referanse og tvinge en søppelrydding vil rydde opp ordentlig. Hvis du også ringe GC.WaitForPendingFinalizers, vil søppelrydding være så deterministisk som du kan gjøre det. Det er, vil du være ganske sikker på nøyaktig når objektet har blitt ryddet opp-på returen fra andre kall til WaitForPendingFinalizers. Som et alternativ, kan du bruke Marshal.ReleaseComObject. Vær imidlertid oppmerksom på at du er svært lite sannsynlig å noen gang trenger å bruke denne metoden.

Svarte 10/01/2013 kl. 22:46
kilden bruker

stemmer
8

Jeg har tradisjonelt fulgt rådet funnet i VVS svar . Men i et forsøk på å holde dette svaret up-to-date med de nyeste alternativer, tror jeg alle mine fremtidige prosjekter vil bruke "NetOffice" bibliotek.

NetOffice er en komplett erstatning for Office PIAer og er helt versjons agnostiker. Det er en samling av Managed COM wrappers som kan håndtere oppryddingen som ofte fører til slike hodepine når du arbeider med Microsoft Office i .NET.

Noen viktige funksjoner er:

  • For det meste versjons uavhengig (og versjonsavhengige egenskaper er dokumentert)
  • ingen avhengig
  • Ingen PIA
  • ingen registrering
  • Ingen VSTO

Jeg er på ingen måte tilknyttet prosjektet; Jeg virkelig setter pris på sterk reduksjon i hodepine.

Svarte 28/03/2013 kl. 11:12
kilden bruker

stemmer
1

Som noen har sikkert allerede skrevet, er det ikke bare viktig hvordan du lukker Excel (objekt); det er også viktig hvordan du åpner den, og også av type prosjekt.

I en WPF applikasjon, i utgangspunktet den samme koden fungerer uten eller med svært få problemer.

Jeg har et prosjekt der samme Excel-filen blir behandlet flere ganger for ulike parameterverdien - f.eks parsing det basert på verdier inne i en generisk liste.

Jeg legger alle Excel-relaterte funksjoner inn i basen klasse, og parser til en underklasse (forskjellige parsere bruke vanlige Excel-funksjoner). Jeg ønsket ikke at Excel åpnes og lukkes igjen for hvert element i en generisk liste, så jeg har åpnet den bare én gang i base klassen og lukke den i underklassen. Jeg hadde problemer når du flytter inn koden i en desktop applikasjon. Jeg har prøvd mange av de ovennevnte løsninger. GC.Collect()var allerede iverksatt før, to ganger som foreslått.

Da har jeg bestemt meg for at jeg vil flytte koden for å åpne Excel til en underklasse. I stedet for å åpne bare én gang, nå lager jeg et nytt objekt (base klasse) og åpne Excel for hvert element og lukke den på slutten. Det er noen ytelse straff, men basert på flere tester Excel prosesser avslutt uten problemer (i debug-modus), så også midlertidige filer blir fjernet. Jeg vil fortsette med testing og skrive litt mer om jeg vil få noen oppdateringer.

Poenget er: Du må også sjekke Initial kode, spesielt hvis du har mange klasser, etc.

Svarte 11/04/2013 kl. 12:01
kilden bruker

stemmer
7

¨ ° º¤ø “¸ skyte Excel proc og tygge tyggegummi ¸“ø¤º ° ¨

public class MyExcelInteropClass
{
    Excel.Application xlApp;
    Excel.Workbook xlBook;

    public void dothingswithExcel() 
    {
        try { /* Do stuff manipulating cells sheets and workbooks ... */ }
        catch {}
        finally {KillExcelProcess(xlApp);}
    }

    static void KillExcelProcess(Excel.Application xlApp)
    {
        if (xlApp != null)
        {
            int excelProcessId = 0;
            GetWindowThreadProcessId(xlApp.Hwnd, out excelProcessId);
            Process p = Process.GetProcessById(excelProcessId);
            p.Kill();
            xlApp = null;
        }
    }

    [DllImport("user32.dll")]
    static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId);
}
Svarte 14/06/2013 kl. 13:16
kilden bruker

stemmer
1

Bruk:

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

Erklære den, legge til kode i finallyblokken:

finally
{
    GC.Collect();
    GC.WaitForPendingFinalizers();
    if (excelApp != null)
    {
        excelApp.Quit();
        int hWnd = excelApp.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(excelApp);
    }
}
Svarte 27/08/2013 kl. 08:11
kilden bruker

stemmer
9

etter å ha prøvd

  1. Slipp COM-objekter i omvendt rekkefølge
  2. Legg GC.Collect()og GC.WaitForPendingFinalizers()to ganger på slutten
  3. Ikke mer enn to prikker
  4. Lukk arbeidsbok og avslutter programmet
  5. Kjør i utløserfunksjon

den endelige løsningen som fungerer for meg er å flytte ett sett av

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

at vi lagt til slutten av funksjonen til en wrapper, som følger:

private void FunctionWrapper(string sourcePath, string targetPath)
{
    try
    {
        FunctionThatCallsExcel(sourcePath, targetPath);
    }
    finally
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}
Svarte 18/11/2013 kl. 19:58
kilden bruker

stemmer
2

De to prikker regelen fungerte ikke for meg. I mitt tilfelle jeg opprettet en metode for å rense mine ressurser som følger:

private static void Clean()
{
    workBook.Close();
    Marshall.ReleaseComObject(workBook);
    excel.Quit();
    CG.Collect();
    CG.WaitForPendingFinalizers();
}
Svarte 12/12/2013 kl. 19:37
kilden bruker

stemmer
6

Jeg fulgte dette akkurat ... Men jeg fortsatt kjørte inn spørsmål 1 av 1000 ganger. Hvem vet hvorfor. Tid for å hente ut hammer ...

Rett etter Excel Application klassen er instansiert får jeg tak i Excel prosess som nettopp ble opprettet.

excel = new Microsoft.Office.Interop.Excel.Application();
var process = Process.GetProcessesByName("EXCEL").OrderByDescending(p => p.StartTime).First();

Så når jeg har gjort alle de ovennevnte COM rydde opp, må jeg sørge for at prosessen ikke kjører. Hvis det er fortsatt kjører, drepe den!

if (!process.HasExited)
   process.Kill();
Svarte 25/02/2014 kl. 00:35
kilden bruker

stemmer
1

min løsning

[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 Excel Process Id (ath the end, you kill him
    GetWindowThreadProcessId(excel.Hwnd, out id);
    Process excelProcess = Process.GetProcessById(id);

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

    // Kill him !
    excelProcess.Kill();
}
Svarte 18/06/2014 kl. 07:34
kilden bruker

stemmer
1

Den aksepterte svaret fungerte ikke for meg. Følgende kode i destructor gjorde jobben.

if (xlApp != null)
{
    xlApp.Workbooks.Close();
    xlApp.Quit();
}

System.Diagnostics.Process[] processArray = System.Diagnostics.Process.GetProcessesByName("EXCEL");
foreach (System.Diagnostics.Process process in processArray)
{
    if (process.MainWindowTitle.Length == 0) { process.Kill(); }
}
Svarte 05/09/2014 kl. 09:42
kilden bruker

stemmer
0

Så langt ser det ut til alle svarene innebære noen av disse:

  1. Drepe prosessen
  2. Bruk GC.Collect ()
  3. Hold styr på alle COM objekt og slipp den riktig.

Som gjør meg setter pris på hvor vanskelig dette problemet er :)

Jeg har jobbet på et bibliotek for å forenkle tilgangen til Excel, og jeg prøver å sørge for at folk som bruker det ikke vil forlate et rot (krysser fingrene).

I stedet for å skrive direkte på grensesnittene Interop gir, å jeg gjør skjøtemetoder gjør levende enklere. Som ApplicationHelpers.CreateExcel () eller workbook.CreateWorksheet ( "mySheetNameThatWillBeValidated"). Naturligvis kan noe som er opprettet føre til et problem senere rydde opp, så jeg er faktisk favoriserer drepe prosessen som siste utvei. Likevel, rydde opp ordentlig (tredje alternativet), er trolig den minst destruktive og mest kontrollerte.

Så, i den sammenheng jeg lurte på om det ikke ville være best å gjøre noe sånt som dette:

public abstract class ReleaseContainer<T>
{
    private readonly Action<T> actionOnT;

    protected ReleaseContainer(T releasible, Action<T> actionOnT)
    {
        this.actionOnT = actionOnT;
        this.Releasible = releasible;
    }

    ~ReleaseContainer()
    {
        Release();
    }

    public T Releasible { get; private set; }

    private void Release()
    {
        actionOnT(Releasible);
        Releasible = default(T);
    }
}

Jeg brukte 'avtagbar' for å unngå forveksling med engangs. Utvide dette til IDisposable skal være enkelt skjønt.

En implementering som dette:

public class ApplicationContainer : ReleaseContainer<Application>
{
    public ApplicationContainer()
        : base(new Application(), ActionOnExcel)
    {
    }

    private static void ActionOnExcel(Application application)
    {
        application.Show(); // extension method. want to make sure the app is visible.
        application.Quit();
        Marshal.FinalReleaseComObject(application);
    }
}

Og man kunne gjøre noe lignende for alle slags COM-objekter.

I fabrikken metode:

    public static Application CreateExcelApplication(bool hidden = false)
    {
        var excel = new ApplicationContainer().Releasible;
        excel.Visible = !hidden;

        return excel;
    }

Jeg forventer at hver beholder vil bli destruert skikkelig av GC, og derfor automatisk ringe til Quitog Marshal.FinalReleaseComObject.

Kommentarer? Eller er dette et svar på spørsmålet om den tredje slag?

Svarte 04/05/2015 kl. 12:12
kilden bruker

stemmer
0

Bare for å legge til en annen løsning på mange oppført her, ved hjelp av C ++ / ATL automatisering (Jeg antar du kan bruke noe lignende fra VB / C # ??)

Excel::_ApplicationPtr pXL = ...
  :
SendMessage ( ( HWND ) m_pXL->GetHwnd ( ), WM_DESTROY, 0, 0 ) ;

Dette fungerer som en sjarm for meg ...

Svarte 07/04/2016 kl. 11:29
kilden bruker

stemmer
13

Først - du aldri trenger å ringe Marshal.ReleaseComObject(...)eller Marshal.FinalReleaseComObject(...)når du gjør Excel interoperabilitet. Det er en forvirrende anti-mønster, men noe informasjon om dette, blant annet fra Microsoft, som indikerer at du må manuelt frigjøre COM referanser fra NET er feil. Faktum er at .NET runtime og søppelsamler riktig å holde styr på og rydde opp COM referanser. For koden din, betyr dette at du kan fjerne hele `while (...) sløyfe på toppen.

For det andre, hvis du ønsker å sikre at de COM referanser til en ut-av-prosess COM-objektet er ryddet opp når prosessen ender (slik at Excel prosessen vil lukke), må du sørge for at søppelinnsamler går. Du gjør dette riktig med samtaler til GC.Collect()og GC.WaitForPendingFinalizers(). Kalle dette to ganger er trygt, og sørger for at sykluser er definitivt ryddet opp også (selv om jeg ikke er sikker på at det er nødvendig, og ville sette pris på et eksempel som viser dette).

Tredje, når du kjører under debugger, lokale referanser vil bli kunstig holdt i live frem til slutten av metoden (slik at lokal variabel inspeksjon fungerer). Så GC.Collect()samtaler er ikke effektiv for rengjøring objekt som rng.Cellsfra samme metode. Du bør dele koden gjør COM Interop fra GC opprydding i egne metoder. (Dette var en viktig oppdagelse for meg, fra en del av svaret lagt ut her etter @nightcoder.)

Det generelle mønsteret vil derfor være:

Sub WrapperThatCleansUp()

    ' NOTE: Don't call Excel objects in here... 
    '       Debugger would keep alive until end, preventing GC cleanup

    ' Call a separate function that talks to Excel
    DoTheWork()

    ' Now let the GC clean up (twice, to clean up cycles too)
    GC.Collect()    
    GC.WaitForPendingFinalizers()
    GC.Collect()    
    GC.WaitForPendingFinalizers()

End Sub

Sub DoTheWork()
    Dim app As New Microsoft.Office.Interop.Excel.Application
    Dim book As Microsoft.Office.Interop.Excel.Workbook = app.Workbooks.Add()
    Dim worksheet As Microsoft.Office.Interop.Excel.Worksheet = book.Worksheets("Sheet1")
    app.Visible = True
    For i As Integer = 1 To 10
        worksheet.Cells.Range("A" & i).Value = "Hello"
    Next
    book.Save()
    book.Close()
    app.Quit()

    ' NOTE: No calls the Marshal.ReleaseComObject() are ever needed
End Sub

Det er mye falsk informasjon og forvirring om dette problemet, inkludert mange innlegg på MSDN og på Stack Overflow (og spesielt dette spørsmålet!).

Hva endelig overbeviste meg til å ta en nærmere titt og finne ut den rette råd var blogginnlegg Marshal.ReleaseComObject betraktet som farlig sammen med å finne problemet med referanser holdt i live under debugger som var forvirrende min tidligere testing.

Svarte 29/06/2016 kl. 21:50
kilden bruker

stemmer
1

Jeg jobber for tiden på kontoret automatisering og har snublet over en løsning for dette som fungerer hver gang for meg. Det er enkelt og ikke innebærer å drepe noen prosesser.

Det synes at ved bare å sløyfe gjennom de aktuelle aktive prosesser, og i en hvilken som helst måte 'å få tilgang til' en åpen Excel-prosessen, vil noen streif hengende forekomst av Excel fjernes. Koden nedenfor sjekker bare for prosesser hvor navnet er 'Excel', deretter skriver MainWindowTitle egenskap av prosessen til en streng. Denne 'samspill' med prosessen ser ut til å gjøre Windows fange opp og avbryte den frosne forekomst av Excel.

Jeg kjører under metode like før add-in som jeg utvikler avsluttes, så det branner det lossing hendelsen. Det fjerner eventuelle hengende forekomster av Excel hver gang. I all ærlighet er jeg ikke helt sikker på hvorfor dette fungerer, men det fungerer bra for meg, og kan plasseres på slutten av alle Excel-programmet uten å måtte bekymre seg for doble prikker, Marshal.ReleaseComObject, eller drepe prosesser. Jeg ville være veldig interessert i noen forslag til hvorfor dette er effektivt.

public static void SweepExcelProcesses()
{           
            if (Process.GetProcessesByName("EXCEL").Length != 0)
            {
                Process[] processes = Process.GetProcesses();
                foreach (Process process in processes)
                {
                    if (process.ProcessName.ToString() == "excel")
                    {                           
                        string title = process.MainWindowTitle;
                    }
                }
            }
}
Svarte 02/08/2016 kl. 13:32
kilden bruker

stemmer
-1

Dette er den eneste måten som virkelig fungerer for meg

        foreach (Process proc in System.Diagnostics.Process.GetProcessesByName("EXCEL"))
        {
            proc.Kill();
        }
Svarte 14/02/2017 kl. 05:55
kilden bruker

stemmer
1

'Dette virker som at det har vært over-komplisert. Fra min erfaring, er det bare tre viktige ting for å få Excel å lukke riktig:

1: Pass på at det ikke er noen gjenværende referanser til excel programmet du opprettet (du bør ha kun én likevel; sette den til null)

2: ring GC.Collect ()

3: Excel må være lukket, enten ved at brukeren manuelt lukke programmet, eller ved at du ringer Avslutt på Excel objekt. (Merk at Avslutt vil fungere akkurat som om brukeren prøvde å lukke programmet, og vil presentere en dialogboks bekreftelse hvis det er ulagrede endringer, selv om Excel er ikke synlig. Brukeren kan trykke avbryte, og deretter Excel vil ikke ha vært stengt .)

1 må skje før to, men tre kan skje når som helst.

En måte å gjennomføre dette på er å vikle Interop Excel objekt med din egen klasse, skape Interop eksempel i konstruktøren, og implementere IDisposable med Kast ser noe sånt

Det vil rydde opp excel fra programmets side av ting. Når Excel er lukket (manuelt av brukeren, eller ved at du ringer Avslutt) prosessen vil gå bort. Hvis programmet allerede er stengt, så prosessen vil forsvinne på GC.Collect () samtale.

(Jeg er ikke sikker på hvor viktig det er, men det kan være lurt en GC.WaitForPendingFinalizers () kaller etter GC.Collect () samtalen, men det er strengt tatt ikke nødvendig å kvitte seg med Excel prosessen.)

Dette har fungert for meg uten problem i årevis. Husk skjønt at mens dette fungerer, du faktisk nødt til å stenge grasiøst for den å arbeide. Du vil fortsatt få akkumulere EXCEL.EXE prosesser hvis du avbryte programmet før Excel er ryddet opp (vanligvis ved å trykke "stopp" når programmet blir feilsøkt).

Svarte 10/03/2017 kl. 02:18
kilden bruker

stemmer
0

Det jeg har en idé, prøve å drepe excel prosessen du har åpnet:

  1. før åpen en excelapplication, får alle prosess ids heter oldProcessIds.
  2. åpne excelapplication.
  3. får nå alle excelapplication prosess ids heter nowProcessIds.
  4. da trenger å slutte, drepe unntatt IDer mellom oldProcessIds og nowProcessIds.

    private static Excel.Application GetExcelApp()
         {
            if (_excelApp == null)
            {
                var processIds = System.Diagnostics.Process.GetProcessesByName("EXCEL").Select(a => a.Id).ToList();
                _excelApp = new Excel.Application();
                _excelApp.DisplayAlerts = false;
    
                _excelApp.Visible = false;
                _excelApp.ScreenUpdating = false;
                var newProcessIds = System.Diagnostics.Process.GetProcessesByName("EXCEL").Select(a => a.Id).ToList();
                _excelApplicationProcessId = newProcessIds.Except(processIds).FirstOrDefault();
            }
    
            return _excelApp;
        }
    
    public static void Dispose()
        {
            try
            {
                _excelApp.Workbooks.Close();
                _excelApp.Quit();
                System.Runtime.InteropServices.Marshal.ReleaseComObject(_excelApp);
                _excelApp = null;
                GC.Collect();
                GC.WaitForPendingFinalizers();
                if (_excelApplicationProcessId != default(int))
                {
                    var process = System.Diagnostics.Process.GetProcessById(_excelApplicationProcessId);
                    process?.Kill();
                    _excelApplicationProcessId = default(int);
                }
            }
            catch (Exception ex)
            {
                _excelApp = null;
            }
    
        }
    
Svarte 19/12/2018 kl. 01:06
kilden bruker

stemmer
0

Testet med Microsoft Excel 2016

En virkelig testet løsningen.

Til C # Reference se: https://stackoverflow.com/a/1307180/10442623

Å VB.net Reference se: https://stackoverflow.com/a/54044646/10442623

1 omfatter klassen jobb

2 implementere klassen til å håndtere apropiate kast excel proces

Svarte 04/01/2019 kl. 17:26
kilden bruker

stemmer
0

Her er en veldig enkel måte å gjøre det:

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

int objExcelProcessId = 0;

Excel.Application objExcel = new Excel.Application();

GetWindowThreadProcessId(new IntPtr(objExcel.Hwnd), out objExcelProcessId);

Process.GetProcessById(objExcelProcessId).Kill();
Svarte 02/04/2019 kl. 19:24
kilden bruker

stemmer
0

Jeg hadde samme problem å få PowerPoint å lukke etter newing opp programmet objektet i min VSTO AddIn. Jeg prøvde alle svarene her med begrenset suksess.

Dette er løsningen jeg fant for min sak - DONT bruke 'ny søknad', har AddInBase base klasse av ThisAddIn allerede et håndtak til 'Application'. Hvis du bruker det håndtaket der du trenger det (gjøre den statisk hvis du må), så du trenger ikke å bekymre deg for å rense den opp og PowerPoint vil ikke henge på nært hold.

Svarte 02/07/2019 kl. 20:25
kilden bruker

stemmer
0

Mitt svar er sent, og dens eneste formål er å støtte forslag til løsning av Govert, Porkbutts, og Dave Cousineau med en komplett eksempel. Automizing Excel eller andre Office-programmet er en tre-trinns prosess:

  1. Få en og bare en global forekomst ExcelAppav Application CoClass. Dette åpner en ny prosess i bakgrunnen.

  2. Implementere funksjoner som gjør oppgavene ved hjelp av ExcelAppå generere via Collection egenskaper nye objekter som arbeidsbok (s), regneark (s), og cellen (e). I disse funksjonene ikke bryr seg for voodoo en-dot-good, to-dot-dårlig regel, ikke prøv å få en referanse for hver implisitt opprettet objekt og gjør ikke Marshall.ReleaseComObjectnoe. Det er jobben av Garbage Collection.

  3. Når du er ferdig med Excel, ring ExcelApp.Quit();, satt ExcelApp = null;, og ringe GC.Collect(); GC.WaitForPendingFinalizers(); to ganger.

Før du kjører min app, åpne oppgavebehandling og se på bakgrunnen fanen prosesser. Når du starter programmet, vises en ny Excel prosess oppføring i listen og blir der til du taste inn ønsket antall. Etter at Avslutt funksjonen vil bli kalt stoppe Excel prosess, som elegant forsvinner fra listen over bakgrunnsprosesser.

using System;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;

namespace GCTestOnOffice
{
  class Program
  {
    private  static Excel.Application ExcelApp = new Excel.Application();

    private static void DoTheJob()
    {
        Excel.Workbook Wb = ExcelApp.Workbooks.Open(@"D:\Aktuell\SampleWorkbook.xlsx");
        Excel.Worksheet NewWs = Wb.Worksheets.Add();

        for (int i = 1; i < 10; i++)
        {
            NewWs.Cells[i, 1] = i;
        }
        Wb.Save();
    }

    public static void Quit(Excel.Application TheApp)
    {
        if (TheApp != null)
        {
            TheApp.Quit(); //Don't forget!!!!!
            TheApp = null;
        }
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        GC.WaitForPendingFinalizers();
    }

    static void Main(string[] args)
    {
        DoTheJob();
        Console.WriteLine("Input a digit: ");
        int k = Console.Read();
        Quit(ExcelApp);
    }
  }
}
Svarte 29/09/2019 kl. 08:46
kilden bruker

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more