This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
How to properly clean up Excel interop objects in C#
I am using EXCEL INTEROP for reading excel files in my .NET application. However I see that after am done with it, I still see the EXCEL.EXE in Windows Task Manager.
The code is as follows:
ApplicationClass excel = new ApplicationClass();
Workbooks workBooks = excel.Workbooks;
Workbook workBook = workBooks.Open(fileName,0,true,5,"","",true,XLPlatform.xlWindows,"\t",false,false,0,true,1,0);
foreach (Name name in workBook.Names)
{
try
{
// =#REF!#REF! indicates that the named range refers to nothing. Ignore these..
if (name.Value != "=#REF!#REF!")
{
if (!retNamedRanges.ContainsKey(name.Name))
{
string keyName = name.Name;
object value = name.RefersToRange.get_Value(missing);
retNamedRanges.Add(keyName, value);
}
}
}
catch (Exception ex)
{
}
}
if(workBook!=null)
{
workBook.Close(false,missing,missing);
}
if(workBook!=null)
{
workBooks.Close();
}
System.Runtime.InteropServices.Marshal.ReleaseComObject(workBook);
System.Runtime.InteropServices.Marshal.ReleaseComObject(workBooks);
workBook = null;
workBooks = null;
excel.Application.Quit();
excel.Quit();
excel = null;
I have tried to do all possible things to clean up, but still it does not go. There are multiple EXCEL files that I need to read. Typically after my application executes I see multiple instances of EXCEL.EXE.
Is there anything else am missing with the clean up?
Many thanks in advance
"Some process specific to my application I am doing..."
Actually, this is most likely where the problem lies. As in the referenced duplicate, if you reference a property of the ApplicationClass then you'll need to make sure you dereference that property before the garbage collector will tidy up and remove Excel.
So, for instance, copy any data you need to string, int, etc. (or your own internal types based on these base types).
Try using Marshal.*Final*ReleaseComObject instead of ReleaseComObject.
Also call it on your "ApplicationClass excel" instance.
Related
I have an created a command line application which takes a crystal report file, opens it, exports it to a text file and then cleans up after itself by deleting the crystal report. This works fine until I suppress sections in the crystal report, then when I try and delete it I get the following:
System.IO.IOException: The process cannot access the file 'C:\ExportCMD\bin\Debug\ReportExport\2ee373f0-e05a-42d6-9b61-9c79e2662c20\14_636271819978854269.rpt' because it is being used by another process.
After some investigation I have found that it happens when I suppress a section in the open report, if I comment out that code it works fine. The code for setting the Suppress flag is:
private static void SuppressReportSection(ref Report openReport, string sectionToFind, bool hideSection)
{
if (!string.IsNullOrWhiteSpace(sectionToFind) && openReport != null)
{
openReport.Sections[sectionToFind].Suppress = hideSection;
}
}
After checking Google for a solution, I gave the following a try:
private static void SuppressReportSection(ref Report openReport, string sectionToFind, bool hideSection)
{
if (!string.IsNullOrWhiteSpace(sectionToFind) && openReport != null)
{
Sections reportSections = openReport.Sections;
try
{
if (reportSections != null)
{
reportSections[sectionToFind].Suppress = hideSection;
}
}
catch
{
throw;
}
finally
{
if (reportSections != null)
{
Marshal.ReleaseComObject(reportSections);
reportSections = null;
}
}
}
}
This unfortunately didn't cure it either. I have tried it with and without the ref in case it is something to do with the report object.
I am having to use the Crystal Reports 8.5, and I have added the reference.
When I destroy my report object I call the Marshal.ReleaseComObject, then GC.WaitForFullGCComplete() in the hope that it will have released the file. Once all this has completed I call the cleanup code which deletes the files. The clean up method will allow multiple attempts at deleting the file before it throws the error.
Where am I going wrong? If there is already an answer to the question can you point me to it please.
I am using C# with .Net 4 as this is the highest version we can get on the servers. I can't use the CrystalDecisions assemblies.
Try to use FinalReleaseComObject instead:
Therefore, use the ReleaseComObject only if it is absolutely required. If you want to call this method to ensure that a COM component is released at a determined time, consider using the FinalReleaseComObject method instead. FinalReleaseComObject will release the underlying COM component regardless of how many times it has re-entered the CLR. The internal reference count of the RCW is incremented by one every time the COM component re-enters the CLR. Therefore, you could call the ReleaseComObject method in a loop until the value returned is zero. This achieves the same result as the FinalReleaseComObject method.
reference
It's all still in your task manager all you have to do is..
Just remove the file in task manager using task kill by opening a .bat file. You can do this after you execute or before deleting the report.. after this you are freely can delete the file or you can open it again without any error.
While my C# program writes data continuously to an Excel spreadsheet, if the end user clicks on the upper right menu and opens the
Excel Options window, this causes following exception:
System.Runtime.InteropServices.COMException with HRESULT: 0x800AC472
This interrupts the data from being written to the spreadsheet.
Ideally, the user should be allowed to do this without causing an exception.
The only solution I found to this error code was to loop and wait until the exception went away:
Exception from HRESULT: 0x800AC472
which effectively hangs the app, data is not written to Excel and the user is left in the dark about the problem.
I thought about disabling the main menu of Excel while writing to it, but cannot find a reference on how to do this.
My app supports Excel 2000 to 2013.
Here is how to reproduce the issue:
Using Visual Studio Express 2013 for Windows Desktop, .NET 4.5.1 on Windows 7 64-bit with Excel 2007, create a new Visual C# Console Application project.
Add reference to "Microsoft ExceL 12.0 Object Library" (for Excel) and to "System.Windows.Forms" (for messagebox).
Here is the complete code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading.Tasks;
using System.Threading; // for sleep
using System.IO;
using System.Runtime.InteropServices;
using System.Reflection;
using Microsoft.Win32;
using Excel = Microsoft.Office.Interop.Excel;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int i = 3; // there is a split pane at row two
Excel.Application xlApp;
Excel.Workbook xlWorkBook;
Excel.Worksheet xlWorkSheet;
try
{
object misValue = System.Reflection.Missing.Value;
xlApp = new Excel.Application();
xlApp.Visible = false;
xlWorkBook = xlApp.Workbooks.Add(misValue);
xlApp.Visible = true;
xlWorkSheet = (Excel.Worksheet)xlWorkBook.Worksheets.get_Item(1);
// next 2 lines for split pane in Excel:
xlWorkSheet.Application.ActiveWindow.SplitRow = 2;
xlWorkSheet.Application.ActiveWindow.FreezePanes = true;
xlWorkSheet.Cells[1, 1] = "Now open the";
xlWorkSheet.Cells[2, 1] = "Excel Options window";
}
catch (System.Runtime.InteropServices.COMException)
{
System.Windows.Forms.MessageBox.Show("Microsoft Excel does not seem to be installed on this computer any longer (although there are still registry entries for it). Please save to a .tem file. (1)");
return;
}
catch (Exception)
{
System.Windows.Forms.MessageBox.Show("Microsoft Excel does not seem to be installed on this computer any longer (although there are still registry entries for it). Please save to a .tem file. (2)");
return;
}
while(i < 65000)
{
i++;
try
{
xlWorkSheet.Cells[i, 1] = i.ToString();
Thread.Sleep(1000);
}
catch (System.Runtime.InteropServices.COMException)
{
System.Windows.Forms.MessageBox.Show("All right, what do I do here?");
}
catch (Exception)
{
System.Windows.Forms.MessageBox.Show("Something else happened.");
}
}
Console.ReadLine(); //Pause
}
}
}
Lanch the app, Excel appears and data is written to it. Open the Excel options dialog window from the menu and up pops the error:
An exception of type 'System.Runtime.InteropServices.COMException' occurred in mscorlib.dll and wasn't handled before a managed/native boundary
Additional information: Exception from HRESULT: 0x800AC472
Click on Continue and my message box "All right, what do I do here?" appears.
Please advise?
Best regards,
Bertrand
We finally went all the way to Microsoft Support with this issue. Their final response was:
I am able to reproduce the issue. I researched on this further and
found that this behaviour is expected and by design. This
exception, 0x800AC472 – VBA_E_IGNORE, is thrown because Excel is busy
and will not service any Object Model calls. Here is one of the
discussions that talks about this.
http://social.msdn.microsoft.com/Forums/vstudio/en-US/9168f9f2-e5bc-4535-8d7d-4e374ab8ff09/hresult-800ac472-from-set-operations-in-excel?forum=vsto The work around I see is to explicitly catch this exception and retry
after sometime until your intended action is completed.
Since we cannot read the minds of the user who might decide to open a window or take a note without realizing the soft has stopped logging (if you mask the error), we decided to work around using:
xlWorkSheet.EnableSelection = Microsoft.Office.Interop.Excel.XlEnableSelection.xlNoSelection;
to lock the Excel window UI. We provide an obvious "unlock" button but when the user clicks it, he is sternly warned in a messagebox along with a "Do you wish to continue?"
Make Excel Interactive is a perfect solution. The only problem is if the user is doing something on Excel at the same time, like selecting range or editing a cell. And for example your code is returning from a different thread and trying to write on Excel the results of the calculations. So to avoid the issue my suggestions is:
private void x(string str)
{
while (this.Application.Interactive == true)
{
// If Excel is currently busy, try until go thru
SetAppInactive();
}
// now writing the data is protected from any user interaption
try
{
for (int i = 1; i < 2000; i++)
{
sh.Cells[i, 1].Value2 = str;
}
}
finally
{
// don't forget to turn it on again
this.Application.Interactive = true;
}
}
private void SetAppInactive()
{
try
{
this.Application.Interactive = false;
}
catch
{
}
}
xlApp = new Excel.Application();
xlApp.Interactive = false;
What I have done successfully is to make a temp copy of the target excel file before opening it in code.
That way I can manipulate it independent of the source document being open or not.
One possible alternative to automating Excel, and wrestling with its' perculiarities, is to write the file out using the OpenXmlWriter writer (DocumentFormat.OpenXml.OpenXmlWriter).
It's a little tricky but does handle sheets with > 1 million rows without breaking a sweat.
OpenXml docs on MSDN
Since Interop does cross threading, it may lead to accessing same object by multiple threads, leading to this exception, below code worked for me.
bool failed = false;
do
{
try
{
// Call goes here
failed = false;
}
catch (System.Runtime.InteropServices.COMException e)
{
failed = true;
}
System.Threading.Thread.Sleep(10);
} while (failed);
I'm loading an excel spreadsheet with pivot tables and charts in it into a web-browser control using C#. The spreadsheets have several connection strings all pointing to a development database. I would like to be able to change the connection strings to one that has been supplied by the user when my application is run.
I have already managed to get this to work when the spreadsheet was created programmatically and the SourceType of the pivot caches are set to 'External'. However, when a spreadsheet that was created in Excel is loaded the source type is set to 'Database' and exceptions are thrown when the 'Connection' property is accessed.
Is there a way to change the SourceType (read-only) property or the connection string of such a spread sheet?
Here is a sample of my code which is based on a solution to a similar problem.
EXCEL.Worksheet sheet = (EXCEL.Worksheet)_application.ActiveSheet;
foreach (EXCEL.PivotTable table in sheet.PivotTables())
{
table.PivotCache().Connection = ConnectionString;
table.RefreshTable();
}
I have also tried this
var workBooks = _application.Workbooks.Cast<EXCEL.Workbook>();
var pivotCaches = workBooks.SelectMany(arg => GetPivotCaches(arg));
foreach (EXCEL.PivotCache cache in pivotCaches)
{
cache.Connection = ConnectionString;
}
In both cases I get a System.Runtime.InteropServices.COMException as soon as I access the Connection property of the pivot cache. Any ideas?
Here is the fix for the problem based on a solution which I found here.
//update the connections
foreach (EXCEL.WorkbookConnection connection in workbook.Connections)
{
if (connection.Type.ToString() == "xlConnectionTypeODBC")
{
connection.ODBCConnection.BackgroundQuery = false;
connection.ODBCConnection.Connection = ConnectionString;
}
else
{
connection.OLEDBConnection.BackgroundQuery = false;
connection.OLEDBConnection.Connection = ConnectionString;
}
}
//Refresh all data
workbook.RefreshAll();
This fixed the problem with most of the reports throwing exceptions. The only one that didn't work was re-created from scratch (it was really old and had been around the block!)
Hope this helps others.
We use Excel interop in numerous places in our code, however I have one function which doesn't ever seem to close down the Excel process it uses.. I've simplified the code down, and it just seems like whenever I open a workbook in this function it stays hanging around. I've included the code below, I've made sure every object is defined, released and nulled, and yet Excel stays running.
System.Data.DataTable dtExcelSheet = new System.Data.DataTable();
Microsoft.Office.Interop.Excel.Application excelObject = new Microsoft.Office.Interop.Excel.Application();
dtExcelSheet.Columns.Add("SheetName", typeof(string));
dtExcelSheet.Columns["SheetName"].ReadOnly = false;
dtExcelSheet.Columns["SheetName"].Caption = "Sheet Name";
Workbooks wbs = excelObject.Workbooks;
Workbook excelWorkbook = wbs.Add(excelFile);
excelWorkbook.Close(false, System.Reflection.Missing.Value, System.Reflection.Missing.Value);
wbs.Close();
excelObject.Quit();
int i1 = Marshal.FinalReleaseComObject(excelWorkbook);
int i2 = Marshal.FinalReleaseComObject(wbs);
int i3 = Marshal.FinalReleaseComObject(excelObject);
excelWorkbook = null;
wbs = null;
excelObject = null;
GC.GetTotalMemory(false);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.GetTotalMemory(true);
Make sure you're not calling Excel on a background thread. I had a similar problem where I was cleaning up all the COM objects, but Excel still wasn't dying and that turned out to be the problem.
I wrote up my experiences and a solution here.
Complete guess, but does this row:
dtExcelSheet.Columns.Add("SheetName", typeof(string));
return the column that is created?
If so you'd probably need to store that reference and clean that up at the end.
Edit: Also, I don't think you should be setting the variables to null at the end, I think that just accesses them again.
And you shouldn't have to tell the GC to collect etc, but I assume that might be your test code.
It's been a while since I've mucked around with this stuff, so nothing jumps out at me.
My usual recommendation in these cases is to set your excel application object to Visible = true to see if there's not a dialog popping up on you. Excel/Word will sometimes refuse to shut down if they think there's a modal dialog open no matter what else you may do. It's the first thing to check anyway.
I tried running your code, but I could not reproduce the issue. If I step through it with a debugger, the Excel process terminates after the last call to FinalReleaseComObject. Is it possible that the culprit lies in some code not present in your listing?
When using COM interop, I have found that it is all too easy to increment the reference count on COM objects in very subtle ways. For example, let's say you do something like this:
excelWorkbook.Foo.Bar();
This can increment the reference count on the Foo object, leaving you with no means of releasing it afterwards...and leaving the Excel process lingering around until you shut down your app. You can re-write the above line of code like this:
Foo foo = excelWorkbook.Foo;
foo.Bar();
Marshal.ReleaseComObject(foo);
It's not as pretty, but it will decrement the reference count on the Foo object after you are done using it.
Same thing happened to me the other day. See my question on Excel Automation as to the fix (question deals with multiple row deletion mostly but I also had the same problem as you turns out it has to do with releasing all the COM objects properly).
I got the same problem. I read the whole thread and tried the examples given. I added these lines to the sample code:
xlRange = (Excel.Range)xlWorkSheet.get_Range("B1", "C1");
xlRange.Merge(Type.Missing);
xlRange = (Excel.Range)xlWorkSheet.get_Range("H5", "M5");
xlRange.Merge(Type.Missing);
xlRange = (Excel.Range)xlWorkSheet.get_Range("N5", "V5");
xlRange.Merge(Type.Missing);
xlRange = (Excel.Range)xlWorkSheet.get_Range("W5", "Z5");
xlRange.Merge(Type.Missing); // up to here everything seems fine
///////////////////////////////////////////////////////////
// if I add these lines below, the process just hanged until
// I manually close the form
xlRange = (Excel.Range)xlWorkSheet.get_Range("AA5", "AB5");
xlRange.Merge(Type.Missing);
xlBorder.Borders.Weight = Excel.XlBorderWeight.xlThin;
///////////////////////////////////////////////////////////
I also added this, before releasing the excelsheet object:
Marshal.ReleaseComObject(xlRange);
Please tell me how should I go about this as I am almost giving up on this. Your response is greatly appreciated. FYI, I'm a newbie and I am looking forward to your reply.
Thanks,
george
Excel is a COM Automation Server.
Even though you call Application.Quit() and you Release the Reference to the COM object, the exe itself will not end. You will still be able to see it in task manager. Making further calls to Excel will use the running instance.
The Excel instance will exit after your application closes.
Ever get "RPC server not found/running" type COM errors? Now you know why.
I use this:
private void DoSomeStuff()
{
var application = new Microsoft.Office.Interop.Excel.Application();
// Do stuff ...
CloseExcel(application);
}
private static void CloseExcel(Microsoft.Office.Interop.Excel.Application excel)
{
while (Marshal.ReleaseComObject(excel) != 0) { }
excel = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
Works like a charm :)
i've found this method to be most useful:
How to properly cleanup Excel interop objects in c#
keep in mind that you'll need to customize the code for your particular situation. i.e. if you create and hold a reference to the application, a workbook, and three range objects, you'll need to:
Call GC.Collect()
Call GC.WaitForPendingFinalizers()
Call Marshal.FinalReleaseComObject for each of the range objects
Close() the workbook
Call Marshal.FinalReleaseComObject for the book object
Close the application instance
Call Marshal.FinalReleaseComObject for the application object
if you have seven range objects, or hold references to any other objects then you'll need to call Marshal.FinalReleaseComObject for each of those as well. the order in which you release everything is also very important.