ThisWorkbook.Application.Workbooks.Open中的AccessViolationException

时间:2012-05-19 16:32:23

标签: visual-studio-2010 vsto excel-interop

我正在开发一个项目,我需要在Excel中自动化一些工作流程,而且我遇到了一个非常讨厌的障碍。在项目中,我使用Visual Studio Tools For Office来创建文档级外接程序。用户使用作为此项目一部分的功能区控件来自动从项目外部的工作簿中复制工作表。外部工作簿从SQL blob加载并写入磁盘。加载项代码打开每个工作簿,将工作表复制到加载项工作簿,然后关闭该外部工作簿。通常,第一个工作簿工作正常,但打开后续工作簿将抛出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];
    }

所以我把问题缩小到一系列可重复的步骤。这个问题似乎源于您向工作簿添加N个工作表,然后删除它们并重新添加工作表的情况。我在这里启用了本地调试建议http://social.msdn.microsoft.com/forums/en-US/vsto/thread/48cd3e88-d3a6-4943-b272-6d7ea81e11e3。当上面的异常时,我看到以下调用堆栈。

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

我不确定我是否在滥用COM对象,但我确实觉得很奇怪,我可以通过删除所有工作表来复制它,并且这是Excel本地的。

2 个答案:

答案 0 :(得分:3)

经过大量的调试,微软VSTO团队的支持票,以及一些漫长的夜晚,我终于得到了答案。这个问题不是来自我的代码,而是来自工作簿本身。最初我们将代码编写为独立项目,然后从用户的电子表格模型中集成表单。关键问题是,当您开始从其他工作簿复制工作表时,您带来了对工作簿的命名引用。我们的用户组为我们提供了一个文件,其中包含数百个我们以前从未见过的错误引用。

即使您使用Application对象抑制警告,这些事件仍会在后台触发。在C#操作工作簿状态时触发的大量事件导致AccessViolationException。

我学到的经验:确保清理工作簿并观察工作簿在没有代码的情况下的行为方式。由于时间问题,我们被迫在微软调试我们的代码时用VBA重写解决方案。在我们清理资源之前,VBA代码运行得更稳定,这可能源于它被解释的事实,并且根据我的观察,在单个线程上运行。

顺便说一句,如果您在文档加载项的上下文中使用VSTO,则应该注意释放引用。在许多情况下,您可能不需要这样做,因为Excel可能正在为您清理它。释放COM对象被视为dangerous

答案 1 :(得分:0)

我记得有类似的东西。虽然我不记得具体细节,但这是我保存和关闭工作簿的模板。我希望这会有所帮助。

    xlWorkBook.SaveAs(fileName, Microsoft.Office.Interop.Excel.XlFileFormat.xlWorkbookNormal, misValue, misValue, misValue, misValue, Microsoft.Office.Interop.Excel.XlSaveAsAccessMode.xlExclusive, misValue, misValue, misValue, misValue, misValue);
    xlWorkBook.Close(true, misValue, misValue);
    xlApp.Quit();

    //Release objects
    releaseObject(xlWorkSheet);
    releaseObject(xlWorkBook);
    releaseObject(xlApp);

...

private void releaseObject(object obj)
{
    try
    {
        System.Runtime.InteropServices.Marshal.ReleaseComObject(obj);
        obj = null;
    }
    catch (Exception ex)
    {
        obj = null;
        Response.Write("Exception Occured while releasing object " + ex.ToString());
    }
    finally
    {
        GC.Collect();
    }
}