分类: WINDOWS
2011-07-01 13:14:48
Learn DLL
1. DLL 声明
通常,在设计DLL时头文件中进行如下声明:
(1)C或C++编程
#ifndef XXX_LIBAPI
#define XXX_LIBAPI __declspec(dllimport)
#endif
XXX_LIBAPI 函数声明
(2)C/C++混合编程
#ifndef XXX_LIBAPI
#define XXX_LIBAPI extern “C” __declspec(dllimport)
#endif
XXX_LIBAPI 函数声明
使用extern “C” 的目的是为了防止C++编译器对函数名进行改编,以免C程序调用时无法找到需要的函数。
2. DLL定义
DLL定义文件中通常有如下形式:
(1)C或C++编程
#define XXX_LIBAPI __declspec(dllexport)
#include “xxxxLib.h” //对应该DLL的头文件
…
函数定义
(2)C/C++混合编程
#define XXX_LIBAPI extern “C” __declspec(dllexport)
#include “xxxxLib.h” //对应该DLL的头文件
…
函数定义
上面的#define 和 #include顺序不能颠倒。
3. 为不同编译器创建DLL
在前面都是假设使用相同的编译器,这不会出现问题。如果生成的DLL供其它编译器使用,由于不同编译器对函数名字的改编规则不同,而通常情况下Dll在生成时名字就决定了,这会导致链接程序找不到需要的函数。解决此问题有两种办法:
(1) 使用def文件
def文件称为模块定义文件,在该文件中定义EXPORTS 节,在该节下指定DLL中导出函数的名字,这样在省城DLL时就会生成def定义的名字,从而别的编译器生成的程序也可以引用该DLL。
(2) 使用 #pragma
#pragma comment(linker, "/export:FuncName1=FuncName1_Mangle")
FuncName1 通常是函数声明时的名字,FuncName1_Mangle 编译器改编后的名字;使用这种方法可以使这两个名字等价。但是这种方法不易使用,因为你必须了解编译器的名字改编规则,推荐使用第一种方法
4. DLL的加载
DLL有两种加载方式:隐式加载和显式加载
(1) 隐式加载
隐式加载通常是在程序中使用#include 指令包含DLL相关的头文件,然后引用DLL中导出的对象,比较简单,不多说。
(2) 显式加载
显示加载是在程序运行时动态加载,这是通过以下两个函数之一实现的:
LoadLibrary(PCTSTR pszDLLPathName)
HMODULE LoadLibraryEx(
PCTSTR pszDLLPathName,
HANDLE hFile,
DWORD dwFlags)
其中,pszDLLPathName 是DLL模块的名字;
hFile保留,设置为NULL
dwFlags为0或者以下值的位组合
DONT_RESOLVE_DLL_REFERENCES
LOAD_LIBRARY_AS_DATAFILE
LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE
LOAD_LIBRARY_AS_IMAGE_RESOURCE
LOAD_WITH_ALTERED_SEARCH_PATH
LOAD_IGNORE_CODE_AUTHZ_LEVEL
对于这些标志,其解释如下:
l DONT_RESOLVE_DLL_REFERENCES
该标志告诉系统把DLL模块映射到调用进程的地址空间,但不执行DllMain,也不进行其它DLL的加载。
通常情况下,系统会把DLL映射到调用进程的地址空间,然后执行DllMain对DLL模块进行初始化;而且系统还会检查该DLL是否引用了其它DLL,如果有则加载引用的DLL。
因此,使用该标志时程序很有可能会由于使用未初始化的结构而引发异常,所以最好不要使用该标志。
l LOAD_LIBRARY_AS_DATAFILE
该标志与DONT_RESOLVE_DLL_REFERENCES类似,它仅把DLL作为数据文件映射到调用进程的地址空间,不作执行代码的准备。
当DLL仅包含资源时,使用该标志;如果程序使用的资源在exe文件中,则必须使用该标志。
l LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE
该标志与LOAD_LIBRARY_AS_DATAFILE相似,区别在于程序使用该模块时不允许其它程序修改模块,提供更高的安全性
l LOAD_LIBRARY_AS_IMAGE_RESOURCE
该标志与LOAD_LIBRARY_AS_DATAFILE 相似,区别在于系统在加载该DLL模块时使用相对虚地址(RVA),这些地址可以直接使用而无需根据DLL在内存中的加载地址进行转换,它使得解析DLL的PE节的时候非常方便
l LOAD_WITH_ALTERED_SEARCH_PATH
该标志用于改变loadLibraryEx定位DLL时的搜索算法,关于DLL的定位搜索算法在后面说明。
l LOAD_IGNORE_CODE_AUTHZ_LEVEL
该标志用于关闭windows的WinSafer校验。
5. DLL模块的定位搜索
一般情况下,DLL模块的定位算法或搜索顺序如下:
(1)可执行文件(调用DLL)所在目录
(2)Windows系统目录;该目录可以通过函数GetSystemDirectory获得
(3)16位系统目录,即windows目录下的system子目录
(4)Windows目录;该目录可以通过函数GetWindowsDirectory获得
(5)进程的当前目录
(6)PATH环境变量中列出的目录
如果在以上目录中未找到需要的DLL文件,则报错。进程的当前目录在Windows目录之后搜索,这是在Win XP SP2之后改变的,是为了安全原因。通常情况下,加载器还会查看DLL模块的imports节,以确定它所引用的DLL并把这些DLL也加载进来。在前面提到动态显式加载DLL时,标志LOAD_WITH_ALTERED_SEARCH_PATH可以改变LoadLibraryEx的搜索顺序,根据传给该函数的pszDLLPathName参数值,它以三种方式搜索:
(1) pszDLLPathName不包含路径(不包含‘\’)
采用前面所述的标准搜索算法
(2) pszDLLPathName含有‘\’字符
在这种情况下,先判断是相对路径还是绝对路径,
l 如果是绝对路径或者网络路径,例如C:\Apps\Libraries\MyLibrary.dll 或,系统直接加载该DLL,如果找不到,则返回NULL,错误类型为ERROR_MOD_NOT_FOUND
l 如果是相对路径,则首先把该相对路径与以下路径进行连接,然后加载
a. 进程当前目录
b. windows系统目录
c. 16位系统目录
d. windows目录
e. PATH的列表目录
值得注意的是,如果该参数中含有“.”或“..”,它仍然会不作任何修改进行上述连接
(3) 使用了函数SetDllDirectory,此时搜索顺序变为
a. 程序所在目录
b. SetDllDirectory设置的目录
c. windows系统目录
d. 16位系统目录
e. windows目录
f. PATH的列表目录
使用SetDllDirectory时,参数如果为“”,则不搜索进程当前目录;如果为NULL,恢复标准搜索。