Windows下Dll的加载方式及错误处理

Dll可以说是Windows下面相当常用的文件类型了。从功能上来讲,它相当于一个可执行程序,它是经过编译和链接而生成的,里面封装好了各种导出给用户的函数或者类。

这篇文章不会讲dll里面是什么样的格式,也不会介绍如何写一个dll。我主要想分享一下,dll加载的几种方式,以及加载dll失败的几个原因。

1. dll加载方式

dll加载方式大致可以分为3类:静态加载、动态加载和延迟加载。

所谓静态加载是说,dll的加载发生在程序main函数启动前。这个加载行为是由crt做的。你所需要做的所有事情就是把编译dll时生成的lib链接上就可以了。如果你的dll路径中缺少这个dll,那么你将会得到一个类似于下面这样的错误:

当你点了确定之后,程序将直接退出,你没有办法在你的main函数中处理它。

动态加载是说,使用LoadLibrary或者LoadLibraryEx来加载一个dll。当dll加载成功时,你会得到一个非空的HMODULE。接下来,你可以使用GetProcAddress来获取这个HMODULE中的导出接口了。你可以理解成,静态链接的lib其实是帮我们在很早的时候就完成了这些事情。使用LoadLibrary有一些细节需要注意。首先是路径,它会在一些特定的路径寻找dll,如果没有找到则会报错。当成功加载了dll后,crt会初始化dll中的全局变量,并且HMODULE的引用计数会+1。如果LoadLibrary多次,在绝大部分的情况下,第一次之后的LoadLibrary会返回第一次的HMODULE,并且增加引用计数。所以,LoadLibrary要和FreeLibrary成对出现,如果你想要释放一个dll,load了多少次,就要free多少次。

延迟加载是当dll需要时,才会被加载。为了使用一个延迟加载的dll,我们在生成exe的时候,需要更改链接器的一些设置,表示我们要用哪些延迟加载的dll:

此外,我们再链接想delayimp.lib,就可以延迟加载一个dll了。

我们可以将<delayimp.h>中的__pfnDliNotifyHook2指定为我们自己的回调函数,更改默认的行为,当一个dll需要延迟加载时,将会走到这里。我们可以在这里面依据自己的需求,LoadLibrary获取一个HMODULE并返回。

2. 加载dll失败的可能原因

当使用静态加载dll时,加载dll失败默认会得到一个错误提示框。如果使用LoadLibrary加载dll失败,那么需要用GetLastError来获取错误信息,而且错误信息可能没那么直观。以下是常见的一些失败原因。

1) 路径错误。如果你尝试LoadLibrary一个不存在的文件(它不存在于系统文件夹、PATH路径、当前exe文件夹、通过SetDllDirectory设置的文件夹)中,那么系统认为无法找到此模块。一般而言,我们都会把自己的dll放在exe同一级目录,方便查找。

2) 未能加载dll静态依赖。之前说过,dll功能和exe类似,所以它也有自己的dll依赖。我们可以用VS的dumpbin [dllname] /dependents来查看某个dll依赖哪些其它dll:

可以看到,我电脑上的assimp32.dll依赖kernel32.dll,msvcr90.dll, msvcp90.dll。如果用户机器上没有其中一个dll,那么LoadLibrary返回的是NULL,并且GetLastError会得到ERROR_MOD_NOT_FOUND。

3) DllMain中返回FALSE

当加载一个dll时,如果dll有DllMain函数,它将会在加载后被调用。Windows会给这个函数一个DLL_PROCESS_ATTACH通知,表示这个dll被进程依附了。函数返回BOOL表示初始化是否成功,如果为FALSE,系统将认为初始化不成功,并且LoadLibrary将返回FALSE。

以上变是常见的加载失败的部分原因。当然还有一些原因并没有在其中说明,比如二进制不兼容等。上面的几种情况应该可以应付大部分加载场景了,希望能帮助大家理解dll的加载。

编辑于 2019-02-21

文章被以下专栏收录

    本专栏收集高品质游戏开发方面的原创文章,并不局限于技术文章,尤其欢迎美术类策划类管理类文章。投资市场营销类暂不接受。欢迎投稿但是否采纳全看运气。如无特别声明,本专栏作品采用知识共享署名 4.0 国际许可协议进行许可。