morzel.net

.net, js, html, arduino, java... no rants or clickbaits.

BadImageFormatException, x86 i x64

Czy natrafiłeś kiedyś na wyjątek typu BadImageFormatException lub komunikat "An attempt was made to load a program with an incorrect format" ("Próbowano załadować program w niepoprawnym formacie")?

Jeśli tak to być może program, który próbowałeś uruchomić nie został skompilowany z użyciem opcji /platform:x86. Zastanawiasz się pewnie dlaczego podczas programowania w C# powinieneś przejmować się tym na jakiej platformie (x86/x64) kod zostanie wykonany. Cóż, w większości przypadków nie musisz o tym myśleć. Jeśli nie używasz bloków unsafe ani nie importujesz natywnych modułów problemu nie ma, bowiem Twój kod C# zostaje przetłumaczony do kodu pośredniego (CIL), który przed wykonaniem zostaje skompilowany (JIT) na postać odpowiednią dla platformy docelowej. Ok, ale...

Wyobraź sobie, że używasz w swojej aplikacji funkcji importowanej z 32 bitowej DLL. Gdy uruchomisz program na 32 bitowym systemie wszystko działa jak należy. Niestety na maszynie x64 otrzymujesz wspominany wyjątek BadImageFormatException. Dlaczego? Jeśli assembly importujące DLL zostało skompilowane z opcją /platform:anycpu to do jego uruchomienia na systemie 64 bitowym została użyta 64 bitowa wersja CLR. Próba załadowania 32 bitowej DLL z aplikacji działającej w procesie 64 bitowym nie powiedzie się. Gdyby do kompilacji została użyta opcja /platform:x86 wówczas na 64 bitowym systemie operacyjnym program zostałby uruchomiony w 32 bitowej wersji CLR (z użyciem WoW64: Windows 32-bit on Windows 64-bit).

Platformę docelową (przełącznik /platform kompilatora C#) można w Visual Studio 2010 ustawić w oknie “Properties” na zakładce “Build”. By tam trafić kliknij prawym klawiszem w plik projektu w Solution Explorer i wybierz „Properties” lub użyj menu głównego „Project | <nazwa projektu> Properties…”.

Ustawienie platformy docelowej w Visual Studio 2010. Kliknij aby powiększyć...

Microsoft stworzył przydatne narzędzie wiersza poleceń o nazwie CorFlags, które służy między innymi do podglądu lub ustawiania platformy docelowej. Dostęp do tego narzędzia można uzyskać korzystając z Visual Studio Command Prompt albo przez znalezienie go bezpośrednio na dysku (u mnie jesto ono pod C:\Program Files\Microsoft.NET\SDK\v2.0\Bin\CorFlags.exe)

Poniżej znajduje się kilka przykładów tego co możesz zobaczyć po sprawdzeniu plików EXE stworzonych z różnymi wartościami opcji /platform kompilatora (do sprawdzenia pliku służy polecenie: CorFlags nazwa.pliku):

anycpu x86 x64
Version   : v4.0.30319
CLR Header: 2.5
PE        : PE32
CorFlags  : 1
ILONLY    : 1
32BIT     : 0
Signed    : 0
Version   : v4.0.30319
CLR Header: 2.5
PE        : PE32
CorFlags  : 3
ILONLY    : 1
32BIT     : 1
Signed    : 0
Version   : v4.0.30319
CLR Header: 2.5
PE        : PE32+
CorFlags  : 1
ILONLY    : 1
32BIT     : 0
Signed    : 0

W kontekście tego posta istotne są 2 rzędy z wyniku zwróconego przez CorFlags: PE i 32BIT.

  • PE: PE32 oznacza, że plik może być wykonany w środowisku x86 i x64
  • PE: PE32+ oznacza, że plik może być wykonany jedynie w środowisku 64 bitowym
  • 32BIT: 1 oznacza, że program musi być wykonany w środowisku x86

Zrozumienie znaczenia 32BIT: 1 jest naprawdę istotne jeśli chcesz uniknąć problemów z importowanie 32 bitowch DLL na 64 bitowej wersji Windows. Jeśli flaga 32BIT jest ustawiona i uruchomisz plik PE32 na x64 wówczas Twoja aplikacja zostanie uruchomiona w środowisku 32 bitowym (z użyciem WoW), dzięki czemu zaistnieje możliwość zaimportowania 32 bitowej DLL. Jeśli flaga 32BIT nie jest ustawiona, aplikacja uruchomi się w procesie 64 bitowym – co spowoduje problem z załadowaniem biblioteki.

Dzięki CorFlags można w łatwy sposób zmodyfikować wartość flagi 32BIT. Do jej ustawienia służy przełącznik /32BIT+

CorFlags file.exe /32BIT+

A do jej usuwania /32BIT-

CorFlags file.exe /32BIT-

Tak więc nawet jeśli nie masz możliwości przekompilowania problematycznego kodu z odpowiednią opcją /platform nadal możesz użyć 32 bitowej DLL w 64 bitowej wersji systemu Windows :)