最近在编写.net应用程序时,发现某些平台下无法加载SQLite DLL的问题。
症状表现为:
a. 本地Windows 7/8 64bit开发环境完全正常。
b. 某些Windows 7 64bit用户的计算机无法加载System.Data.SQLite.DLL。
c. 极个别Windows XP的计算机无法加载该DLL。
无法加载DLL时,均报BadImageFormatException异常,甚至直接被Windows关闭而无法采集异常信息。奇怪的是在当前目录包含了这个DLL文件,所以理应能成功加载才是。
由于各种场景的组合在一起,一时很难判断问题出于何处。起初笔者努力尝试各种方法试图在本机重现,也均告失败。在偶然的机会,笔者删除了本机所有与SQLite相关的组件,问题成功重现。
绝望之中燃起了一点希望。我发现自己曾经安装了三个与SQLite相关的组件:
class="java" name="code">SQLite-1.0.66.0-setup.exesqlite-netFx35-setup-bundle-x64--1.0.82.0.exesqlite-netFx35-setup-bundle-x86--1.0.82.0.exe
反复多次后确认了以下内容:
SQLite-1.0.66.0-setup.exe - 无效果sqlite-netFx35-setup-bundle-x86--1.0.82.0.exe - 无效果sqlite-netFx35-setup-bundle-x64--1.0.82.0.exe - 成功
看来,用户系统上没有sqlite-netFx35-setup-bundle-x64--1.0.82.0.exe,所以导致运行失败。经仔细检查,这个包在系统GAC中安装了64位DLL,而之前我发布的目录中包含的是32位DLL文件。一切真相大白,将64位版本与32位版本分开发布,问题解决!
到这里,本该可以松口气了,但我心中仍然充满疑惑:
(1) 在64位系统上,我的.net应用到底算是32位的还是64位的?
(2) 32位应用程序照理可以在64位系统上正常运行,为什么当前目录的DLL没有加载成功?
(3) 为何以前调用的第三方DLL从没有发生32/64位的问题,只有这个DLL如此特别?
(4) .net的加载DLL的顺序是什么,到底以哪个优先?
经过一番认真的Google,上述迷团一一破解。
(1) 在64位系统上,.net程序是通常以64位程序运行的。Windows不会启用Wow。这可以从任务管理器中看到(在64位系统中运行的32位程序将以*32或“32位”标记)。
然后事情并非还如此简单。.net程序目前有以下几种处理器架构:
Any CPU(处理器无关的纯MSIL代码)x64(仅64位处理器,又称AMD64)x86(仅32位处理器)IA64(仅IA64位处理器)
之前所说的“通常”,即指Any CPU。这样的程序在32位系统上以32位模式运行,在64位系统上以64位模式运行。这时,而标识为特定处理器的,只能在相应的模式上运行。
更详细地,使用corflags工具可以看到以下信息:
D:\Lib\x64\>corflags System.Data.SQLite.DLL Microsoft (R) .NETFramework 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.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
ILONLY: 在映像中是否只包含了IL代码。1表示纯IL代码,0表示包含了Native代码。
32BIT: 即使是纯IL代码,在32位与64位环境中运行还是有区别的。这个标记用于区分x86与Any CPU。
PE: 32位为PE32,64位为PE32+。这个用于区分32位与64位映象。
Any CPU:PE = PE32 and 32BIT = 0x86: PE = PE32 and 32BIT = 1x64: PE = PE32+ and 32BIT = 0
由于之前我们试图在64位进程上加载的32位DLL,其ILONLY=0,即包含平台相关代码,因此无法加载而抛出错误。【注:通过现象推测,需要跟踪.net Framework代码证实】
(2) 这个问题通过上述剖析,已经很清楚了。要说明的是,如果想绕过64位DLL带来的麻烦,将主程序也编译成x86而不是Any CPU,也是可以在64位系统上运行的。这时,操作系统将启用Wow模式启动这个进程。
(3) 这个SQLite的DLL混合了IL和Native代码,必须严格地对应平台调用。
(4) .net加载程序集的顺序:
1、 在GAC(Global Assembly Cache)中搜索相应版本的DLL;
2、 配置文件(web.config或app.config);
3、 应用程序(.exe)当前目录下;
但事实上有可能比这个顺序更为复杂,详见:
/en-us/library/aa73.aspx 及参考文献7。
之前,在我开发环境中,由于本地GAC中已经包含了正确的x64版本,所以能正确运行。但复制到别人的64位机器上时就出问题了。而有些XP机器上安装了错误的小版本,因此也出现加载失败的现象。
最后,补充说明一下GAC的位置:\Windows\assembly。使用“我的电脑”打开这个目录时,看到的是所有.net全局程序集的列表,这是Windows特殊处理后的结果。只有通过命令行才能看到这个目录中分成了GAC_32, GAC_64, GAC_MSIL等几个子目录。这几个目录正是前文所述不同处理器架构的几种DLL。
补充:
关于之前[c]的问题的解决方案:
后来发现,System.Data.SQLite.DLL版本1.0.66均能正常工作,但1.0.82在某些Windows XP上无法工作。由于官方网站对1.0.82的解释大多关注在x86与x64的区别,我一度仍然以为是处理器架构造成的。最后,通过depends工具查看发现,1.0.82版的DLL引用了MSVCR90.DLL。而出问题的机器上未安装VC Runtime,自然无法找到该DLL了。
解决的办法有两个:使用1.0.66版,或在XP上安装VC运行包:
/zh-cn/download/details.aspx?id=29
由于之前对64位平台应用的理解有限,文中很多内容通过实验推测,有可能和Microsoft官方表述有不一致或者错误的地方。欢迎大家指正!
参考文献:
1、/en-us/magazine/dd727509.aspx
2、/b/gauravseth/archive//03/07/545104.aspx
3、/b/junfeng/archive//08/11/212555.aspx
4、/b/junfeng/archive//09/12/228635.aspx
5、/cstod/article/details/4887049
6、/questions/6507675/gac-32bit-vs-64bit
7、/wangjunhe/article/details/6692194