I am working on a project where I need to automate some workflows in Excel, and I have hit a pretty nasty roadblock. In the project, I am using Visual Studio Tools For Office to create a document level add-in. A user uses a ribbon control that is part of this project to automate copying of worksheets from workbooks external to the project. The external workbooks are loaded from SQL blobs and written to disk. The add-in code opens each workbook, copies a worksheet into the add-in workbook, and then closes that external workbook. Typically, the first workbook works just fine, but opening a subsequent workbook will throw an AccessViolationException.
public void AddSheetFromTempFile(string tempfilePath)
{
Sheets sheets = null;
Excel.Workbook workbook = null;
Excel.Workbooks books = null;
try
{
books = this.Application.Workbooks;
//Throws AccessViolationException
workbook = books.Open(tempfilePath, 0, true, 5,
String.Empty, String.Empty, true, XlPlatform.xlWindows,
String.Empty, true, false, 0, true, true, false);
sheets = workbook.Worksheets;
sheets.Copy(After: this.GetLastWorksheet());
workbook.Close(SaveChanges: false);
}
finally
{
if (sheets != null)
{
Marshal.FinalReleaseComObject(sheets);
}
if (workbook != null)
{
Marshal.FinalReleaseComObject(workbook);
}
if (books != null)
{
Marshal.FinalReleaseComObject(books);
}
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
//extension method for getting last worksheet
public static Microsoft.Office.Interop.Excel.Worksheet
GetLastWorksheet(this Microsoft.Office.Tools.Excel.WorkbookBase workbook)
{
int veryHiddenSheets = 0;
foreach(Worksheet sheet in workbook.Worksheets)
{
if(sheet.Visible == XlSheetVisibility.xlSheetVeryHidden)
{
veryHiddenSheets++;
}
}
int lastIndex = workbook.Worksheets.Count - veryHiddenSheets;
return workbook.Worksheets[lastIndex];
}
So I’ve narrowed down the issue to a set of repeatable steps. This issues seems to be stemming from cases where you add some N sheets to the workbook, then delete them, and re-add a sheet. I enabled native debugging sugggested here http://social.msdn.microsoft.com/forums/en-US/vsto/thread/48cd3e88-d3a6-4943-b272-6d7ea81e11e3. I see the following call-stack when the exception above.
ntdll.dll!_ZwWaitForSingleObject@12() + 0x15 bytes
ntdll.dll!_ZwWaitForSingleObject@12() + 0x15 bytes
kernel32.dll!_WaitForSingleObjectExImplementation@12() + 0x43 bytes
[External Code]
First-chance exception at 0x2ff2489e in Excel.exe: 0xC0000005: Access violation reading location 0x00000000.
A first chance exception of type 'System.AccessViolationException' occurred in PublicCompModel.DLL
An exception of type 'System.AccessViolationException' occurred in PublicCompModel.DLL but was not handled in user code
Not sure if I am misusing the COM object, but I definitely find it odd that I can replicate this with deleting all of the sheets and that this is local to Excel.
After a lots of debugging, a support ticket with Microsoft’s VSTO team, and some long nights, I finally got to the answer. The issue was not stemming from my code, but from the workbook itself. Initially we had written the code as a standalone project and then integrated sheets from our users’ spreadsheet model. The key problem is that when you start copying sheets from other workbooks you bring along with them named references to the workbooks. Our user group provided us with a file that had hundreds of bad references which we hadn’t previously seen.
Even if you suppress the warnings with the Application object, these events are still firing in the background. The large number of events firing while C# was manipulating the state of the workbook resulted in an AccessViolationException.
The lesson I learned: make sure to clean up the workbook and observe how the workbook is behaving without your code. Due to timing issues, we were forced to rewrite the solution in VBA while Microsoft debugged our code. Before we ever cleaned up the resources, the VBA code ran stabler which might stem from the fact that it is interpreted and, by my observations, running on a single thread.
As an aside, if you’re working with VSTO in the context of a document add-in you should be careful about freeing references. In many cases, you probably don’t need to do this, because Excel is probably cleaning this up for you. Freeing COM objects is considered dangerous.