再谈System.BadImageFormatException
今天,当我们继续学习.NET异常处理系列时,我们将查看System.BadImageFormatException。System.BadImageFormatException与GIF或JPG无关,而是在.NET应用程序尝试加载与当前公共语言运行库(CLR)所需的正确格式不匹配的动态链接库(.dll)或可执行文件(.exe)时发生。
在本文中,我们将看到System.BadImageFormatException在.NET异常层次结构中的确切位置,并查看System.BadImageFormatException的一些潜在原因,让我们开始讨论它!
如前所述,System.BadImageFormatException发生在非常特殊的情况下:当.NET试图使用.dll或.exe时,即以某种方式与当前公共语言运行库不兼容。“不兼容的公共语言运行时”的定义可能有所不同,但通常这意味着.NET版本(1.1、2.0等)或各种编译程序集的CPU类型(32位与64位)不匹配。
最后,System.BadImageFormatExceptions表示版本控制不兼容。对于许多现代软件应用程序,的主要版本通常包括打破兼容性问题,防止与以前版本的某些方面向后兼容。.NET程序集(.dll或.exe)基本相同,尝试使用包含不兼容项的两种不同类型的程序集通常会生成System.BadImageFormatException。
为了说明这一点,我们将通过几个不同的例子。我已经包含了下面的完整代码示例以供参考,之后我们将更详细地探讨细节:
using System; using System.Reflection; using Utility; namespace Airbrake.BadImageFormatException { class Program { static void Main(string[] args) { LoadingNonDotNetLibraryExample(); Logging.Log("-----------------"); DifferingCPUExample(); Logging.Log("-----------------"); OldDotNetExample(); } private static void LoadingNonDotNetLibraryExample() { try { // Generate path to notepad.exe. string filePath = Environment.ExpandEnvironmentVariables("%windir%") + @"\System32\notepad.exe"; Assembly assem = Assembly.LoadFile(filePath); } catch (System.BadImageFormatException exception) { Logging.Log(exception); } } private static void DifferingCPUExample() { try { // Load Utility.dll, a 64-bit assembly. Assembly assem = Assembly.LoadFrom(@".\Utility.dll"); Logging.Log(assem.ToString()); } catch (System.BadImageFormatException exception) { Logging.Log(exception); } } private static void OldDotNetExample() { try { // Load Author-1.1.dll (compiled in .NET 1.1). Assembly assem = Assembly.LoadFrom(@".\Author-1.1.dll"); Logging.Log(assem.ToString()); } catch (System.BadImageFormatException exception) { Logging.Log(exception); } } } } using System; using System.Diagnostics; namespace Utility { /// <summary> /// Houses all logging methods for various debug outputs. /// </summary> public static class Logging { /// <summary> /// Outputs to <see cref="System.Diagnostics.Debug.WriteLine"/> if DEBUG mode is enabled, /// otherwise uses standard <see cref="Console.WriteLine"/>. /// </summary> /// <param name="value">Value to be output to log.</param> public static void Log(object value) { #if DEBUG Debug.WriteLine(value); #else Console.WriteLine(value); #endif } /// <summary> /// When <see cref="Exception"/> parameter is passed, modifies the output to indicate /// if <see cref="Exception"/> was expected, based on passed in `expected` parameter. /// <para>Outputs the full <see cref="Exception"/> type and message.</para> /// </summary> /// <param name="exception">The <see cref="Exception"/> to output.</param> /// <param name="expected">Boolean indicating if <see cref="Exception"/> was expected.</param> public static void Log(Exception exception, bool expected = true) { string value = $"[{(expected ? "EXPECTED" : "UNEXPECTED")}] {exception.ToString()}: {exception.Message}"; #if DEBUG Debug.WriteLine(value); #else Console.WriteLine(value); #endif } } }
引发System.BadImageFormatException的第一种(也可以说是最常见的)方法是尝试使用非托管程序集时,就像它是使用.NET框架创建的程序集一样。非托管程序集是由.NET的公共语言运行库未处理和编译的代码生成的程序集。这包括许多较旧的应用程序和程序集,特别是为32位系统创建的应用程序和程序集。
作为一个例子,这里我们试图加载一个非托管程序集—特别是位于Windows/System32目录中的众所周知的notepad.exe程序集:
private static void LoadingNonDotNetLibraryExample() { try { // Generate path to notepad.exe. string filePath = Environment.ExpandEnvironmentVariables("%windir%") + @"\System32\notepad.exe"; Assembly assem = Assembly.LoadFile(filePath); } catch (System.BadImageFormatException exception) { Logging.Log(exception); } }
.NET对此不满意,因为notepad.exe不受管理(不是使用.NET编译的),因此引发System.BadImageFormatException:
[EXPECTED] System.BadImageFormatException: The module was expected to contain an assembly manifest. (Exception from HRESULT: 0x80131018) at System.Reflection.RuntimeAssembly.nLoadFile(String path, Evidence evidence) at System.Reflection.Assembly.LoadFile(String path) at Airbrake.BadImageFormatException.Program.LoadingNonDotNetLibraryExample() in D:\work\Airbrake.io\Exceptions\.NET\Airbrake.BadImageFormatException\Program.cs:line 26: The module was expected to contain an assembly manifest. (Exception from HRESULT: 0x80131018)
另一种可能引发System.BadImageFormatException的方法是尝试加载使用不同于当前在.NET上执行的CPU类型编译的程序集。
例如,在我们的许多代码片段中,我们一直在使用包含日志类的简单实用程序名称空间,这使得在调试和测试期间输出日志信息更加容易。默认情况下,Utility.dll编译为64位程序集。但是,如果我们将当前的CPU配置切换为以x86(32位)CPU执行,则会遇到一些问题:
private static void DifferingCPUExample() { try { // Generate path to Utility.dll, a 64-bit assembly. Assembly assem = Assembly.LoadFrom(@".\Utility.dll"); Logging.Log(assem.ToString()); } catch (System.BadImageFormatException exception) { Logging.Log(exception); } }
当试图加载Utility.dll程序集(64位)时,当将当前代码编译为32位时,.NET抛出System.BadImageFormatException,通知我们这些格式不匹配:
[EXPECTED] System.BadImageFormatException: An attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B) at System.Reflection.RuntimeAssembly.nLoadFile(String path, Evidence evidence) at System.Reflection.Assembly.LoadFile(String path) at Airbrake.BadImageFormatException.Program.DifferingCPUExample() in D:\work\Airbrake.io\Exceptions\.NET\Airbrake.BadImageFormatException\Program.cs:line 40: An attempt was made to load a program with an incorrect format. (Exception from HRESULT: 0x8007000B)
最后,如果尝试加载使用更旧版本的.NET(如.NET 1.1)编译的程序集,我们也会遇到问题:
private static void OldDotNetExample() { try { // Load Author-1.1.dll (compiled in .NET 1.1). Assembly assem = Assembly.LoadFrom(@".\Author-1.1.dll"); Logging.Log(assem.ToString()); } catch (System.BadImageFormatException exception) { Logging.Log(exception); } }
在上述情况下,System.BadImageFormatException通常会在编译时抛出,而不是在运行时抛出,因为.NET编译器在试图首先执行任何代码之前都会识别出不兼容。