▲ 图文无关

做过.NET开发的,肯定会碰到一个报错就是“未能加载文件或程序集“xxx.dll”或它的某一个依赖项。找不到指定的模块。System.IO.FileNotFoundException 在 xxx”,这其实是一个比较模糊的报错,有以下几种可能:

  • xxx.dll缺失,这个可能性很小,也非常容易排除,就是在程序的相同目录下看是否存在这个目标dll
  • xxx.dll版本不对,通常回报BadImage错误,这通常是xxx.dll的编译版本和当前程序的编译版本不一致,比如目标平台是x86,但是xxx.dll是一个x64的版本。或者是xxx.dll编译的是.NET Framework 4.5,而当前程序是.NET Framework 4这显然会报错。
  • xxx.dll没问题,但是其依赖的其它dll缺失或报错,这个问题最难解决。

我最近刚碰到过这个问题,这里记录一下。

问题背景


有一个用C++编写的行情和交易接口,需要在.NET 环境下使用。于是使用C++ CLI技术将原生的C++接口封装为了可以直接在.NET中调用的名为dll,并编写了一个.NET 程序,这个程序在本机环境下运行一切正常,但是当把它复制到生产环境,启动就会报错:

能加载文件或程序集“XTPNET.dll”或它的某一个依赖项。找不到指定的模块。System.IO.FileNotFoundException   在 XTP.XTPDataFeed..ctor(String configName)

在确定了XTPNET.dll存在,并且该dll的编译版本和当前的exe程序的版本一致。

开始以为是VC的运行时环境缺失,还重新安装了VC_redist.x64.exe (Microsoft Visual C++ 2015-2022 Redistributable (x64) 14.44.35211),仍旧报同样的错误。

解决方法


根据以上的排除,那可能就是XTPNET.dll的依赖环境出了问题,但是既然VC运行时环境看起来没问题,那到底是哪里出问题了呢? 

由于.NET程序报错,在Windows事件中也会存在记录,于是我去Windows事件日志中去查看了一下。

错误应用程序名称: xxxr.exe,版本: 1.0.0.0,时间戳: 0x6a06c5d1
错误模块名称: KERNELBASE.dll,版本: 10.0.17763.1518,时间戳: 0xff301d3c
异常代码: 0xe0434352
错误偏移量: 0x00000000000396c9
错误进程 ID: 0x1fe4
错误应用程序启动时间: 0x01dce43b6f75cacc
错误应用程序路径: D:\test\xxx\xxxr.exe
错误模块路径: C:\Windows\System32\KERNELBASE.dll
报告 ID: d1f485c8-bdc4-43ac-9df2-86aea240c4ed
错误程序包全名: 
错误程序包相对应用程序 ID:

没有给出特别有用的信息。

这里的最重要的问题是如何确定缺失的dll是哪个?在报错信息里并没有给出明确的答案。

Dependencies这个软件能够查看C++程序的依赖相,于是把运行程序DependenciesGui.exe,把XTPNET.dll打开:

▲ 用Dependencies查看缺失的依赖项

果不其然,在实盘环境,缺少三个dll,分别是:VCRUNTIME140D.dll、ucrtbased.dll和MSVCP140D.dll。

可以看到,这几个带有D的表示是调试版的dll,它包含有更多的调试信息。看到这里就真相大白了,之前的错误在于,在.NET的程序中引用了Debug版本的XTPNET.dll这个包装类。而在实盘环境中,并没有Debug版本的VC运行时,本机由于安装了Visual Studio这类开发工具,所以自带有Debug版本的运行时。这就是为什么在本机能够正确运行,而在生产环境报错的原因。

解决方法也很简单,就是在Release环境下编译XTPNET.dll,然后在.NET程序中重新编译发布,最后再用DependenciesGui.exe查看XTPNET.dll,这下全部正确了。

▲ 现在全部正常

总结


在.NET引用第三方dll时,有时候会出现dll无法加载的情况,通常可能是这个第三方的dll缺失,版本不对(通常报BadImage错误),或者是dll的依赖性没有正确加载。以前通常靠猜,比如缺失了vc运行时环境之类的,现在有了Dependencies这类工具,可以非常方便的查看到底缺失了什么依赖项,这对于快速精准定位问题非常关键。本文分享了一个由于在开发环境错误使用了Debug版本的dll,编译发布到正式环境后,由于正式环境缺失Debug版本的VC运行时而导致dll无法加载从而报错的问题。这也从另外一方面提醒了,在正式环境下通常都需要编译成Release环境的一个隐藏问题。