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).
Related
I'm using the following code to run a VBA macro via C# Excel interop:
public void macroTest()
{
Excel.Application xlApp = new Excel.Application();
xlApp.Visible = true;
string bkPath = #"C:\somePath\someBk.xlsm";
Excel.Workbook bk = xlApp.Workbooks.Open(bkPath);
string bkName = bk.Name;
string macroName = "testThisMacro_m";
string runString = "'" + bkName + "'!"+macroName;
xlApp.Run(runString);
bk.Close(false);
xlApp.Quit();
}
testThisMacro_m is in a module testMacro, and this runs successfully. When I replace it with:
string macroName = "testThisMacro_s";
where testThisMacro_s has its code in Sheet1, the xlApp.Run() line gives the following COM Exception:
Cannot run the macro ''someBk.xlsm'!testThisMacro_s'.
The macro may not be available in this workbook or all macros may be disabled.
I checked macro security settings, and they are indeed set to "Disable with notification", but being able to run a macro from a module and not from a worksheet seems to indicate that this is a different issue than application-level macro security.
Is there something different that I have to do when making an interop call to a macro in a worksheet?
UPDATE: I was able to get the macro to execute by changing the call to:
string macroName = "Sheet1.testThisMacro_s"
but it seems that this hands control back to C# before the macro completes, so now I need to figure out how to check for macro completion (probably a different question).
A Worksheet object is an object - and objects are defined with class modules. Worksheets, workbooks, user forms; they're all objects. And you can't just call a method on an object, if you don't have an instance of that object.
Macros work off standard modules, which aren't objects, and don't need to be instantiated.
Application.Run can't call methods of an object, that's why macros need to be in standard modules.
I was able to get the macro to execute by changing the call to:
string macroName = "Sheet1.testThisMacro_s"
Wouldn't a helper sub solve both of your problems, re: Mat's Mug's reply concerning instantiation?
In some standard module:
Sub testHelperSubToBeCalledFromInterop
Call Sheet1.testThisMacro_s
End Sub
EDIT:
I'm trying to determine whether or not an instance of Excel of running with a particular file open, and if so attach to it so I can control that instance.
I've searched around and the majority of what I've got have come from this question. It has a reference to another site, but unfortunately it's dead for me so I cannot read up on it.
My code so far is;
//Is Excel open?
if (Process.GetProcessesByName("EXCEL").Length != 0)
{
Process[] processes = Process.GetProcesses();
foreach (Process process in processes)
{
//Find the exact Excel instance
if (process.ProcessName.ToString() == "EXCEL" && process.MainWindowTitle == ("Microsoft Excel - " + fileName))
{
//Get the process ID of that instance
int processID = (int)Process.GetProcessById(process.Id).MainWindowHandle;
//Attach to the instance...
Excel.Application existingExcel = (Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject(process.Id);
}
}
}
So far I've managed to get the process ID of the instance I want to attach to, but I'm lost when it comes to using that ID.
Any ideas on how to proceed?
Marshal.GetActiveObject() doesn't take a process ID as a parameter. What you want is:
Marshal.GetActiveObject("Excel.Application");
Note that this doesn't require keeping track of the process at all, there just needs to be one.
It gets a lot more complicated if you can have multiple processes and want to attach to a specific one. That is where the answer to the other question comes in.
There is also a good article at http://blogs.msdn.com/b/andreww/archive/2008/11/30/starting-or-connecting-to-office-apps.aspx with a more full description of different ways of launching excel. Note that not all of them are necessarily up to date with Excel 2013, where having a single process for all Excel windows complicates things. For your purposes though, the GetActiveObject solution should be fine.
I have a weird problem where Excel is behaving differently on my development machine and a testing machine.
In my add-in, I've turned off ScreenUpdating in several places for long running processes. On my machine this works fine. On the testing machine, Excel sets ScreenUpdating = true as soon as I write to a cell.
The following code demonstrates the issue for me.
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
Microsoft.Office.Interop.Excel.Application excel = Globals.ThisAddIn.Application;
MessageBox.Show(excel.ScreenUpdating.ToString());
excel.ScreenUpdating = false;
MessageBox.Show(excel.ScreenUpdating.ToString());
Workbook workbook = Globals.ThisAddIn.Application.ActiveWorkbook;
Worksheet w = (Worksheet)workbook.Worksheets[1];
((Range)w.Cells[1, 1]).Value = "Test";
MessageBox.Show(excel.ScreenUpdating.ToString());
}
On my machine, opening Excel gives three message boxes saying
"True", "False", "False".
On the test machine they say
"True", "False" and "True".
I've also stepped through with a remote debugger and watched the ScreenUpdating property change immediately after the cell value is set. Further, this isn't the only thing that resets ScreenUpdating. Adding or removing a Worksheet or Workbook will also do this.
The Excel version on each system is the same (14.0.6112.5000 (32-bit)).
What could be causing this? How can I fix it so that Excel respects my settings?
I am experincing the same problem with ScreenUpdating (and other settings) being reset to true by the Bloomberg add-in with VSTO add-in. Their support are working on a solution but in the meanwhile the following solution works ok:
By hiding each sheet when interacting with its cells the screen won't update and you'll get the performance benefits. Make sure to not try to hide the last sheet as it will raise an error. Not sure if this is suitable for your project but it is a workable solution for mine.
Here is some example code in VB.NET to hide a sheet:
' Create a book with a single worksheet
Dim Book As Excel.Workbook
Book = Globals.ThisAddIn.Application.Workbooks.Add(Excel.XlWBATemplate.xlWBATWorksheet)
'% Create sheet variable
Dim Sheet As Excel.Worksheet
Sheet = TryCast(Book.ActiveSheet, Excel.Worksheet)
' Visible
Sheet.Visible = Excel.XlSheetVisibility.xlSheetVisible
' Hidden but user able to show
Sheet.Visible = Excel.XlSheetVisibility.xlSheetHidden
' Hidden for user as well
Sheet.Visible = Excel.XlSheetVisibility.xlSheetVeryHidden
Other addins in Excel can interfere with that single global setting.
It is for that reason you are supposed to save the current ScreenUpdating state to a local variable before, and restore it after, each use.
Ignore the changing of that setting in the ThisAddIn_Startup event (as you would not normally do your work there anyway).
If an add-in is interfering with your ScreenUpdating somehow, you can stop them form doing it by disabling events temporarily. using EnableEvents. It is possible that this will break the functionality of that addon, but it worked fine for my needs.
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.
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.