动态链接库

                动态链接库

 一个动态链接库是一类公有函数池。Windows将不能加载一个DLL文件的几个复件到内存中,所以即使你的程序在同一时间运行了很多实例,在内存中程序用到的Dll文件也仅只有一个拷贝。不过我还要澄清一点。实际上,所有用到相同dll文件的程序,它们自己都有那个dll文件复件。这看起来像有很多dll文件的复件在内存中。但是事实上,windows用它魔法般的内存分页技术让所有的进程共享同一个dll代码。所以在实际的内存中,也仅有一个dll代码复件。然而每一个进程将有它自己唯一的dll数据节区。(数据段)


程序在运行时才链接到动态链接库的这种方式并不像古老的静态链接。这也是为什么叫动态链接库的原因。当你不需要它时,你最好在运行时刻卸载这个DLL文件。如果仅有一个程序使用这个DLL文件,它将立即从内存中释放出来。但是如果动态链接库还被其它程序使用,那么这个DLL文件将保留在内存里,只到最后一个使用它的程序卸载它的服务


 然而,当连接器为一个可执行文件完成地址修正后,它还有很多困难的工作要做。因为它不能提取这些函数并且把它们插入到最后的可执行文件中,为了能够在运行时定位和装载正确的DLL,它必须把关于这个DLL文件以及 在可执行文件中的函数的 足够多的 信息用某种方式储存起来。


 简而言之,存放这些信息的地方就是引入库。一个引入库包含DLL文件所表征的所有信息。连接器能从引入库中获取它所需要的信息并且填充它到可执行文件中。当windows装载器把一个程序装入内存中时,它设法让程序和dll链接在一起,所以它搜索那个DLL文件并把它映射到进程的地址空间中,然后为了调用这个dll文件中的函数,它还要完成地址修正。

你可以自己决定装载那个dll文件而没有必要依赖于windows的装载器。


这种方法有它肯定的地方和否定的地方:

它并不需要一个引入库,所以你能装载和使用任何的dll文件,即使它从来就没有引入库。然而,你需要知道它内部的函数功能,它们需要多少个参数和参数的类型

当你让装载器为你的程序装载一个dll文件时,如果装载器不能找到dll文件它将报告”A required .DLL file,xxxxx.dll is missing”并且有POOF的声音!这样你的程序将没有机会运行,即使这个dll文件并不是程序运行时必不可少的。如果是你自己加载这个dll文件,当dll文件不能被发现时它对程序运行来说并不是必不可少的,你的程序只是告诉用户事实然后继续运行。


你能调用“无书面文件的”的函数,这些函数并不包含在输入表中。前提是假如你知道这个函数足够多的信息。

 如果你用LoadLibrary,你必须为你想调用的每一个函数调用GetProcAddress函数。GetProcAddress函数在一个特殊的dll中获得函数的入口地址。所以你的程序运行起来可能要多占一些内存,也会有点慢不过并不明显。


    观看LoadLibrary调用的有利条件和不利条件,现在我们现在进入如何创建dll的细节。
    下面这些代码是dll文件的框架。


                           DLLSkeleton.asm

.386
.model flat,stdcall
option casemap:none
include /masm32/include/windows.inc
include /masm32/include/user32.inc
include /masm32/include/kernel32.inc
includelib /masm32/lib/user32.lib
includelib /masm32/lib/kernel32.lib

.data
.code
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
        mov  eax,TRUE
        ret
DllEntry Endp

; 这是一个空函数
;  它什么都没有,我放置在这是为了告诉你能在dll文件的什么地方插入函数

TestFunction proc
    ret
TestFunction endp

End DllEntry


                             DLLSkeleton.def

LIBRARY   DLLSkeleton
EXPORTS   TestFunction
 

 上面的程序是dll的框架。每一个dll必须有一个入口函数。Windows每一次遇到如下情况时都将调用入口函数。

          1:  第一次装载这个dll文件。
          2:  dll文件被卸载
          3:  同一进程的一个线程被创建
          4:  同一进程的一个线程被销毁。

DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
        mov  eax,TRUE
        ret
