【Windows下DLL查找顺序 】
一、写作初衷
在Windows下单个DLL可能存在多个不同的版本,若不特别指定DLL的绝对路径或使用其他手段指定,在应用程序加载DLL时可能会查找到错误的版本,进而引出各种莫名其妙的问题。本文主要考虑以下两个方面:
a. 参考MSDN,给出Windows下DLL查找顺序
b. 简单使用ProcessMonitor来验证DLL查找顺序
二、DLL查找顺序
(本部分多数内容是参考MSDN上的Dynamic-Link Library Search Order一文,链接如下http://msdn.microsoft.com/en-us/library/ms682586(v=vs.85).aspx。多数为翻译,有部分内容修改。本文仅关注桌面应用程序的查找顺序,对于Windows Store apps请参考MSDN原文。)
1. DLL查找路径基础
应用程序可以通过以下方式控制一个DLL的加载路径:使用全路径加载、使用DLL重定向、使用manifest文件。如果上述三种方式均未指定,系统查找DLL的顺序将按照本部分描述的顺序进行。
对于以下两种情况的DLL,系统将不会查找,而是直接加载:
a. 对于已经加载到内存中的同名DLL,系统使用已经加载的DLL,并且忽略待加载DLL的路径。(注意对某个进程而言,系统已经加载的DLL一定是唯一的存在于某个目录下。)
b. 如果该DLL存在于某个Windows版本的已知DLL列表(unkown DLL)中,系统使用已知DLL的拷贝(包括已知DLL的依赖项)。已知DLL列表可以从如下注册表项看到:HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs。
这里有个比较坑的地方,对于有依赖项的DLL(即使使用全路径指定DLL位置),系统查找其所依赖DLL的方法是按照实际的模块名称来的,因此如果加载的DLL不在系统查找顺序目录下,那么动态加载该DLL(LoadLibrary)会返回一个"找不到模块"的错误。
2. 系统标准DLL查找顺序
系统使用的标准DLL查找顺序依赖于是否设置了"安全DLL查找模式"(safe DLL search mode)。"安全DLL查找模式"会将用户当前目录置于查找顺序的后边。
"安全DLL查找模式"默认是启用的,禁用的话,可以将注册表项HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode设为0。调用SetDllDirectory函数可以禁用"安全DLL查找模式",并修改DLL查找顺序。
Windows XP下,"安全DLL查找模式"默认是禁用的,需要启用该项的话,在注册表中新建一个SafeDllSearchMode子项,并赋值为1即可。"安全DLL查找模式"从Windows XP SP2开始,默认是启用的。
启用"安全DLL查找模式"时,查找顺序如下:
a. 应用程序所在目录;
b. 系统目录。GetSystemDirectory返回的目录,通常是系统盘\Windows\System32;
c. 16位系统目录。该项只是为了向前兼容的处理,可以不考虑;
d. Windows目录。GetWindowsDirectory返回的目录,通常是系统盘\Windows;
e. 当前目录。GetCurrentDirectory返回的目录;
f. 环境变量PATH中所有目录。
如果"安全DLL查找模式"被禁用,查找顺序如下:
a. 应用程序所在目录;
b. 当前目录。GetCurrentDirectory返回的目录;
c. 系统目录。GetSystemDirectory返回的目录,通常是系统盘\Windows\System32;
d. 16位系统目录。该项只是为了向前兼容的处理,可以不考虑;
e. Windows目录。GetWindowsDirectory返回的目录,通常是系统盘\Windows;
f. 环境变量PATH中所有目录。
3. 修改系统DLL查找顺序
系统使用的标准DLL查找顺序可以通过以下两种方式调整:
3.1 使用LOAD_WITH_ALTERED_SEARCH_PATH标志调用LoadLibraryEx函数;
这种方式调用LoadLibraryEx函数,需要设置lpFileName参数(绝对路径)。与标准查找策略不同的是,使用LOAD_WITH_ALTERED_SEARCH_PATH标志调用LoadLibraryEx函数的DLL查找顺序将"查找应用程序所在目录"修改为lpFileName指定的目录。
3.2 调用SetDllDirectory函数。
注意:SetDllDirectory函数在Windows XP SP1开始支持的。
函数SetDllDirectory在调用参数lpPathName是一个路径时,可支持修改DLL搜索路径。修改之后的搜索顺序如下:
a. 应用程序所在目录;
b. 函数SetDllDirectory参数lpPathName给定的目录;
c. 系统目录。GetSystemDirectory返回的目录,通常是系统盘\Windows\System32;
d. 16位系统目录。该项只是为了向前兼容的处理,可以不考虑;
e. Windows目录。GetWindowsDirectory返回的目录,通常是系统盘\Windows;
f. 环境变量PATH中所有目录。
如果lpPathName参数为空字符串,这样就会把当前目录从DLL搜索路径中去掉。
如果用NULL参数调用SetDllDirectory函数,可以恢复按照系统注册表的"安全DLL查找模式"来查找DLL。
当然win8或者windows server 2012提供更多的可定制方法,这个可以参考MSDN上介绍。比如:SetDefaultDllDirectories、 AddDllDirectory、RemoveDllDirectory。
三、ProcessMonitor使用
ProcessMonitor可以从http://technet.microsoft.com/en-us/sysinternals/bb896645下载。
官网给出的介绍资料如下:
Process Monitor一款系统进程监视软件,总体来说,Process Monitor相当于Filemon+Regmon,其中的Filemon专门用来监视系统 中的任何文件操作过程,而Regmon用来监视注册表的读写操作过程。 有了Process Monitor,使用者就可以对系统中的任何文件和 注册表操作同时进行监视和记录,通过注册表和文件读写的变化, 对于帮助诊断系统故障或是发现恶意软件、病毒或木马来说,非常 有用。
软件下载之后,解压就可以直接运行。Process Monitor默认会启用针对真当前系统的"File System"、"Registry"、"Process"的所有操作的记录,类似wireshark网卡抓包软件,只是抓取的信息不同。如果仅关心某个进程的事件,可以在工具栏或者菜单中选择Filter-Filiter,弹出下图所示对话框:

举个例子,我们只关心进程名为"qwe.exe"的相关操作,可以做如下处理:从第一个下拉列表框中选择ProcessName,将进程名字填入输入框,然后点击"Add"按钮,点击 "OK"(如果已经开始监测,可以直接点Apply按钮)。
其他关于Process Monitor的使用可以参考帮助文档,介绍整体比较详细,这里不做赘述。
四、验证Windows下DLL加载顺序是否正确
那么我们可以考虑在win7下验证下DLL加载顺序,想法很简单,随便写一个系统中不存在的DLL,用LoadLibray动态加载下看看,用Process Monitor记录当前进程的操作记录。
代码如下:

1 #include <windows.h>
2 #include <iostream>
3
4 int main(int argc, char ** argv)
5 {
6 using std::cout;
7 using std::endl;
8
9 // 随便设置一个不存在的dll名
10 HMODULE hMod = LoadLibrary("123.dll");
11
12 if (NULL != hMod)
13 FreeLibrary(hMod);
14
15 cout << "LoadLibrary Test" << endl;
16
17 return 0;
18 }

使用MinGW编译之后,在命令行下运行该程序。Process Monitor输出如下信息(这里仅截取关于123.dll加载的信息):

可以看到这里搜索的路径跟系统标准DLL搜索路径时一致的。我的环境变量Path从D:\software\Subversion\Apache2\bin开始到D:\software\tortoiseGit\bin结束。
五、总结
本文主要介绍了Windows下DLL查找顺序,理清这个查找顺序基本可以找到LoadLibrary返回NULL,提示"找不到指定模块"的原因。通常可以考虑一下几点:
a. 待加载DLL文件是正确的、完整的吗? 是否有损坏? 加载全路径是否正确?
b. 待加载DLL的依赖项是否存在?(Dependency Walker可以查看依赖性)这些依赖项在是否都在系统可以查找到的目录下?
另外,本文简单介绍了使用Process Monitor分析程序运行对文件系统、注册表、进程/线程的操作,工具不错,有待开发。
还有一个问题,需要特别注意的是当前目录,因为很多情况下当前目录会因为SetCurrentDirectory或者OpenFile的操作而改变,某些情况下会对加载DLL造成意外的麻烦。
注:版权所有,请勿用于商业用途,转载请注明原文地址。本人保留所有权利
【Windows下DLL查找顺序 】的更多相关文章
- Windows下DLL查找顺序
目录 第1章说明 2 1.1 查找顺序 2 1.1.1 检查DllCharacteristics字段 3 1.1.2 读取manifset资源 3 1.1.3 读取manifs ...
- Python调用windows下DLL详解
Python调用windows下DLL详解 - ctypes库的使用 2014年09月05日 16:05:44 阅读数:6942 在python中某些时候需要C做效率上的补充,在实际应用中,需要做部分 ...
- Windows下node-gyp查找VS安装路径简单解析
node-gyp的作用我已经不想赘述了,这里给一个我之前文章的链接:cnblogs看这里,知乎看这里.本文主要从源码入手,介绍node-gyp查找VisualStudio的过程 为了方便我们研究nod ...
- Python调用windows下DLL详解 - ctypes库的使用
在python中某些时候需要C做效率上的补充,在实际应用中,需要做部分数据的交互.使用python中的ctypes模块可以很方便的调用windows的dll(也包括linux下的so等文件),下面将详 ...
- python多继承下的查找顺序-MRO原则演变与C3算法
在python历史版本中的演变史 python2.2之前: MRO原则: 只有经典类,遵循深度优先(从左到右)原则, 存在的问题:在有重叠的多继承中,违背重写可用原则 解决办法是再设计类的时候不要设计 ...
- vim7.4版本在windows下的配置文件及所在位置
1.vim在windows下默认首先会查找"_vimrc"文件,如果没有则会找".vimrc".造成这个原因是windows早期不支持以点开头的文件及目录.2. ...
- windows下rundll32介绍
最近看书介绍rundll32可以加载dll文件并执行其中导出函数,在MSDN中我们可以看到绍http://support.microsoft.com/kb/164787/zh-cn rundll32调 ...
- Windows下静态库与动态库的创建与使用
Windows下静态库与动态库的创建与使用 学习内容:本博客介绍了Windows下使用Visual C++ 6.0制作与使用静态库与动态库的方法. --------CONTENTS-------- 一 ...
- Linux的.a、.so和.o文件 windows下obj,lib,dll,exe的关系 动态库内存管理 动态链接库搜索顺序 符号解析和绑定 strlen函数的汇编实现分析
Linux的.a..so和.o文件 - chlele0105的专栏 - CSDN博客 https://blog.csdn.net/chlele0105/article/details/23691147 ...
随机推荐
- Android自己定义button实现长按功能
Android自己定义button实现长按功能 通过自己定义BUTTON,写一个LongTouchBtn类,在按下的时候运行onTouchEvent事件,通过这个事件使用回调函数来实现长按功能! XM ...
- Java线程总结(转)
作者的blog:(http://blog.matrix.org.cn/page/Kaizen) 首先要理解线程首先须要了解一些主要的东西,我们如今所使用的大多数操作系统都属于多任务,分时操作系统.正是 ...
- iOS开发-项目的完整重命名方法,图文教程。
前言:在IOS开发中,有时候想改一下项目的名字,都会遇到很多麻烦.直接改项目名吧,XCODE又不会帮你改所有的名字.总是有很多文件.文件夹或者是项目设置的项.而且都是不能随便改的,有时候改着改着,编译 ...
- spring配置,spring中的bean 的id不能相同
lib下加入包 spring.jar commons-logging.jar src下添加 applicationContext.xml <?xml version="1.0" ...
- 编写C函数的技术-《lua程序设计》 27章 学习
1.数组操作 void lua_rawgeti(lua_State * L ,int index,int key) void lua_rewseti(lua_State * L,int index,i ...
- MySQL学习总结(三)索引
补充一下,上一章节中约束的一点东西.我们在为约束设置名称的时候(标识符)推荐写法“约束缩写_字段名”,这样让人看起来就会很清晰.例如:FK_Deptno,我们通过索引的名字就可以大概知道这是一个设置的 ...
- JAVA方法传递参数:传值?传引用?
先来看下面这三段代码: //Example1: public class Example1 { static void check(int a) { a++; } public static void ...
- Angularjs学习笔记10_directive3
1. restrict M 使用模板 A 属性扩展 2. template,templateUrl,$templateCache 模板缓存 //注射器加载完所有模块时,此方法执行一 ...
- Visual Studio - 创建和使用动态库
一.VS2013 创建动态库 1.1 新建项目 1.2.在Win32应用程序向导对话框上勾选“DLL”和“空项目”复选框,点完成 1.3 .添加对应的.C文件和.h文件 1.4 在.h文件中添加如下代 ...
- 生产环境中 Ngx_lua 使用技巧和应用的范例
生产环境中 Ngx_lua 使用技巧和应用的范例 时间 -- :: 51CTO技术博客 原文 http://rfyiamcool.blog.51cto.com/1030776/1252501 主题 L ...