Jasica.Net

C#, .Net, SQL i nie tylko

BadImageFormatException - problem z 32 i 64 bitowymi bibliotekami po raz N-ty?

Wystąpienie BadImageFormatException, wśród doświadczonych developerów, rodzi pytanie czy przez przypadek nie została załadowana zewnętrzna biblioteka w złej wersji- 32 lub 64 bitowej. Nie jest to jednak jedyna przyczyna występowania tego wyjątku. Na początek jednak przyjrzyjmy się kawałkowi prostego kodu:

using System;
using System.Diagnostics;

namespace BadImageFormatExceptionSample
{
    public class Data
    {
        public string Text { get; set; }
    }

    public class Base<T> where T : Data
    {
        public virtual T Print(T t)
        {
            Log(() =>
            {
                Console.WriteLine(t.Text);
            });
            return t;
        }

        protected virtual void Log(Action action)
        {
            var stackTrace = new StackTrace();
            var method = stackTrace.GetFrame(1).GetMethod();
            Console.WriteLine("Type: {0}, Method: {1}; before", 
                method.DeclaringType.Name, method.Name);
            action();
            Console.WriteLine("Type: {0}, Method: {1}; after", 
                method.DeclaringType.Name, method.Name);
        }
    }

    public class TestClass : Base<Data>
    {
        public override Data Print(Data data)
        {
            Log(() =>
            {
                data = base.Print(data);
            });

            return data;
        }
    }

    class Program
    {
        static void Main()
        {
            var z = new TestClass();
            z.Print(new Data() { Text = "Test" });
        }
    }
}

Kod jest stosunkowo prosty. Można by dać go jako rozgrzewkę osobie na rozmowie kwalifikacyjnej i spytać co program wypisze na ekranie. Jako wyjście spodziewalibyśmy się tekstu podobnego do:

Type: TestClass, Method: Print; before
Type: Base`1, Method: Print; before
Test
Type: Base`1, Method: Print; after
Type: TestClass, Method: Print; after

Niestety, wbrew wszelkim oczekiwaniom pojawi się tu wyjątek, który jest bohaterem tego artykułu. Dla dopełnienia prezentuję początek wyjścia z programu:

Type: TestClass, Method: Print; before

Unhandled Exception: System.BadImageFormatException: An attempt was made to load 
    a program with an incorrect format. (Exception from HRESULT: 0x8007000B)

Na pierwszy rzut oka ciężko wskazać co jest nie tak, jednak przy dłuższej analizie zdekompilowanego kodu można dojść do wniosku, że coś jest nie w porządku. Problem da się łatwo obejść; jeśli wystąpił wystarczy użyć mechanizmu delegatów i wszystko będzie działać jak na począku zakłądaliścy:

public override Data Print(Data data)
{
    var print = new Func<Data, Data>(base.Print);
    Log(() =>
    {
        data = print(data);
    });
    return data;
}

Całe szczęście problem nie występuje często - trzeba użyć kilku bardziej zaawansowanych mechanizmów języka by problem wystąpił. Problem został naprawiony w kompilatorze nadchodzącym z wersją 2012 Visual Studio, więc możemy spać spokojnie i dalej śmiało używać wyrażeń lambda, jak tylko wyobraźnia nam pozwoli. Dociekliwym przedstawiam jeszcze kod IL wygenerowany przez Visual Studio 2012:

 

.method public hidebysig instance void  '<Print>b__0'() cil managed
{
  // Code size       25 (0x19)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldarg.0
  IL_0003:  ldfld      class BadImageFormatExceptionSample.TestClass BadImageFormatExceptionSample.TestClass/'<>c__DisplayClass1'::'<>4__this'
  IL_0008:  ldarg.0
  IL_0009:  ldfld      class BadImageFormatExceptionSample.Data BadImageFormatExceptionSample.TestClass/'<>c__DisplayClass1'::data
  IL_000e:  call instance class BadImageFormatExceptionSample.Data BadImageFormatExceptionSample.TestClass::'<>n__FabricatedMethod3'(class BadImageFormatExceptionSample.Data)
  IL_0013:  stfld      class BadImageFormatExceptionSample.Data BadImageFormatExceptionSample.TestClass/'<>c__DisplayClass1'::data
  IL_0018:  ret
} // end of method '<>c__DisplayClass1'::'<Print>b__0'

Oraz kłopotliwa wersja z 2010 dla porównania(różnica w lini IL_000e):

.method public hidebysig instance void  '<Print>b__0'() cil managed
{
  // Code size       25 (0x19)
  .maxstack  8
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldarg.0
  IL_0003:  ldfld      class BadImageFormatExceptionSample.TestClass BadImageFormatExceptionSample.TestClass/'<>c__DisplayClass1'::'<>4__this'
  IL_0008:  ldarg.0
  IL_0009:  ldfld      class BadImageFormatExceptionSample.Data BadImageFormatExceptionSample.TestClass/'<>c__DisplayClass1'::data
  IL_000e:  call instance !0 BadImageFormatExceptionSample.TestClass::'<>n__FabricatedMethod3'(!0)
  IL_0013:  stfld      class BadImageFormatExceptionSample.Data BadImageFormatExceptionSample.TestClass/'<>c__DisplayClass1'::data
  IL_0018:  ret
} // end of method '<>c__DisplayClass1'::'<Print>b__0'

Na koniec rodzi się jeszcze jedna myśl- czy nie za bardzo ufamy narzędziom z których korzystamy? Kompilatory tworzą też ludzie i mogą popełniać błędy, więc do każdego kodu należy podchodzić z pewną rezerwą- mniejszą bądź większą, w zależności od źródła. Taka programistyczna zasada ograniczonego zaufania.