2

我正在做一个项目,我需要在 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 本地的。

4

2 回答 2

3

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

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

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

顺便说一句,如果您在文档加载项的上下文中使用 VSTO,您应该小心释放引用。在许多情况下,您可能不需要这样做,因为 Excel 可能会为您清理这些。释放 COM 对象被认为是危险的。

于 2012-06-25T14:42:55.180 回答
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();
    }
}
于 2012-05-22T22:52:44.673 回答