I wrote a C# application that queries a DB and puts the results in an excel file. The program itself works fine. However if I open a second completely unrelated excel file while the application is running, an exception occurs and the process stops.
Now, in the program itself I've set the visibility to false, and after opening the second unrelated Excel file, the one that was being generated is suddenly open and visible, and then I get the exception.
Anyone know what is causing the problem or how to resolve it?
The relevant parts of the code is as follows, the exception occurs at the line worksheet.get_Range(currCol.GetString() + excelRow, Missing.Value).Formula = item.ToString();
The exception itself is : "Exception from HRESULT: 0x800AC472"
Application exc = new Application();
//Makes the Excel file not visible
exc.Visible = false;
exc.UserControl = false;
exc.DisplayAlerts = false;
Workbooks workbooks = exc.Workbooks;
Workbook workbook = workbooks.Add(XlWBATemplate.xlWBATWorksheet);
Sheets sheets = workbook.Worksheets;
Worksheet worksheet = (Worksheet)sheets.get_Item(1);
int excelRow = 1;
ExcelChar currCol = new ExcelChar('A');
System.Data.DataTable testTable = dbConnection.searchQuery("Select * from testTable").Copy();
if (worksheet == null)
{
Console.WriteLine("ERROR: worksheet == null");
}
foreach (System.Data.DataRow row in testTable.Rows)
{
foreach (var item in row.ItemArray)
{
worksheet.get_Range(currCol.GetString() + excelRow, Missing.Value).Formula = item.ToString();
currCol.Add(1);
}
excelRow++;
currCol = new ExcelChar('A');
}
Take a look at this thread.
It looks like your error is VBA_E_IGNORE, in which case you need to register an IMessageFilter implementation so you can implement retry logic.
I've seen this issue in the past, when using the same instance of Excel with interop and interactively - for example when instatiating an Excel Application object using:
Marshal.GetActiveObject("Excel.Application")
In your case, you're creating a new instance of Excel using:
exc = new Application();
What you should try to do is to make sure you close this instance as quickly as possible. This is not always easy, because of the problem described in this KB article. Otherwise you might consider something other than COM Interop to write to Excel (e.g. OLEDB or a third party library such as Aspose or EPPlus).
When Excel is busy - e.g. has a modal dialog displayed, or is busy loading a workbook, it will not respond to incoming COM messages, so it returns an error which is translated into this exception. An IMessageFilter implementation (specifically: RetryRejectedCall) will typically retry a few times, then either fail or prompt the user to retry ("Server busy").
When using the Office InterOp services, you must close the objects you've created in reverse order.
private static void Excel_FromDataTable(DataTable dt)
{
// Global missing variable.
object missing = System.Reflection.Missing.Value;
// Creates an excel object,
Excel.Application excel = new Microsoft.Office.Interop.Excel.Application();
// Then a workbooks object,
Excel.Workbooks workbooks = excel.Workbooks;
// Then adds a workbook object,
Excel.Workbook workbook = workbooks.Add(true);
// Then adds a worksheet object,
Excel.Worksheet activeSheet = workbook.ActiveSheet;
// Then names the worksheet to what we need.
activeSheet.Name = "scbyext";
// Add column headings,
int iCol = 0;
// for each row of data,
int iRow = 0;
foreach (DataRow r in dt.Rows)
{
iRow++;
// Then add each row's cell data.
iCol = 0;
foreach (DataColumn c in dt.Columns)
{
iCol++;
excel.Cells[iRow, iCol] = r[c.ColumnName];
}
}
// Disable Excel prompts.
excel.DisplayAlerts = false;
// Save the workbook to the correct folder.
workbook.SaveAs("C:\\Escaped\\Path",
Excel.XlFileFormat.xlExcel8, missing, missing,
false, false, Excel.XlSaveAsAccessMode.xlNoChange,
missing, missing, missing, missing, missing);
// Release the objects we made, in reverse order, to allow Excel to quit correctly.
ReleaseObj(activeSheet);
ReleaseObj(workbook);
ReleaseObj(workbooks);
excel.Quit();
ReleaseObj(excel);
}
If you don't, the process will stay stuck open. I'm not sure what it's doing when it stays stuck, but by the end of the day the CPU Time used by it can get quite high.
I had a similar problem. I was creating an excel file using C# with a lot of charts that took a long time to create. If a user opened an existing excel file while my C# file was still being written it caused an exception to be thrown and my application would crash.
I fixed it with the following:
xlApp = new Application();
xlApp.IgnoreRemoteRequests = true;
Related
I have to open an excel file and want to get the data to word a document. I use to this code:
private Excel.Application excelapp;
Type ExcelType = Type.GetTypeFromProgID("Excel.Application");
dynamic ExcelInst = Activator.CreateInstance(ExcelType);
this.excelapp = ExcelInst;
this.workbook = this.excelapp.Workbooks.Open(Filename : this.filePath, ReadOnly: true);
I used all the techniques to close/quit/dispose the opened process (except for Process.Kill), but those won't work. How do I fully terminate any running background tasks?
You just need to call the Quit method of the Excel Application class when you are done:
private Excel.Application excelapp;
Type ExcelType = Type.GetTypeFromProgID("Excel.Application");
dynamic ExcelInst = Activator.CreateInstance(ExcelType);
this.excelapp = ExcelInst;
this.workbook = this.excelapp.Workbooks.Open(Filename : this.filePath, ReadOnly: true);
' do whatever you need there
this.workbook.Save();
this.excelapp.Quit();
If unsaved workbooks are open when you use this method, Excel displays a dialog box asking whether you want to save the changes. You can prevent this by saving all workbooks before using the Quit method or by setting the DisplayAlerts property to False. When this property is False, Excel doesn't display the dialog box when you quit with unsaved workbooks; it quits without saving them.
If you set the Saved property for a workbook to True without saving the workbook to the disk, Excel will quit without asking you to save the workbook. Note: this does not save the workbook; it just makes it look like it's saved.
My understanding is that since interop uses “COM” objects, you need to “release" those objects. Something like…
Marshal.ReleaseComObject(workbook);
Marshal.ReleaseComObject(xlApp);
in that order. This should release those objects from the background tasks
I have had success with the following approach when using office-interop…
Set up all the Excel/Word/Outlook. Etc.… interop stuff in a try/catch/finally statement. This should almost guarantee that your code will properly close and “release” the resources it uses even if there is an exception. Otherwise, the code will always be susceptible to leaking a resource.
Word.Application app = null;
Word.Document doc = null;
try {
app = new Word.Application();
//app.Visible = true;
doc = app.Documents.Add();
// do some stuff with the document…
doc.SaveAs2(filename);
MessageBox.Show("Document created successfully !");
}
catch (Exception e) {
MessageBox.Show("ERROR: " + e.Message);
}
finally {
if (doc != null) {
doc.Close();
Marshal.ReleaseComObject(doc);
}
if (app != null) {
app.Quit();
Marshal.ReleaseComObject(app);
}
}
I use the following to open excel and read / update it.
Excel.Application app;
Excel.Workbook wb;
private void openAndRead()
{
app = new Excel.Application();
wb = app.Workbooks.Open(YourFileHere);
//do stuff here read or write
//app.ActiveSheet.Cells[1, "A"] = "write this in cell A1";
//string read= app.ActiveSheet.Cells[1, "A"];
}
private void closeExcel ()
{
app.DisplayAlerts = false; //this will stop popup questions from excel
wb.Close();
app.Quit();
}
If you want to be confident that EXCEL.EXE process will go away, there's one way to realize it. May be this is not the elegant solution, but at least it does work. The main idea is to catch the Excel window's handle. Having this handle, you can get its process and shut it down.
WARNING: If you don't make Excel window visible, this method WILL NOT work!
using System.Diagnostics;
using Excel = Microsoft.Office.Interop.Excel;
void Kill_Excel()
{
// Create instance of Excel application.
// If you don't make Excel window visible, this method WILL NOT work!
Excel.Application excel = new Excel.Application { Visible = true };
// Catch Excel window handle
IntPtr hwnd = new IntPtr(excel.Hwnd);
// Get EXCEL.EXE process
Process xlproc = Process.GetProcesses().Where(p => p.MainWindowHandle == hwnd).First();
// Do some operations
Excel.Workbook book = excel.Workbooks.Add();
Excel.Worksheet sheet = book.Sheets[1] as Excel.Worksheet;
sheet.Cells[1, 1] = "Hello!";
// Close Excel
book.Close(SaveChanges: false);
excel.Quit();
// Garbage collection
GC.Collect();
GC.WaitForFullGCComplete();
// Kill process - FOR SURE! :)
xlproc.Kill();
}
var tempFileName = file.Replace(".xlsx", "_Temp.xlsx");
Application excelApp = new Application();
Workbooks books = excelApp.Workbooks;
Workbook excelFile = books.Open(file);
Sheets sheets = excelFile.Worksheets;
var app = excelApp.Application;
DeleteRows(sheets, 3);
excelFile.SaveAs(tempFileName);
excelFile.Close();
books.Close();
app.Quit();
excelApp.Quit();
Marshal.FinalReleaseComObject(sheets);
Marshal.ReleaseComObject(excelFile);
Marshal.ReleaseComObject(books);
Marshal.FinalReleaseComObject(excelApp);
sheets = null;
excelFile = null;
excelApp = null;
books = null;
I am trying to dispose of my excel objects, however even the above code still leaves me with hanging EXCEL.EXE processes after the program has finished running. I have seen this question on here a number of times, but most of the askers had not tried the ReleaseComObject() method, however that did not fix anything for me.
EDIT: This is the code i use to read the table after deleting the header
System.Data.DataTable schemaTable = excelConnection.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, new object[] { null, null, null, "TABLE" });
DataRow schemaRow = schemaTable.Rows[0];
string sheet = schemaRow["TABLE_NAME"].ToString();
if (!sheet.EndsWith("_"))
{
string query = "SELECT * FROM [Daily Payment$]";
OleDbDataAdapter daexcel = new OleDbDataAdapter(query, excelConnection);
dtexcel.Locale = CultureInfo.CurrentCulture;
daexcel.Fill(dtexcel);
var reader = dtexcel.CreateDataReader();
and here is the code for DeleteRows:
void DeleteRows(Sheets sheets, int n)
{
foreach (Worksheet workSheet in sheets)
{
Range range = workSheet.get_Range("A1", "A" + n);
Range row = range.EntireRow;
row.Delete(XlDirection.xlUp);
Marshal.ReleaseComObject(workSheet);
}
}
FINAL EDIT: I have reposted the new code that I have at the top of the post. I am still getting one lingering object (even though i run the process multiple times). Calling Gc.Collect() works, but as a relative neophyte, i figured it was best to avoid something so powerful.
For freeing Office COM interop objects, you can go by the 'one release per . rule'. That is, when you have a chain of member accesses, you need to free all the intermediate objects.
This often means splitting up your code into additional lines.
Following that rule, you're missing one. You need to keep a handle to the Workbooks object you've implicitly created.
Microsoft.Office.Interop.Excel.Application excelApp = new Microsoft.Office.Interop.Excel.Application();
Workbooks books = excelApp.Workbooks;
Workbook excelFile = books.Open(file);
DeleteRows(excelFile, 3);
Then you need to call:
Marshal.ReleaseComObject(books);
at the end.
Failing this, you can also try calling:
books.Close()
before you quit Excel. And to go overkill, you can change all your ReleaseComObject calls to FinalReleaseComObject.
EDIT:
In your newly posted update, you are not releasing your Row or Range objects. Call FinalReleaseComObject on these, and I'd expect the problem to be solved.
I am writing an Excel app in C# which does some data visualization. The first stage of the analysis is to copy the data from a single sheet in an input workbook to a new sheet in an output workbook where the analysis is performed.
This runs correctly on my laptop but when the client I am producing the code for runs the same application, they get some extra worksheets created which cause the code to crash.
It's pretty difficult to debug the application on the client's PC so I am having difficultly understanding how it's possible the results are different on two different machines.
Setup code is below, any help at all would be incredibly useful. Using Microsoft.Office.Interop.Excel 14.
Excel.Workbooks wbs = excelApp.Workbooks;
Excel.Workbook dataSource = wbs.Open(inFile);
//get the data sheet from the source file
Excel.Sheets dataSourceSheets = dataSource.Worksheets;
Excel.Worksheet dataSourceSheet = (Excel.Worksheet)dataSourceSheets.get_Item(1);
//create a new workbook
dataOutput = wbs.Add();
outputSheets = dataOutput.Worksheets;
//ensure there are only 2 sheets in the output
int osc = outputSheets.Count;
for (int i = 2; i < osc; ++i)
{
outputSheets.get_Item(i).Delete();
}
//setup summary and contents pages
summaryPage = outputSheets.get_Item(1);
summaryPage.Name = "Summary";
contentsPage = outputSheets.Add(Type.Missing, summaryPage);
contentsPage.Name = "Contents";
dataSourceSheet.Copy(Type.Missing, contentsPage); //copy the sheet AFTER contents page
dataSheet = (Excel.Worksheet)outputSheets.get_Item(outputSheets.Count);
dataSheet.Name = "Raw Data";
//fails with outputSheets.Count == 5
if (outputSheets.Count != 3)
{
throw new Exception("Error, there are too many sheets here!");
}
I read a lot about how to communicate from C# to Excel and saw some good references.
The thing is I'm looking for an easy way to update existing excel file while it is still open, using
the most advanced way (linq for example) and not OLEDB.
This should be few lines of code describing how can I read current cell, update his value and take into consideration the file might not be exist, but if it does exist and open, it will just update the file without giving the notification the file is already exist. If the file doesn't exist it will create a new one.
SO:
1. connect to an excel file, check if it exist, if not create one
2. read from cell
3. update cell
4. do this while the excel sheet can be still open wild.
I already visited the following places:
http://social.msdn.microsoft.com/Forums/vstudio/en-US/ef11a193-54f3-407b-9374-9f5770fd9fd7/writing-to-excel-using-c
Updating an excel document programmatically
Update specific cell of excel file using oledb
I used the following code:
if (File.Exists(#"C:\\temp\\test.xls"))
{
Microsoft.Office.Interop.Excel.Application excelApp = new Microsoft.Office.Interop.Excel.Application();
Microsoft.Office.Interop.Excel.Workbooks workBooks = excelApp.Workbooks;
Microsoft.Office.Interop.Excel.Workbook workBook = workBooks.Open(#"C:\\temp\\test.xls");
Microsoft.Office.Interop.Excel.Worksheet workSheet = workBook.Worksheets.get_Item(1);
int nColumns = workSheet.UsedRange.Columns.Count;
int nRows = workSheet.UsedRange.Rows.Count;
for (int i = 2; i < nRows; i++)
{
workSheet.Columns["1","A"] = "test";
}
workBook.Save();
workBook.Close();
}
So I use VSTO Contrib to help out with COM Interop and memory management and that's why you see .WithComCleanup().
To open up a spreadsheet:
try
{
using (var xlApp = new Microsoft.Office.Interop.Excel.Application().WithComCleanup())
using (var wrkbooks = xlApp.Resource.Workbooks.WithComCleanup())
using (var wrkbook = wrkbooks.Resource.Open(filePath, false, true).WithComCleanup())
{
If the excel file is already open, then to get around the Read-Only follow this tip:
wrkbooks.Resource.Open(filePath, false, FALSE).WithComCleanup())
Here's how I iterate though the sheets (note that some Excel sheets are ChartSheets):
foreach (object possibleSheet in xlApp.Resource.Sheets)
{
Microsoft.Office.Interop.Excel.Worksheet aSheet = possibleSheet as Microsoft.Office.Interop.Excel.Worksheet;
if (aSheet == null)
continue;
Here is a quick way to get a reference to the sheet you're interested in:
activeSheet = wrkbook.Resource.Sheets[sheetToImport];
You read and write to cells just as you've identified:
for (int i = 2; i < nRows; i++)
{
activeSheet.Columns["1","A"] = "test";
}
Here is how I close Excel:
MathematicaAPI.XlHelper.CloseExcel((Worksheet)activeSheet, (Workbook)wrkbook.Resource , (Workbooks)wrkbooks.Resource);
public static void CloseExcel(Worksheet activeSheet, Workbook wrkbook, Workbooks wrkbooks)
{
//http://support.microsoft.com/kb/317109 -> excel just wont close for some reason
if (activeSheet != null)
{
Marshal.FinalReleaseComObject(activeSheet);
activeSheet = null;
}
if (wrkbook != null)
{
wrkbook.Saved = true;
wrkbook.Close(Microsoft.Office.Interop.Excel.XlSaveAction.xlDoNotSaveChanges);
}
if (wrkbooks != null)
{
wrkbooks.Close();
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
Sometimes Excel just wont close and you have to kill it (after trying to close it properly of course) - I dont recommend this, but if you cant track down the un-disposed memory and all else fails then...
if (xlApp != null)
{
ExcelDataSourceHelper.GetWindowThreadProcessId(new IntPtr(xlApp.Resource.Hwnd), ref excelProcessId);
}
if (excelProcessId > 0)
{
XlHelper.KillProcess(excelProcessId);
}
public static void KillProcess(int excelProcessId)
{
if (excelProcessId > 0)
{
System.Diagnostics.Process ExcelProc = null;
try
{
ExcelProc = System.Diagnostics.Process.GetProcessById(excelProcessId);
if (ExcelProc != null)
{
ExcelProc.Kill();
}
}
catch
{ }
}
}
Note: I reduce the chances of needing to kill Excel by using VSTO Contrib with Using's.
OK thank you all for trying to solve the issue
The solution was using Excel 2011/2013 Add-In which can communicate excel as a plugin
create an application-level add-in for Microsoft Office Excel. The features that you create in this kind of solution are available to the application itself, regardless of which workbooks are open.
You can visit MSDN
I am trying to an Add-in directly from C# so that when I open a workbook, and do a Workbook.Calculate() the UDF's (User Defined Functions) that are defined in an external addin correctly calculate in the worksheet. Currently, I am looping through each adding and simple setting:
AddIn.Installed = true
This does not work. C# does not load add-in at all, and I want to avoid using VBA. I want to open a workbook an excel workbook with the specific add in loaded, do a full calculated, and should have all values of the worksheet updated, including cells with UDF's.
Thanks for any help....
Some code:
Excel.Workbook wkbk = ExcelApp.ActiveWorkbook;
Excel.XlFixedFormatType paramExportFormat = Excel.XlFixedFormatType.xlTypePDF;
Excel.XlFixedFormatQuality paramExportQuality = Excel.XlFixedFormatQuality.xlQualityStandard;
bool paramOpenAfterPublish = false;
bool paramIncludeDocProps = true;
bool paramIgnorePrintAreas = true;
object paramFromPage = Type.Missing;
object paramToPage = Type.Missing;
ExcelApp.Visible = true;
//foreach (Excel.AddIn aiTemp in ExcelApp.AddIns)
//{
// if (aiTemp.Name.Contains(""))
// {
// aiTemp.Installed = false;
// aiTemp.Installed = true;
// }
//}
while (ExcelApp.CalculationState == Microsoft.Office.Interop.Excel.XlCalculationState.xlCalculating)
{
Thread.Sleep(50);
}
ExcelApp.CalculateFull();
var wksht = wkbk.ActiveSheet;
Excel.Range rng = ((Excel.Worksheet)wksht).get_Range("B1", "B1");
rng.Calculate();
//EnsureCalcFinished();
ExcelApp.Visible = false;
wkbk.ExportAsFixedFormat(Microsoft.Office.Interop.Excel.XlFixedFormatType.xlTypePDF, PathToDocument.Replace(".xlsx", ".pdf"), paramExportQuality, true, false, Type.Missing, Type.Missing, true,Type.Missing);
UPDATE:
I found a link with the method I use to register UDFs.
Ref: http://msdn.microsoft.com/en-us/library/ms173189(v=vs.80).aspx
In Excel, you need to go to Options -> Add-Ins => Excel Add-in (Go..) => automation = select the library and hit OK. Once you do this once, it'll be auto-loaded each time you open excel.
Here is the algorithm to load excel AddIns so that when you open excel workbook, a calculation will include UDF's defined in Add-in:
1)Initiliaze Excel Application
ExcelApp = new Excel.Application();
2)Load AddIns *
foreach (Excel.AddIn ai in ExcelApp.AddIns)
{
ai.Installed = false;
ai.Installed = true;
ExcelApp.Wait(50);
}
**The Key is to load add-ins before you open Excel Workbook.
3)Open Excel Workbook, which will trigger calculations
4)Set Calculation Mode to manual so that any changes in Interop do not trigger lengthy recalc
ExcelApp.Calculation = Microsoft.Office.Interop.Excel.XlCalculation.xlCalculationManual;
5)Perform any manipulations, and perform calc
ExcelApp.CalculateFull();
6)Dispose of Excel Objects appropriately
Hope this helps someone with a similar issue.. Ended up being a simple fix for a simple problem. Just remember to load add-ins before opening the workbook. Otherwise, opening an excel workbook with UDF's dependent on AddIn will fail.