使用 Excel 互操作创建新工作簿时 C# HRESULT 0x800AC472

C# HRESULT 0x800AC472 when creating a new Workbooks with Excel Interop

直到最近,这个预定的 C# 控制台应用程序运行良好,但大约 2 周后,它在我开始使用 Excel Interop 的代码开头崩溃。

当 运行 它自己运行时它运行良好,但是当我尝试通过计划任务管理器执行它时(Windows Server 2008 r2 Windows Server 2016,都已正确安装 Excel 和有效的许可证密钥)它在我尝试启动后立即崩溃。

(不好意思,我不是很习惯写"real"英文,如果有什么不清楚的,请不要害怕寻求解释)。

计划任务管理器指出程序return错误0xE0434352。我已经搜索了一下,发现它实际上是一个通用错误号。所以我添加了一个 AppDomain 捕捉器来在日志文件中获取正确的错误号。我现在知道真正的错误是 "HRESULT : 0x800AC472" 并抛出 :

wb = xlApp.Workbooks.Add(XlWBATemplate.xlWBATWorksheet);

我已经测试过声明一个空工作簿然后在其中添加一个 sheet,或者使用 "XlSheetType.xlWorksheet" 而不是 "XlWBATemplate.xlWBATWorksheet" 但仍然相同。它只通过了一次,但在 :

上出现了同样的错误

ws.Name = "Name";

我还读到了一个 Whosebug 错误,但只创建了一个 Excel 进程,我尝试添加一个 Thread.Sleep(50//500//5000) 但它没有改变任何事物。 这是它崩溃的代码部分,我认为不需要代码的任何其他部分:

        object misValue = System.Reflection.Missing.Value;
        String part = "";
        String pathD = "";
        List<Part> partList = new List<Part>();
        int ligne = 1;
        Workbook wb = null;
        Worksheet ws = null;

        Microsoft.Office.Interop.Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();
        if (xlApp == null)
        {
            Console.WriteLine("EXCEL could not be started. Check that your office installation and project references are correct.");
            return;
        }
        xlApp.Visible = false;
        xlApp.DisplayAlerts = false;

        foreach (StockRow stockRow in Requete.getStockRows())
        {
            if (stockRow.Part != part)
            {
                partList.Add(new Part(stockRow.PartCode, stockRow.Part));
                if (wb != null)
                {
                    ws.Range[ws.Cells[1, 1], ws.Cells[ligne, 9]].entirecolumn.autofit();
                    ws.Range[ws.Cells[1, 1], ws.Cells[ligne, 9]].entirerow.autofit();
                    ws.Range[ws.Cells[1, 1], ws.Cells[ligne, 9]].verticalalignment = XlVAlign.xlVAlignCenter;
                    if (!Directory.Exists(pathD))
                    {
                        Directory.CreateDirectory(pathD);
                    }
                    wb.SaveAs(pathD + "\Stock_" + DateTime.Now.ToString("dd-MM-yyyy_HH") + "h.xlsx", XlFileFormat.xlOpenXMLWorkbook, misValue, misValue, false, false,
                        XlSaveAsAccessMode.xlNoChange, XlSaveConflictResolution.xlUserResolution, true, misValue, misValue, misValue);
                    wb.Close();
                }

                pathD = path + stockRow.PartCode;
                wb = xlApp.Workbooks.Add(XlWBATemplate.xlWBATWorksheet);
                ws = (Worksheet)wb.ActiveSheet;
                ws.Name = "Name";
                ws.PageSetup.Zoom = false;
                ligne = 1;
                ws.Cells[ligne, 1] = "Column Name";
                ws.Cells[ligne, 2] = "Column Name";
                ws.Cells[ligne, 3] = "Column Name";
                ws.Cells[ligne, 4] = "Column Name";
                ws.Cells[ligne, 5] = "Column Name";
                ws.Cells[ligne, 6] = "Column Name";
                ws.Cells[ligne, 7] = "Column Name";
                ws.Cells[ligne, 8] = "Column Name";
                ws.Cells[ligne, 9] = "Column Name";

                //codes continue ...
            }
            // codes continue ...
        }
        // codes continue ...

我显然很想知道为什么这现在不再有效,即使您没有完美的答案,即使是提示也将不胜感激。

非常感谢您阅读到这里。

在服务器手动启动Excel,然后执行您的应用程序手动执行的步骤。请务必使用任务计划程序用于 运行 您的程序的同一用户帐户。强烈建议使用实际用户帐户而不是某些内置服务帐户。

如果您确实需要使用 "SYSTEM" 这样的内置帐户,您需要确保 C:\Windows\System32\config\systemprofile 没有任何问题,例如缺少桌面文件夹。

可能 Excel 崩溃并试图要求您恢复上一个文档,从而阻止任何进展。当您在没有 UI 的情况下使用自动化时,有时会发生奇怪的事情。评论说应该避免无人值守的自动化,尤其是在服务器上,是正确的。一种更稳定的方法是使用可以访问 XLSX 文档并在那里进行修改的库。我们使用服务器端办公自动化将 Word 和 PowerPoint 发布为 PDF 和 XPS 文件,但只要 Office 需要,它就会随机失败。

如果一切都失败了,请在服务器上使用全新的用户帐户 运行 应用程序,从而 Excel。通常这是 Excel 本身的问题,您无法在代码中对此做任何事情,并且缺少 UI,也不会进行太多调试。

如果您无法访问 Excel 运行 所在的服务器,您将无法重现该行为,那您就不走运了。

您需要在使用完 COM 对象后释放它们,否则它会导致进程在您调用关闭后仍保持活动状态。

如果您在没有释放的情况下获取了太多 COM 对象,那么 Excel 将会完全崩溃(堆损坏、访问冲突、内存损坏等)。

例如

xlApp.Workbooks.Add

将导致保证 COM 泄漏。 xlApp.Workbooks 会获取一个 Workbooks COM 对象,但您没有存储对它的引用。

你会想做

var workbooks = xlApp.Workbooks;
wb = workbooks.Add(...);

然后当您使用完工作簿后

Marshal.ReleaseComObject(workbooks);
Marshal.ReleaseComObject(wb);

对您获取的每个 COM 对象执行此操作。 IE。任何属于 Interop.Excel 命名空间

的东西

查明是否有 COM 泄漏的最简单方法是检查任务管理器,看看您的应用程序进程在退出后是否保持活动状态。 (假设你没有像 Environment.ExitProcess.Kill 这样的强制杀戮)