近在編寫(xiě).net應(yīng)用程序時(shí),發(fā)現(xiàn)某些平臺(tái)下無(wú)法加載SQLite DLL的問(wèn)題。
癥狀表現(xiàn)為:
a. 本地Windows 7/8 64bit開(kāi)發(fā)環(huán)境完全正常。
b. 某些Windows 7 64bit用戶的計(jì)算機(jī)無(wú)法加載System.Data.SQLite.DLL。
c. 極個(gè)別Windows XP的計(jì)算機(jī)無(wú)法加載該DLL。
無(wú)法加載DLL時(shí),均報(bào)BadImageFormatException異常,甚至直接被Windows關(guān)閉而無(wú)法采集異常信息。奇怪的是在當(dāng)前目錄包含了這個(gè)DLL文件,所以理應(yīng)能成功加載才是。
由于各種場(chǎng)景的組合在一起,一時(shí)很難判斷問(wèn)題出于何處。起初筆者努力嘗試各種方法試圖在本機(jī)重現(xiàn),也均告失敗。在偶然的機(jī)會(huì),筆者刪除了本機(jī)所有與SQLite相關(guān)的組件,問(wèn)題成功重現(xiàn)。
絕望之中燃起了一點(diǎn)希望。我發(fā)現(xiàn)自己曾經(jīng)安裝了三個(gè)與SQLite相關(guān)的組件:
- SQLite-1.0.66.0-setup.exe
- sqlite-netFx35-setup-bundle-x64-2008-1.0.82.0.exe
- sqlite-netFx35-setup-bundle-x86-2008-1.0.82.0.exe
SQLite-1.0.66.0-setup.exesqlite-netFx35-setup-bundle-x64-2008-1.0.82.0.exesqlite-netFx35-setup-bundle-x86-2008-1.0.82.0.exe
反復(fù)多次后確認(rèn)了以下內(nèi)容:
- SQLite-1.0.66.0-setup.exe - 無(wú)效果
- sqlite-netFx35-setup-bundle-x86-2008-1.0.82.0.exe - 無(wú)效果
- sqlite-netFx35-setup-bundle-x64-2008-1.0.82.0.exe - 成功
SQLite-1.0.66.0-setup.exe - 無(wú)效果sqlite-netFx35-setup-bundle-x86-2008-1.0.82.0.exe - 無(wú)效果sqlite-netFx35-setup-bundle-x64-2008-1.0.82.0.exe - 成功
看來(lái),用戶系統(tǒng)上沒(méi)有sqlite-netFx35-setup-bundle-x64-2008-1.0.82.0.exe,所以導(dǎo)致運(yùn)行失敗。經(jīng)仔細(xì)檢查,這個(gè)包在系統(tǒng)GAC中安裝了64位DLL,而之前我發(fā)布的目錄中包含的是32位DLL文件。一切真相大白,將64位版本與32位版本分開(kāi)發(fā)布,問(wèn)題解決!
到這里,本該可以松口氣了,但我心中仍然充滿疑惑:
(1) 在64位系統(tǒng)上,我的.net應(yīng)用到底算是32位的還是64位的?
(2) 32位應(yīng)用程序照理可以在64位系統(tǒng)上正常運(yùn)行,為什么當(dāng)前目錄的DLL沒(méi)有加載成功?
(3) 為何以前調(diào)用的第三方DLL從沒(méi)有發(fā)生32/64位的問(wèn)題,只有這個(gè)DLL如此特別?
(4) .net的加載DLL的順序是什么,到底以哪個(gè)優(yōu)先?
經(jīng)過(guò)一番認(rèn)真的Google,上述迷團(tuán)一一破解。
(1) 在64位系統(tǒng)上,.net程序是通常以64位程序運(yùn)行的。Windows不會(huì)啟用Wow。這可以從任務(wù)管理器中看到(在64位系統(tǒng)中運(yùn)行的32位程序?qū)⒁?32或“32位”標(biāo)記)。
然后事情并非還如此簡(jiǎn)單。.net程序目前有以下幾種處理器架構(gòu):
- Any CPU(處理器無(wú)關(guān)的純MSIL代碼)
- x64(僅64位處理器,又稱AMD64)
- x86(僅32位處理器)
- IA64(僅IA64位處理器)
Any CPU(處理器無(wú)關(guān)的純MSIL代碼)x64(僅64位處理器,又稱AMD64)x86(僅32位處理器)IA64(僅IA64位處理器)
之前所說(shuō)的“通?!?,即指Any CPU。這樣的程序在32位系統(tǒng)上以32位模式運(yùn)行,在64位系統(tǒng)上以64位模式運(yùn)行。這時(shí),而標(biāo)識(shí)為特定處理器的,只能在相應(yīng)的模式上運(yùn)行。
更詳細(xì)地,使用corflags工具可以看到以下信息:
- D:\Lib\x64\>corflags System.Data.SQLite.DLL
- Microsoft (R) .NET Framework CorFlags Conversion Tool. Version 2.0.50727.42
- Copyright (c) Microsoft Corporation. All rights reserved.
-
- Version : v2.0.50727
- CLR Header: 2.5
- PE : PE32+
- CorFlags : 24
- ILONLY : 0
- 32BIT : 0
- Signed : 1
D:\Lib\x64\>corflags System.Data.SQLite.DLLMicrosoft (R) .NET Framework CorFlags Conversion Tool. Version 2.0.50727.42Copyright (c) Microsoft Corporation. All rights reserved.Version : v2.0.50727CLR Header: 2.5PE : PE32+CorFlags : 24ILONLY : 032BIT : 0Signed : 1
- D:\Lib\x86\>corflags System.Data.SQLite.DLL
- Microsoft (R) .NET Framework CorFlags Conversion Tool. Version 2.0.50727.42
- Copyright (c) Microsoft Corporation. All rights reserved.
-
- Version : v2.0.50727
- CLR Header: 2.5
- PE : PE32
- CorFlags : 24
- ILONLY : 0
- 32BIT : 0
- Signed : 1
D:\Lib\x86\>corflags System.Data.SQLite.DLLMicrosoft (R) .NET Framework CorFlags Conversion Tool. Version 2.0.50727.42Copyright (c) Microsoft Corporation. All rights reserved.Version : v2.0.50727CLR Header: 2.5PE : PE32CorFlags : 24ILONLY : 032BIT : 0Signed : 1
ILONLY: 在映像中是否只包含了IL代碼。1表示純IL代碼,0表示包含了Native代碼。
32BIT: 即使是純IL代碼,在32位與64位環(huán)境中運(yùn)行還是有區(qū)別的。這個(gè)標(biāo)記用于區(qū)分x86與Any CPU。
PE: 32位為PE32,64位為PE32+。這個(gè)用于區(qū)分32位與64位映象。
- Any CPU:PE = PE32 and 32BIT = 0
- x86: PE = PE32 and 32BIT = 1
- x64: PE = PE32+ and 32BIT = 0
Any CPU:PE = PE32 and 32BIT = 0x86: PE = PE32 and 32BIT = 1x64: PE = PE32+ and 32BIT = 0
由于之前我們?cè)噲D在64位進(jìn)程上加載的32位DLL,其ILONLY=0,即包含平臺(tái)相關(guān)代碼,因此無(wú)法加載而拋出錯(cuò)誤。【注:通過(guò)現(xiàn)象推測(cè),需要跟蹤.net Framework代碼證實(shí)】
(2) 這個(gè)問(wèn)題通過(guò)上述剖析,已經(jīng)很清楚了。要說(shuō)明的是,如果想繞過(guò)64位DLL帶來(lái)的麻煩,將主程序也編譯成x86而不是Any CPU,也是可以在64位系統(tǒng)上運(yùn)行的。這時(shí),操作系統(tǒng)將啟用Wow模式啟動(dòng)這個(gè)進(jìn)程。
(3) 這個(gè)SQLite的DLL混合了IL和Native代碼,必須嚴(yán)格地對(duì)應(yīng)平臺(tái)調(diào)用。
(4) .net加載程序集的順序:
1、 在GAC(Global Assembly Cache)中搜索相應(yīng)版本的DLL;
2、 配置文件(web.config或app.config);
3、 應(yīng)用程序(.exe)當(dāng)前目錄下;
但事實(shí)上有可能比這個(gè)順序更為復(fù)雜,詳見(jiàn):
http://msdn.microsoft.com/en-us/library/aa720133.aspx 及參考文獻(xiàn)7。
之前,在我開(kāi)發(fā)環(huán)境中,由于本地GAC中已經(jīng)包含了正確的x64版本,所以能正確運(yùn)行。但復(fù)制到別人的64位機(jī)器上時(shí)就出問(wèn)題了。而有些XP機(jī)器上安裝了錯(cuò)誤的小版本,因此也出現(xiàn)加載失敗的現(xiàn)象。
最后,補(bǔ)充說(shuō)明一下GAC的位置:\Windows\assembly。使用“我的電腦”打開(kāi)這個(gè)目錄時(shí),看到的是所有.net全局程序集的列表,這是Windows特殊處理后的結(jié)果。只有通過(guò)命令行才能看到這個(gè)目錄中分成了GAC_32, GAC_64, GAC_MSIL等幾個(gè)子目錄。這幾個(gè)目錄正是前文所述不同處理器架構(gòu)的幾種DLL。
補(bǔ)充:
關(guān)于之前[c]的問(wèn)題的解決方案:
后來(lái)發(fā)現(xiàn),System.Data.SQLite.DLL版本1.0.66均能正常工作,但1.0.82在某些Windows XP上無(wú)法工作。由于官方網(wǎng)站對(duì)1.0.82的解釋大多關(guān)注在x86與x64的區(qū)別,我一度仍然以為是處理器架構(gòu)造成的。最后,通過(guò)depends工具查看發(fā)現(xiàn),1.0.82版的DLL引用了MSVCR90.DLL。而出問(wèn)題的機(jī)器上未安裝VC2008 Runtime,自然無(wú)法找到該DLL了。
解決的辦法有兩個(gè):使用1.0.66版,或在XP上安裝VC2008運(yùn)行包:
http://www.microsoft.com/zh-cn/download/details.aspx?id=29
由于之前對(duì)64位平臺(tái)應(yīng)用的理解有限,文中很多內(nèi)容通過(guò)實(shí)驗(yàn)推測(cè),有可能和Microsoft官方表述有不一致或者錯(cuò)誤的地方。歡迎大家指正! 參考文獻(xiàn):
1、
http://msdn.microsoft.com/en-us/magazine/dd727509.aspx 2、
http://blogs.msdn.com/b/gauravseth/archive/2006/03/07/545104.aspx 3、
http://blogs.msdn.com/b/junfeng/archive/2004/08/11/212555.aspx 4、
http://blogs.msdn.com/b/junfeng/archive/2004/09/12/228635.aspx 5、
http://blog.csdn.net/cstod/article/details/4887049 6、
http://stackoverflow.com/questions/6507675/gac-32bit-vs-64bit 7、
http://blog.csdn.net/wangjunhe/article/details/6692194