Chinaunix首页 | 论坛 | 博客
  • 博客访问: 291672
  • 博文数量: 63
  • 博客积分: 814
  • 博客等级: 军士长
  • 技术积分: 700
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-09 15:46
文章分类

全部博文(63)

文章存档

2017年(1)

2016年(4)

2015年(13)

2014年(9)

2012年(3)

2011年(33)

分类: WINDOWS

2011-07-01 13:14:48


Learn DLL

1.       DLL 声明

通常,在设计DLL时头文件中进行如下声明:

1CC++编程

#ifndef  XXX_LIBAPI

#define XXX_LIBAPI __declspec(dllimport)

#endif

XXX_LIBAPI 函数声明

2C/C++混合编程

#ifndef  XXX_LIBAPI

#define XXX_LIBAPI extern “C” __declspec(dllimport)

#endif

XXX_LIBAPI 函数声明

使用extern “C” 的目的是为了防止C++编译器对函数名进行改编,以免C程序调用时无法找到需要的函数。

 

2.       DLL定义

DLL定义文件中通常有如下形式:

1CC++编程

#define XXX_LIBAPI __declspec(dllexport)

#include “xxxxLib.h” //对应该DLL的头文件

函数定义

2C/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)       显式加载

显示加载是在程序运行时动态加载,这是通过以下两个函数之一实现的:

LoadLibraryPCTSTR pszDLLPathName

HMODULE LoadLibraryEx(

        PCTSTR pszDLLPathName,

        HANDLE hFile,

        DWORD dwFlags)

其中,pszDLLPathName DLL模块的名字;

hFile保留,设置为NULL

dwFlags0或者以下值的位组合

   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映射到调用进程的地址空间,然后执行DllMainDLL模块进行初始化;而且系统还会检查该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在内存中的加载地址进行转换,它使得解析DLLPE节的时候非常方便

l  LOAD_WITH_ALTERED_SEARCH_PATH

该标志用于改变loadLibraryEx定位DLL时的搜索算法,关于DLL的定位搜索算法在后面说明。

l  LOAD_IGNORE_CODE_AUTHZ_LEVEL

该标志用于关闭windowsWinSafer校验。

 

5.       DLL模块的定位搜索

一般情况下DLL模块的定位算法或搜索顺序如下:

1)可执行文件(调用DLL)所在目录

2Windows系统目录;该目录可以通过函数GetSystemDirectory获得

316位系统目录,即windows目录下的system子目录

4Windows目录;该目录可以通过函数GetWindowsDirectory获得

5)进程的当前目录

6PATH环境变量中列出的目录

如果在以上目录中未找到需要的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,恢复标准搜索。

阅读(1201) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~