DllEntry Endp

 你能用任何你想要的字串来为入口点函数命名只要你让它和END标志想匹配。END<函数入口点名称>.这个函数有三个参数,仅有开始的两个是重要的。
         HinstDll: 是dll的模块句柄。它并不同于进程的实例句柄。如果你稍后将用到它你应该保存                    它的值。你能不费力的再次获得它。

         reason:值是下面四个中的一个:

                DLL_PROCESS_ATTACH:当它第一次注入到进程的地址空间时,dll文件接收到这个值。你                                   能使用这个机会做一些初始化的工作。
                DLL_PROCESS_DETACH :当它从进程地址空间中卸载时,dll接收到这个值。你能利用这                                   个机会做一些清理工作。例如:释放内存等等。

                DLL_THEAD_ATTACH :当进程创建一个新线程时,dll接收到这个值。
 
                DLL_THREAD_DETACH :当进程中的线程被销毁时,dll接收到这个值。

如果你想dll继续运行,在eax中返回TRUE,如果返回FALSE,这个dll将不被装载。
     例如:如果初始化代码必须分配一些内存而不成功时,这个入口函数应该返回FALSE来指示dll文件不能运行。你能把你的函数放在dll文件的入口函数之后或者之前。但是如果你想让它们能被其它程序随时支取,你必须把它们的名字放在模块定义文件的输出表中。

      一个dll文件需要一个模块定义文件在它们的发展阶段。
      现在我们来看一下它:

         LIBRARY   DLLSkeleton
         EXPORTS   TestFunction

      LIBRARY 语句定义了dll文件的内部模块名。通常这行你必须有。你应该让dll的文件名和它相配。

      EXPORTS 语句告诉连接器在dll中的输出函数,也就是能被其它程序随时支取的函数。在这个例子       中,我们想让其它模块能够调用TextFunction函数,所以我们把它的名字放在EXPORTS语句中。


 例外的变化是连接器的开关。你必须放置/DLL开关和/DEF:<your def filename>在你的链接器中,像这样:

link /DLL /SUBSYSTEM:WINDOWS /DEF:DLLSkeleton.def /LIBPATH:c:/masm32/lib DLLSkeleton.obj

汇编器的开关选项是一样的 ,就是/c /coff /Cp。当你连接成目标文件后,你将得到.dll和.lib文件。这.lib是引入库,这个引入库能用来链接其它程序,链接之后,其它程序就能使用在dll中的函数。


下面我将向你现实如何用LoadLibrary来装载一个dll文件。


                               UseDLL.asm

.386
.model flat,stdcall
option casemap:none
include /masm32/include/windows.inc
include /masm32/include/user32.inc
include /masm32/include/kernel32.inc
includelib /masm32/lib/kernel32.lib
includelib /masm32/lib/user32.lib

.data
LibName db "DLLSkeleton.dll",0
FunctionName db "TestHello",0
DllNotFound db "Cannot load library",0
AppName db "Load Library",0
FunctionNotFound db "TestHello function not found",0

.data?
hLib dd ?                                         ; the handle of the library (DLL)
TestHelloAddr dd ?                        ; the address of the TestHello function

.code
start:
        invoke LoadLibrary,addr LibName

传递你想要的dll名字给LoadLibrary并调用它,如果调用成功,它将返回一个动态链接库的句柄,如果不成功,它返回NULL。你能传递dll的句柄给GetProcAddress或者是任何需要一个动态链接库句柄作为它参数的函数。

        .if eax==NULL
                invoke MessageBox,NULL,addr DllNotFound,addr AppName,MB_OK
        .else
                mov hLib,eax
                invoke GetProcAddress,hLib,addr FunctionName

当你得到动态链接库句柄时,你传递一个在dll文件中你想调用的函数名的地址给GetProcAddress函数。如果成功,它将返回函数的地址。其它情况,它返回NULL。

除非你卸载了库文件或者是重新加载库文件,否则函数的地址不会改变。所以你能将它们放在一全局变量中已备将来使用。
                .if eax==NULL
                        invoke MessageBox,NULL,addr FunctionNotFound,addr AppName,MB_OK
                .else
                        mov TestHelloAddr,eax
                        call [TestHelloAddr]

以后您就可以和调用其它函数一样调用该函数了。其中要把包含函数地址信息的变量用方括号括起来。

                .endif
                invoke FreeLibrary,hLib

当你再也不需要这个库文件时,调用FreeLibrary卸载它。
        .endif
        invoke ExitProcess,NULL
end start

  使用LoadLibrary是有那么一点棘手,但是它有更多的灵活性。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值