进行插件式编程的时候,经常性地弹出这么个东西找到的程序集清单定义与程序集引用不匹配。 (异常来自 HRESULT:0x80131040),往往这种问题特别难以解决,搞定了一个还要出另外一个。得研究一下怎么处理。
引用不匹配
这里提示需要加载一个4.2.0.0版本的dll,我先看看文件夹下面有没有对应的dll,查看文件dll的详细信息。
这个版本号4.6.27818.1和4.2.0.0也差的有点太远了吧,是这个问题?其实不是的,这个地方显示的版本和程序集的版本不是一回事。
程序集版本
.NET程序有很多版本的说法,官方对这个有解释,通过文件管理器获得的版本是AssemblyFileVersion
,而程序集加载器定位的版本使用的是AssemblyVersion
,这两个东西完全不是一回事。通过右键,我们看不到AssemblyVersion,比较简单的方式,可以通过Powershell脚本来查看程序集版本。
ls *.dll -r |
ForEach-Object {
try {
$_ | Add-Member NoteProperty FileVersion ($_.VersionInfo.FileVersion)
$_ | Add-Member NoteProperty AssemblyVersion (
[Reflection.AssemblyName]::GetAssemblyName($_.FullName).Version
)
} catch {}
$_
} |
Select-Object Name,FileVersion,AssemblyVersion
可以看到,我这边的程序集是4.2.0.1版本的,不是4.2.0.0版本的,因此,程序集不能正常加载。
我自己的项目是使用nuget进行包管理的,引用的包的版本号是4.5.3(又多一个版本...),程序集版本是4.2.0.1。我找遍了整个项目,都没有找到我dll项目中关于4.2.0.0版本的引用,苦思良久,打盹的时候忽然想起来,是不是那个exe的问题?
绑定重定向
我的主exe程序是使用.NET Framework 4.6.1进行编译,然后单独编译dll作为插件放入一个文件夹,由exe程序进行加载。那有可能是exe引用了4.2.0.0这个版本,或者是其他dll插件引用了这个版本,造成版本不兼容。
这种情况可以有很多种解决方案,这篇文章写的非常详细,推荐读一读。而我这里使用了最简单也是作者比较推荐的办法,程序集引用的绑定重定向。
使用这个方法有一个前提,你需要完全了解其他程序引用这个程序版本的时候不会出问题,一般小版本号的变动,是相对比较安全的。
一直以来,我写.NET Framework程序貌似就很少有这种问题,是因为微软会自动给程序设置重定向,我们可以通过在项目上右键,点选自动生成绑定重定向。
之后,会出现一个app.config
文件,它会在生成程序的时候,变成程序名称.dll.config
的形式,里面大概是这个样子的:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks.Extensions" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.2.0.1" newVersion="4.2.0.1" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
可以发现,这个东西对我们引用的版本内容进行了限制,强制某个范围内的版本重定向为一个固定的版本号。不过,虽然dll中已经有这个内容,但是exe不会理会它的,它只会看自己主程序的.exe.config文件。所以我们需要做的,就是把这一个部分内容搬到主程序的.exe.config
中。
修改后重新运行,然后有又提示找不到System.Buffers.dll了,如法炮制,问题解决。
Fusion Log
有时候,通过这个方法找下去,处理完了,也不一定能够解决,因为这个错误提示的是“未能加载文件或程序集或它的某一个依赖项。”,也可能是某个引用的依赖项出了问题,这样就不是很好找了。
好在.NET提供了一个程序集绑定日志的工具,可以帮助我们查看绑定的问题。一般情况下,这个东西是关闭的,系统会提示:
警告: 程序集绑定日志记录被关闭。
要启用程序集绑定失败日志记录,请将注册表值 [HKLMSoftwareMicrosoftFusion!EnableLog] (DWORD)设置为 1。
注意: 会有一些与程序集绑定失败日志记录关联的性能损失。
要关闭此功能,请移除注册表值 [HKLMSoftwareMicrosoftFusion!EnableLog]。
调试的话,可以打开这个注册表键值。或者简单点,直接使用Fuslogvw.exe
(程序集绑定日志查看器),用管理员账号启动,在设置中设置好记录的日志信息,然后就可以记录了,详细的使用方法,见这里。就能看到详细的信息了,可以帮助我们深入分析内部绑定的问题。(用完记得关,有性能损失。)
=== 预绑定状态信息 ===
日志: DisplayName = System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
(Fully-specified)
日志: Appbase = file:///C:/Temp/360zip$Temp/360$0/
日志: 初始 PrivatePath = NULL
调用程序集: DependencyWalker, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null。
===
日志: 此绑定从 default 加载上下文开始。
日志: 未找到应用程序配置文件。
日志: 使用主机配置文件:
日志: 使用 C:WindowsMicrosoft.NETFrameworkv4.0.30319configmachine.config 的计算机配置文件。
日志: 策略后引用: System.Threading.Tasks.Extensions, Version=4.2.0.1, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
日志: 相同的绑定已出现过,因 hr = 0x80070002 而失败。
Dependency Walker
说到dll的引用,就有必要提一提非常有名的一个工具Dependency Walker,它可以查看PE文件的引用情况,但是这个程序很久没有更新了,对.NET很不友好。我找了一下,发现几个替代:
这个工具可以认为是Dependency Walker的升级版,可以查看PE文件的引用信息,对.NET也可以支持,不过看的信息太少了,也无法显示缺失的情况。
这个工具是专门为.NET设计的,可以查看程序集的引用情况,我删除了我插件的几个dll,然后看就是这个样子,一目了然,版本号也非常清楚。
通过这些工具,可以帮我们找一找到底是dll的问题,也许会对“试图加载格式不正确的程序。”、“引用不匹配。”等引用相关问题有所帮助。
这些工具处理的大多是静态引用,对于使用动态引用的,一般是不支持的。
总结
插件式编程极大增强了程序的拓展能力,不过,在处理程序集引用的时候,需要非常小心不同插件带来的引用问题。程序设计的时候,可以使用不同文件夹隔离不同的插件dll,并通过一些技巧来加载(可以查看之前的文章)和隔离,这样,就可以避免出现不匹配的错误了。