c # ed excel automation: termina l’istanza in esecuzione

Sto tentando l’automazione di Excel attraverso C #. Ho seguito tutte le istruzioni di Microsoft su come procedere, ma sto ancora cercando di scartare il / i riferimento / i definitivo / i in Excel per poterlo chiudere e consentire al GC di raccoglierlo.

Segue un esempio di codice. Quando commento il blocco di codice che contiene righe simili a:

Sheet.Cells[iRowCount, 1] = data["fullname"].ToString(); 

quindi il file salva ed Excel si chiude. Altrimenti il ​​file si salva ma Excel viene lasciato in esecuzione come processo. La prossima volta che questo codice viene eseguito crea una nuova istanza e alla fine si accumulano.

Qualsiasi aiuto è apprezzato. Grazie.

Questo è il barebone del mio codice:

  Excel.Application xl = null; Excel._Workbook wBook = null; Excel._Worksheet wSheet = null; Excel.Range range = null; object m_objOpt = System.Reflection.Missing.Value; try { // open the template xl = new Excel.Application(); wBook = (Excel._Workbook)xl.Workbooks.Open(excelTemplatePath + _report.ExcelTemplate, false, false, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt); wSheet = (Excel._Worksheet)wBook.ActiveSheet; int iRowCount = 2; // enumerate and drop the values straight into the Excel file while (data.Read()) { wSheet.Cells[iRowCount, 1] = data["fullname"].ToString(); wSheet.Cells[iRowCount, 2] = data["brand"].ToString(); wSheet.Cells[iRowCount, 3] = data["agency"].ToString(); wSheet.Cells[iRowCount, 4] = data["advertiser"].ToString(); wSheet.Cells[iRowCount, 5] = data["product"].ToString(); wSheet.Cells[iRowCount, 6] = data["comment"].ToString(); wSheet.Cells[iRowCount, 7] = data["brief"].ToString(); wSheet.Cells[iRowCount, 8] = data["responseDate"].ToString(); wSheet.Cells[iRowCount, 9] = data["share"].ToString(); wSheet.Cells[iRowCount, 10] = data["status"].ToString(); wSheet.Cells[iRowCount, 11] = data["startDate"].ToString(); wSheet.Cells[iRowCount, 12] = data["value"].ToString(); iRowCount++; } DirectoryInfo saveTo = Directory.CreateDirectory(excelTemplatePath + _report.FolderGuid.ToString() + "\\"); _report.ReportLocation = saveTo.FullName + _report.ExcelTemplate; wBook.Close(true, _report.ReportLocation, m_objOpt); wBook = null; } catch (Exception ex) { LogException.HandleException(ex); } finally { NAR(wSheet); if (wBook != null) wBook.Close(false, m_objOpt, m_objOpt); NAR(wBook); xl.Quit(); NAR(xl); GC.Collect(); } private void NAR(object o) { try { System.Runtime.InteropServices.Marshal.ReleaseComObject(o); } catch { } finally { o = null; } } 

Aggiornare

Non importa cosa provo, il metodo ‘clean’ o il metodo ‘brutto’ (vedi le risposte di seguito), l’istanza di Excel si blocca ancora non appena questa linea viene colpita:

 wSheet.Cells[iRowCount, 1] = data["fullname"].ToString(); 

Se commento quella linea (e le altre simili sotto, ovviamente) l’app Excel esce con grazia. Non appena una riga per sopra non viene commentata, Excel rimane in attesa.

Penso che dovrò controllare se c’è un’istanza in esecuzione prima di assegnare la variabile xl e collegarla invece. Ho dimenticato di dire che questo è un servizio di Windows, ma non dovrebbe essere importante, dovrebbe?


AGGIORNAMENTO (novembre 2016)

Ho appena letto un argomento convincente di Hans Passant che usare GC.Collect è in realtà la strada giusta da percorrere. Non lavoro più con Office (grazie al cielo), ma se lo facessi probabilmente avrei intenzione di fare un altro tentativo – sarebbe sicuramente una semplificazione di molte (migliaia di righe) di codice che ho scritto cercando di fare le cose nel modo giusto “modo (come ho visto allora).

Lascerò la mia risposta originale per i posteri …


Come dice Mike nella sua risposta, c’è un modo semplice e un modo difficile per affrontare questo. Mike suggerisce di usare la via semplice perché … è più facile. Personalmente non credo che sia una buona ragione, e non credo che sia la strada giusta. Mi sa di “spegnerlo e riaccenderlo”.

Ho diversi anni di esperienza nello sviluppo di un’applicazione di automazione Office in .NET, e questi problemi di interoperabilità COM mi hanno tormentato per le prime settimane e mesi in cui mi sono imbattuto per la prima volta nel problema, non ultimo perché Microsoft è molto timida nell’ammettere che c’è un problema in il primo posto, e al momento era difficile trovare buoni consigli sul web.

Ho un modo di lavorare che ora uso virtualmente senza pensarci, ed è anni che non ho avuto problemi. È ancora importante essere vivi per tutti gli oggetti nascosti che potresti creare – e sì, se ne manchi uno, potresti avere una perdita che diventa evidente solo molto dopo. Ma non è peggio di quello che erano i vecchi tempi di malloc / free .

Penso che ci sia qualcosa da dire per ripulire te stesso mentre vai, piuttosto che alla fine. Se stai solo avviando Excel per riempire alcune celle, allora forse non importa – ma se stai facendo un po ‘di lavoro pesante, allora è diverso.

Ad ogni modo, la tecnica che uso è quella di utilizzare una class wrapper che implementa IDisposable e che nel suo metodo Dispose chiama ReleaseComObject . In questo modo posso using istruzioni per garantire che l’object sia eliminato (e l’object COM rilasciato) non appena ho finito con esso.

Fondamentalmente, verrà eliminato / rilasciato anche se la funzione ritorna presto, o c’è un’eccezione, ecc. Inoltre, verrà eliminata / rilasciata solo se è stata effettivamente creata in primo luogo – chiamami pedante ma il suggerito il codice che tenta di rilasciare oggetti che potrebbero non essere stati effettivamente creati mi sembra come un codice sciatto. Ho un’obiezione simile all’utilizzo di FinalReleaseComObject: dovresti sapere quante volte hai causato la creazione di un riferimento COM e dovresti quindi riuscire a rilasciarlo lo stesso numero di volte.

Uno snippet tipico del mio codice potrebbe apparire come questo (o lo sarebbe, se usassi C # v2 e potrei usare i generics :-)):

 using (ComWrapper application = new ComWrapper(new Excel.Application())) { try { using (ComWrapper workbooks = new ComWrapper(application.ComObject.Workbooks)) { using (ComWrapper workbook = new ComWrapper(workbooks.ComObject.Open(...))) { using (ComWrapper worksheet = new ComWrapper(workbook.ComObject.ActiveSheet)) { FillTheWorksheet(worksheet); } // Close the workbook here (see edit 2 below) } } } finally { application.ComObject.Quit(); } } 

Ora, non sto per fingere che non sia verboso, e il rientro causato dalla creazione di oggetti può sfuggire di mano se non dividi le cose in metodi più piccoli. Questo esempio è qualcosa di pessimo, poiché tutto ciò che stiamo facendo è creare oggetti. Di solito c’è molto di più tra le parentesi e il sovraccarico è molto meno.

Si noti che, come nell’esempio sopra, passerei sempre gli oggetti “avvolti” tra i metodi, mai un object COM nudo, e sarebbe la responsabilità del chiamante di disfarsene (di solito con un’istruzione using ). Allo stesso modo, restituirei sempre un object avvolto, mai uno nudo, e ancora una volta sarebbe responsabilità del chiamante liberarlo. Potresti usare un protocollo diverso, ma è importante avere regole chiare, proprio come quando dovevamo fare la nostra gestione della memoria.

La ComWrapper qui usata spera che richieda poche spiegazioni. Memorizza semplicemente un riferimento all’object COM spostato e lo rilascia esplicitamente (utilizzando ReleaseComObject ) nel suo metodo Dispose . Il metodo ComObject restituisce semplicemente un riferimento digitato all’object COM spostato.

Spero che questo ti aiuti!

EDIT : Ho solo ora seguito il collegamento alla risposta di Mike ad un’altra domanda , e vedo che un’altra risposta a questa domanda ha un link a una class wrapper, proprio come suggerisco sopra.

Inoltre, per quanto riguarda la risposta di Mike a quell’altra domanda, devo dire che sono stato quasi sedotto GC.Collect “usa solo GC.Collect “. Tuttavia, sono stato attratto principalmente da ciò su una premessa sbagliata; a prima vista sembrava che non ci fosse bisogno di preoccuparsi dei riferimenti COM. Tuttavia, come dice Mike, è ancora necessario rilasciare in modo esplicito gli oggetti COM associati a tutte le variabili nel campo di applicazione – e quindi tutto ciò che hai fatto è ridurre piuttosto che rimuovere la necessità di gestione degli oggetti COM. Personalmente, preferirei andare tutto il maiale.

Rilevo anche una tendenza in molte risposte a scrivere codice in cui tutto viene rilasciato alla fine di un metodo, in un grosso blocco di chiamate ReleaseComObject . Va tutto bene se tutto funziona come previsto, ma inviterò chiunque a scrivere codice serio a considerare cosa succederebbe se venisse lanciata un’eccezione, o se il metodo avesse diversi punti di uscita (il codice non sarebbe stato eseguito, e quindi gli oggetti COM non sarebbe stato rilasciato). Questo è il motivo per cui preferisco l’uso dei “wrapper” e l’ using s. È prolisso, ma rende il codice antiproiettile.

EDIT2 : ho aggiornato il codice sopra per indicare dove la cartella di lavoro deve essere chiusa con o senza salvare le modifiche. Ecco il codice per salvare le modifiche:

 object saveChanges = Excel.XlSaveAction.xlSaveChanges; workbook.ComObject.Close(saveChanges, Type.Missing, Type.Missing); 

… e per non salvare le modifiche, cambia semplicemente xlSaveChanges su xlDoNotSaveChanges .

Quello che sta succedendo è la tua chiamata a:

 Sheet.Cells[iRowCount, 1] = data["fullname"].ToString(); 

È essenzialmente la stessa di:

 Excel.Range cell = Sheet.Cells[iRowCount, 1]; cell.Value = data["fullname"].ToString(); 

In questo modo, puoi vedere che stai creando un object Excel.Range e quindi Excel.Range ad esso un valore. In questo modo forniamo anche un riferimento alla nostra variabile di intervallo, la variabile di cell , che ci consente di rilasciarlo direttamente se lo volessimo. Quindi puoi pulire i tuoi oggetti in due modi:

(1) Il modo difficile e brutto:

 while (data.Read()) { Excel.Range cell = Sheet.Cells[iRowCount, 1]; cell.Value = data["fullname"].ToString(); Marshal.FinalReleaseComObject(cell); cell = Sheet.Cells[iRowCount, 2]; cell.Value = data["brand"].ToString(); Marshal.FinalReleaseComObject(cell); cell = Sheet.Cells[iRowCount, 3]; cell.Value = data["agency"].ToString(); Marshal.FinalReleaseComObject(cell); // etc... } 

In quanto sopra, stiamo rilasciando ogni object range tramite una chiamata a Marshal.FinalReleaseComObject(cell) mentre procediamo.

(2) Il modo semplice e pulito:

Lascia il tuo codice esattamente come lo hai attualmente, quindi alla fine puoi pulire come segue:

 GC.Collect(); GC.WaitForPendingFinalizers(); if (wSheet != null) { Marshal.FinalReleaseComObject(wSheet) } if (wBook != null) { wBook.Close(false, m_objOpt, m_objOpt); Marshal.FinalReleaseComObject(wBook); } xl.Quit(); Marshal.FinalReleaseComObject(xl); 

In breve, il codice esistente è estremamente vicino . Se aggiungi solo chiamate a GC.Collect () e GC.WaitForPendingFinalizers () prima delle chiamate ‘NAR’, penso che dovrebbe funzionare per te. (In breve, sia il codice di Jamie che il codice di Ahmad sono corretti. Jamie’s è più pulito, ma il codice di Ahmad è una “soluzione rapida” più semplice perché dovresti solo aggiungere le chiamate alle chiamate a GC.Collect () e GC.WaitForPendingFinalizers () al codice esistente.)

Jamie e Amhad hanno anche elencato i link al .NET Automation Forum a cui partecipo (grazie ragazzi!) Ecco un paio di post correlati che ho realizzato qui su StackOverflow :

(1) Come pulire correttamente gli oggetti di interoperabilità di Excel in C #

(2) C # Automatizza PowerPoint Excel – PowerPoint non si chiude

Spero che questo aiuti, Sean …

Mike

Aggiungi quanto segue prima della tua chiamata a xl.Quit ():

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

È inoltre ansible utilizzare Marshal.FinalReleaseComObject() nel metodo NAR anziché ReleaseComObject. ReleaseComObject decrementa il conteggio dei riferimenti di 1 mentre FinalReleaseComObject rilascia tutti i riferimenti in modo che il conteggio sia 0.

Quindi il tuo blocco finale sarà simile a:

 finally { GC.Collect(); GC.WaitForPendingFinalizers(); NAR(wSheet); if (wBook != null) wBook.Close(false, m_objOpt, m_objOpt); NAR(wBook); xl.Quit(); NAR(xl); } 

Metodo NAR aggiornato:

 private void NAR(object o) { try { System.Runtime.InteropServices.Marshal.FinalReleaseComObject(o); } catch { } finally { o = null; } } 

L’ho cercato un po ‘di tempo fa e negli esempi che ho trovato di solito le chiamate relative al GC erano alla fine dopo aver chiuso l’app. Tuttavia, c’è un MVP (Mike Rosenblum) che menziona che dovrebbe essere chiamato all’inizio. Ho provato entrambi i modi e hanno funzionato. Ho anche provato senza WaitForPendingFinalizers e ha funzionato anche se non dovrebbe ferire nulla. YMMV.

Ecco i link rilevanti dell’MVP che ho menzionato (sono in VB ma non è così diverso):

Dato che altri hanno già coperto InterOp, suggerirei che se gestisci file Excel con estensione XLSX dovresti usare EPPlus che farà sparire gli incubi di Excel.

Ho appena risposto a questa domanda qui:

Killing excel process dalla sua finestra principale hWnd

I suoi oltre 4 anni da quando questo è stato pubblicato ma mi sono imbattuto nello stesso problema ed è stato in grado di risolverlo. Apparentemente solo l’accesso all’array Cells crea un object COM. Quindi se dovessi fare:

  wSheet = (Excel._Worksheet)wBook.ActiveSheet; Microsoft.Office.Interop.Excel.Range cells = wSheet.Cells; int iRowCount = 2; // enumerate and drop the values straight into the Excel file while (data.Read()) { Microsoft.Office.Interop.Excel.Range cell = cells[iRowCount, 1]; cell = data["fullname"].ToString(); Marshal.FinalReleaseComObject(cell); } Marshal.FinalReleaseComObject(cells); 

e poi il resto della pulizia dovrebbe risolvere il problema.

Quello che ho finito per risolvere un problema simile è stato ottenere l’ID del processo e ucciderlo come ultima risorsa …

 [DllImport("user32.dll", SetLastError = true)] static extern IntPtr GetWindowThreadProcessId(int hWnd, out IntPtr lpdwProcessId); ... objApp = new Excel.Application(); IntPtr processID; GetWindowThreadProcessId(objApp.Hwnd, out processID); excel = Process.GetProcessById(processID.ToInt32()); ... objApp.Application.Quit(); Marshal.FinalReleaseComObject(objApp); _excel.Kill(); 

Ecco il contenuto del mio hasn’t-failed-yet finally block per ripulire l’automazione di Excel. La mia applicazione lascia Excel aperto, quindi non c’è nessuna chiamata di uscita. La referenza nel commento è stata la mia fonte.

 finally { // Cleanup -- See http://www.xtremevbtalk.com/showthread.php?t=160433 GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); GC.WaitForPendingFinalizers(); // Calls are needed to avoid memory leak Marshal.FinalReleaseComObject(sheet); Marshal.FinalReleaseComObject(book); Marshal.FinalReleaseComObject(excel); } 

Hai considerato l’utilizzo di una soluzione .NET pura come SpreadsheetGear per .NET ? Ecco come potrebbe essere il tuo codice con SpreadsheetGear:

 // open the template using (IWorkbookSet workbookSet = SpreadsheetGear.Factory.GetWorkbookSet()) { IWorkbook wBook = workbookSet.Workbooks.Open(excelTemplatePath + _report.ExcelTemplate); IWorksheet wSheet = wBook.ActiveWorksheet; int iRowCount = 2; // enumerate and drop the values straight into the Excel file while (data.Read()) { wSheet.Cells[iRowCount, 1].Value = data["fullname"].ToString(); wSheet.Cells[iRowCount, 2].Value = data["brand"].ToString(); wSheet.Cells[iRowCount, 3].Value = data["agency"].ToString(); wSheet.Cells[iRowCount, 4].Value = data["advertiser"].ToString(); wSheet.Cells[iRowCount, 5].Value = data["product"].ToString(); wSheet.Cells[iRowCount, 6].Value = data["comment"].ToString(); wSheet.Cells[iRowCount, 7].Value = data["brief"].ToString(); wSheet.Cells[iRowCount, 8].Value = data["responseDate"].ToString(); wSheet.Cells[iRowCount, 9].Value = data["share"].ToString(); wSheet.Cells[iRowCount, 10].Value = data["status"].ToString(); wSheet.Cells[iRowCount, 11].Value = data["startDate"].ToString(); wSheet.Cells[iRowCount, 12].Value = data["value"].ToString(); iRowCount++; } DirectoryInfo saveTo = Directory.CreateDirectory(excelTemplatePath + _report.FolderGuid.ToString() + "\\"); _report.ReportLocation = saveTo.FullName + _report.ExcelTemplate; wBook.SaveAs(_report.ReportLocation, FileFormat.OpenXMLWorkbook); } 

Se hai più di poche righe, potresti essere scioccato da quanto è più veloce. E non dovrai mai preoccuparti di un’istanza sospesa di Excel.

Puoi scaricare la versione di prova gratuita qui e provarla tu stesso.

Disclaimer: Possiedo SpreadsheetGear LLC

Il modo più semplice per incollare il codice è attraverso una domanda – questo non significa che io abbia risposto alla mia stessa domanda (sfortunatamente). Mi scuso con coloro che cercano di aiutarmi – non sono stato in grado di tornare a questo fino ad ora. Mi ha ancora bloccato … Ho completamente isolato il codice di Excel in una funzione come da

 private bool GenerateDailyProposalsReport(ScheduledReport report) { // start of test Excel.Application xl = null; Excel._Workbook wBook = null; Excel._Worksheet wSheet = null; Excel.Range xlrange = null; object m_objOpt = System.Reflection.Missing.Value; xl = new Excel.Application(); wBook = (Excel._Workbook)xl.Workbooks.Open(@"E:\Development\Romain\APN\SalesLinkReportManager\ExcelTemplates\DailyProposalReport.xls", false, false, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt); wSheet = (Excel._Worksheet)wBook.ActiveSheet; xlrange = wSheet.Cells[2, 1] as Excel.Range; // PROBLEM LINE ************ xlrange.Value2 = "fullname"; //************************** wBook.Close(true, @"c:\temp\DailyProposalReport.xls", m_objOpt); xl.Quit(); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); GC.WaitForPendingFinalizers(); Marshal.FinalReleaseComObject(xlrange); Marshal.FinalReleaseComObject(wSheet); Marshal.FinalReleaseComObject(wBook); Marshal.FinalReleaseComObject(xl); xlrange = null; wSheet = null; wBook = null; xl = null; // end of test return true; } 

Se commento la PROBLEM LINE sopra, l’istanza di Excel viene rilasciata dalla memoria. Così com’è, non è così. Gradirei ulteriore aiuto su questo aspetto poiché il tempo è fugace e una scadenza incombente (non tutti).

Si prega di chiedere se avete bisogno di ulteriori informazioni. Grazie in anticipo.

appendice

Un po ‘più di informazioni che possono o meno far luce su questo. Ho fatto ricorso all’abbattimento del processo (misura stopgap) dopo un certo intervallo di tempo (5-10 secondi per dare a Excel il tempo di terminare i suoi processi). Ho pianificato due rapporti: il primo report viene creato e salvato su disco e il processo Excel viene ucciso, quindi inviato via email. Il secondo viene creato, salvato su disco, il processo viene ucciso ma improvvisamente si verifica un errore durante il tentativo di email. L’errore è: il processo non può accedere al file ‘….’ ecc

Quindi, anche quando l’app Excel è stata uccisa, il vero file Excel è ancora trattenuto dal servizio Windows. Devo uccidere il servizio per eliminare il file …

Temo di essere a corto di idee qui, Sean. 🙁

Gary potrebbe avere qualche idea, ma sebbene il suo approccio al wrapper sia molto solido, in realtà non ti aiuterà in questo caso perché stai già facendo tutto in modo corretto.

Elencherò alcuni pensieri qui. Non vedo come qualcuno di loro funzionerà davvero perché la tua linea misteriosa

 xlrange.Value2 = "fullname"; 

non sembra essere influenzato da nessuna di queste idee, ma qui va:

(1) Non utilizzare le interfacce _Workbook e _Worksheet. Utilizzare invece la cartella di lavoro e il foglio di lavoro. (Per ulteriori informazioni vedere: Interop di Excel: _Worksheet o Foglio di lavoro?. )

(2) Ogni volta che si hanno due punti (“.”) Sulla stessa riga quando si accede a un object Excel, suddividerlo in due righe, assegnando ciascun object a una variabile denominata. Quindi, all’interno della sezione di pulizia del codice, rilasciare esplicitamente ciascuna variabile usando Marshal.FinalReleaseComObject ().

Ad esempio, il tuo codice qui:

  wBook = (Excel._Workbook)xl.Workbooks.Open(@"E:\Development\Romain\APN\SalesLinkReportManager\ExcelTemplates\DailyProposalReport.xls", false, false, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt, m_objOpt); 

potrebbe essere suddiviso in:

 Excel.Workbooks wBooks = xl.Workbooks; wBook = wBooks.Open("@"E:\Development\...\DailyProposalReport.xls", etc...); 

E poi più tardi, all’interno della sezione di pulizia, avresti:

 Marshal.FinalReleaseComObject(xlrange); Marshal.FinalReleaseComObject(wSheet); Marshal.FinalReleaseComObject(wBook); Marshal.FinalReleaseComObject(wBooks); // <-- Added Marshal.FinalReleaseComObject(xl); 

(3) Non sono sicuro di cosa sta succedendo con il tuo approccio Process.Kill. Se chiami wBook.Close () e quindi xl.Quit () prima di chiamare Process.Kill (), non dovresti avere problemi. Workbook.Close () non restituisce l'esecuzione finché la cartella di lavoro non viene chiusa e Excel.Quit () non restituisce l'esecuzione fino a quando Excel non ha terminato la chiusura (anche se potrebbe essere ancora in sospeso).

Dopo aver chiamato Process.Kill (), è ansible controllare la proprietà Process.HasExited in un ciclo o, meglio, chiamare il metodo Process.WaitForExit () che si interromperà fino a quando non sarà terminato. Direi che in genere ci vorrà ben poco meno di un secondo. È meglio aspettare meno tempo ed essere certi che aspettare 5-10 secondi e fare solo ipotesi.

(4) Dovresti provare queste idee di pulizia che ho elencato sopra, ma sto iniziando a sospettare che potresti avere un problema con altri processi che potrebbero funzionare con Excel, come un programma aggiuntivo o antivirus. Questi componenti aggiuntivi possono causare il blocco di Excel se non vengono eseguiti correttamente. In questo caso, può essere molto difficile o imansible ottenere Excel da rilasciare. Dovresti trovare il programma incriminato e poi disabilitarlo. Un'altra possibilità è che operare come servizio Windows in qualche modo è un problema. Non vedo perché sarebbe, ma non ho esperienza automatizzando Excel tramite un servizio di Windows, quindi non posso dire. Se i tuoi problemi sono legati a questo, quindi utilizzando Process.Kill sarà probabilmente il tuo unico resort qui.

Questo è tutto ciò che riesco a pensare, Sean. Spero che aiuti. Fateci sapere come va...

-- Mike

Sean,

Inserirò nuovamente il codice con le mie modifiche (di seguito). Ho evitato di modificare troppo il codice, quindi non ho aggiunto alcuna gestione delle eccezioni, ecc. Questo codice non è robusto.

 private bool GenerateDailyProposalsReport(ScheduledReport report) { Excel.Application xl = null; Excel.Workbooks wBooks = null; Excel.Workbook wBook = null; Excel.Worksheet wSheet = null; Excel.Range xlrange = null; Excel.Range xlcell = null; xl = new Excel.Application(); wBooks = xl.Workbooks; wBook = wBooks.Open(@"DailyProposalReport.xls", false, false, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing); wSheet = wBook.ActiveSheet; xlrange = wSheet.Cells; xlcell = xlrange[2, 1] as Excel.Range; xlcell.Value2 = "fullname"; Marshal.ReleaseComObject(xlcell); Marshal.ReleaseComObject(xlrange); Marshal.ReleaseComObject(wSheet); wBook.Close(true, @"c:\temp\DailyProposalReport.xls", Type.Missing); Marshal.ReleaseComObject(wBook); Marshal.ReleaseComObject(wBooks); xl.Quit(); Marshal.ReleaseComObject(xl); return true; } 

Points to note:

  1. The Workbooks method of the Application class creates a Workbooks object which holds a reference to the corresponding COM object, so we need to ensure we subsequently release that reference, which is why I added the variable wBooks and the corresponding call to ReleaseComObject .

  2. Similarly, the Cells method of the Worksheet object returns a Range object with another COM reference, so we need to clean that up too. Hence the need for 2 separate Range variables.

  3. I’ve released the COM references (using ReleaseComObject ) as soon as they’re no longer needed, which I think is good practice even if it isn’t strictly necessary. Also, (and this may be superstition) I’ve released all the objects owned by the workbook before closing the workbook, and released the workbook before closing Excel.

  4. I’m not calling GC.Collect etc. because it shouldn’t be necessary. Veramente!

  5. I’m using ReleaseComObject rather than FinalReleaseComObject, because it should be perfectly sufficient.

  6. I’m not null-ing the variables after use; once again, it’s not doing anything worthwhile.

  7. Not relevant here, but I’m using Type.Missing instead of System.Reflection.Missing.Value for convenience. Roll on C#v4 where optional parameters will be supported by the compiler!

I’ve not been able to compile or run this code, but I’m pretty confident it’ll work. In bocca al lupo!

There’s no need to use the excel com objects from C#. You can use OleDb to modify the sheets.

http://www.codeproject.com/KB/office/excel_using_oledb.aspx

Stavo avendo un problema simile. I removed _worksheet and _workbook and all was well.