测试一定要充分!接上回书,话说我提交了Office PIA的代码以后,测试的同学就展开了充分的测试。在此之前我可是信心满满的,自以为已经做了充分的测试了,应该测不出什么bug来了。可是,很快,测试的同学就报了一个Exception issue给我:
System.Runtime.InteropServices.COMException (0x80028018): Old format or invalid type library. (Exception from HRESULT: 0x80028018 (TYPE_E_INVDATAREAD)) at ******.Excel.ExcelApp.CreateWorkbook(String fileName) in C:\svnroot\******\Trunk\Development\Software\******\Excel\ExcelApp.cs:line 213
这个问题在我本地从不重现,在测试的机器上也不总是可重现。经比对,发现重现此issue的测试机,其语言和区域设置(Clock, Language, and Region)中的Format设置的是Chinese (Simplified, PRC)。简单地说,就是涉及到中文环境时能复现这个问题。在此要说明一下,本人就职于某大型外企,大家平时的的计算机都是英文的系统和英文的语言、区域、格式设置。
总算发现一定能复现这个exception issue的条件了,但是乍一看这个问题真是匪夷所思,我编程当中没有用到什么涉及到不同的Culture的地方,也不曾hard code地传入什么字符串内容,怎么会单单在非英语环境中出错呢?但是不管怎么说,我还是要检讨一下,说明我之前做的测试很不充分,没有做国际性/本地化测试。
好,问题的背景介绍到此。既然有了线索,那就好办了。借助万能的Google和MSDN,很快就找到了root cause。MSDN中已经有文章说明此事,微软承认这是Microsoft Office PIA的Bug:
BUG: "Old format or invalid type library" error when automating Excel
Link: http://support.microsoft.com/kb/320369
If you automate Microsoft Excel with Microsoft Visual Basic .NET, Microsoft Visual C# .NET, or Microsoft Visual C++, you may receive the following error when calling certain methods:
Error: 0x80028018 (-2147647512)
Description: Old Format or Invalid Type Library
......
......
此文中说到,当前线程的Culture不是英语(“en-US”)的时候,就会遭遇此问题。
怎么解决这个问题呢?其实这个问题我肯定没有办法解决,我只能说绕开这个问题(Workaround)。
首先我做的尝试是,在出问题的地方(Excel.Worksheet.SaveAs),前后分别设置当前Culture为“en-US”和恢复原有Culture属性。但是程序执行到其它的地方又报出同样的问题。可见,这个Office PIA Bug影响的绝不仅仅是SaveAs()这个方法,还有很多其他的方法。具体是哪些方法我就不去较这个劲了,我直接在类的初始化中设置当前线程的Culture为“en-US”,然后在类的Dispose方法中恢复原有Culture属性:
using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Threading; using Excel = Microsoft.Office.Interop.Excel; /// <summary> /// Excel application interop class. /// </summary> public class ExcelApp : IDisposable { ...... ...... #region Private variables /// <summary>Original culture info</summary> private CultureInfo originalCultureInfo = null; #endregion ...... ...... #region Initializations and Finalizations /// <summary> /// Initializes a new instance of the ExcelApp class. /// </summary> public ExcelApp() { this.originalCultureInfo = Thread.CurrentThread.CurrentCulture; this.SetCurrentCultureAsENU(); } /// <summary> /// Finalizes an instance of the ExcelApp class. /// </summary> ~ExcelApp() { this.Dispose(false); } #endregion ...... ...... /// <summary> /// Implement Dispose method in IDisposable /// </summary> public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } #endregion ...... ...... #region Private methods for IDisposable /// <summary> /// Dispose method /// </summary> /// <param name="disposing">Disposing status</param> protected void Dispose(bool disposing) { if (this.disposed) { return; } if (disposing) { this.Close(); } this.RestoreCurrentCulture(); this.disposed = true; } #endregion ...... ...... #region Private methods for Office PIA bug fixing /// <summary> /// Set current culture as en-US to resolve the bug described in Notice. /// Notice: this method is to avoid Microsoft Office PIA bug Q320369: /// BUG: "Old format or invalid type library" error when automating Excel /// Link: http://support.microsoft.com/default.aspx?scid=kb;en-us;320369 /// </summary> private void SetCurrentCultureAsENU() { Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); } /// <summary> /// Restore original culture. /// </summary> private void RestoreCurrentCulture() { Thread.CurrentThread.CurrentCulture = this.originalCultureInfo; } #endregion }