win32汇编-动态链接库(六)

DLL函数头

    
DllEntry        proc hInstDLL,dwReason,dwReserved

 

                mov     eax,dwReason

                .if     eax ==  DLL_PROCESS_ATTACH

                        ;保存hInstDll

                        ;初始化库需要的各种资源

                        .if     初始化成功

                            mov     eax,TRUE

                        .else

                            mov eax,FALSE

                        .endif

                .elseif eax ==  DLL_THREAD_ATTACH

                    ;释放库使用的资源

                .elseif eax ==  DLL_THREAD_DETACH

                    ;为新的线程分配资源

                .elseif eax ==  DLL_PROCESS_DETACH

                    ;为线程释放资源

                .endif

                ret

 

DllEntry        Endp 
View Code

Windows会传给入口函数3个参数,dwReason参数的值表示本次调用的原因,它可能是下面四种情况。

dwReason的值是DLL_PROCESS_ATTACH  表示动态链接库刚被映射到某个进程的地址空间,程序可以在这里进行一些初始化的工作,并返回TRUE表示初始化成功,返回FALSE表示初始化出错,这样库的装入就会失败。报"初始化错误" 这给了动态链接库一个机会来阻止自己被装入。

当dwReason的值是DLL_PROCESS_DETACH 表示动态链接库将被卸载,库程序可以在这里进行一些资源的释放工作,如将初始化时申请的内存释放,将打开的文件关闭等。 以DLL_PROCESS_ATTACH和DLL_PROCESS_DETACH值进行的调用在库的生命周期中只可能出现一次。

当dwReason的值是DLL_THREAD_ATTACH 表示应用程序创建了一个新的线程。当某个线程正常终止的时候,dwReason的值是DLL_PROCESS_DETACH。如果应用程序不是以多线程方式工作的话,就不会有这两种原因的调用;反之,如果应用程序频繁地创建和结束线程,那么入口函数将不断被调用。

hInstDll是动态链接库的模块实例句柄。当使用这个句柄来装入资源的时候,表示资源是定义在库文件中的,对于动态链接库来说,获取这个句柄的惟一途径就是保留入口函数的这个参数,如果在DLL_PROCESS_ATTACH时不将这个句柄保存下来的话,运行时可能就没有其他方法可以获取了。dwReserved参数是系统保留的参数,可以不必理会。

动态链接库有一种很“极端”的应用:纯资源库,这些库仅包含资源而没有任何的功能函数,如字体文件等,对于这些库来说,库中的全部代码仅是入口函数中用来返回TRUE的那几句,这是库能被正常装入所必须的代码。

2. 导出函数

与写普通的可执行文件相比,动态链接库的设计流程中多了一个文件,那就是定义文件 *.def,示例源代码目录中还包括一个Counter.def文件,它的内容是:

EXPORTS _IncCount

        _DecCount

调用未导出函数声明将报"在xx.dll未找到xx函数的错误"

 

如果dll文件是当做最终应用程序发布的,可以仅发布dll文件;如果是当做插件供其他人做二次开发用的,那么就要为其他程序员同时提供dll文件和lib文件,并且根据情况提供不同语言使用的头文件(最后,还要为每个导出函数写一个说明,包括参数的个数、类型和定义等)。目录中还有一个Counter.inc文件,它的内容如下:

 

_IncCount   proto   :dword,:dword

 

_DecCount   proto   :dword,:dword

使用动态链接库

 

1. 方法一:常规方法

头文件中导入xx.inc和xx.lib 后当作一般函数调用 需保证xx.dll文件始终存在不然将报"找不到xx.dll"错误

 

2. 方法二:动态装入

 

方法一的优点就是使用方便,应用程序可以像使用自己内部的函数一样使用DLL中的函数,缺点也显而易见,如果装入DLL的过程中有任何错误,应用程序没有任何机会完成应变的措施,因为它根本没有被装入执行。

使用情况:1 程序需要使用系统中的保留函数。

            2 同版本Windows中的函数集不同 根据函数是否存在做不同的处理。

            3 使用某些并不重要库(如仅用来显示程序版本的库),丢失后,程序也能继续运行

 

不再需要Counter.lib文件和Counter.inc文件

LoadLibrary函数的使用方法是:

 invoke  LoadLibrary,lpDllFileName //需要装载的库文件名(仅文件名)

   .if     eax

           mov     hDllInstance,eax

   .endif

 

如果装载动态链接库成功,下一步就是使用GetProcAddress函数来获取库中函数的地址。GetProcAddress函数的使用方法是:

 invoke  GetProcAddress,hDllInstance,lpProcName

   .if     eax

           mov     lpProc,eax

   .endif

hDllInstance参数就是LoadLibrary函数返回的动态链接库的实例句柄,lpProcName指向要获取的函数名称,函数名也是用以NULL结尾的字符串来定义。有些系统DLL中的函数名称并不是字符串,而是使用数值编号,对于这种情况,lpProcName参数可以指定为函数的编号数值。

如果执行成功,返回值是要获取的函数的入口地址,程序可以保存它并在以后调用。如果执行失败,比如因为版本变化等原因导致需要获取的函数不存在,这时函数返回NULL。

在不再需要动态链接库的时候,为了释放库所占用的系统资源,需要使用FreeLibrary函数释放它。FreeLibrary函数的用法是:

   invoke  FreeLibrary,hDllInstance

输入参数是LoadLibrary函数返回的实例句柄,函数导致系统以DLL_PROCESS_DETACH代码调用库的入口函数,这样库文件可以自己释放占用的一些资源,然后,整个库的代码和数据被从应用程序的地址空间中清除。

方法二和方法一的最后一个不同点是调用函数的方法,在使用GetProcAddress函数获取了库中导出函数的入口点以后,程序在调用的时候一般使用将参数手工入栈的方法,如对_IncCount函数的调用可以写为:

   push        IDC_COUNTER

   push        hWnd

   call        lpIncCount      lpIncCount保存有GetProcAddress获取的地址

这样写法的缺点是无法使用invoke伪指令来进行参数检验,容易引发错误。实际上还有一个变通的方法,可以将一个变量定义为子程序入口指针,并为它定义参数个数,方法是两次使用typedef伪操作

_PROCVAR2       typedef proto :dword,:dword

PROCVAR2            typedef ptr _PROCVAR2

如上面的第一句将_PROCVAR2类型定义为使用两个参数的函数类型,第二句将PROCVAR类型定义为_PROCVAR2类型的指针,这样,在数据段中就可以将保存函数入口地址的变量使用PROCVAR2类型来定义了,得到的好处就是可以用invoke语句来调用这个变量中的指针:

                   .data?

lpIncCount      PROCVAR2    ?

lpDecCount      PROCVAR2    ?

动态链接库中的数据共享

 

未初始化数据段 .data?的节区名称为 .bbs,加上/section:.bss,S链接选项就可以将这个段的属性改为共享,这样,当DLL被不同应用程序装载的时候,不但映射到不同进程地址空间中的代码段来自同一段物理内存,.data?段的映射也来自同一段物理内存。