Chinaunix首页 | 论坛 | 博客
  • 博客访问: 129608
  • 博文数量: 37
  • 博客积分: 1490
  • 博客等级: 上尉
  • 技术积分: 326
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-01 16:38
文章分类

全部博文(37)

文章存档

2011年(1)

2010年(23)

2009年(13)

我的朋友

分类: WINDOWS

2009-12-26 11:38:10

 win32 asm第十七课:动态链接库
 
 
      在这一课中,我们将学习关于动态链接库(DLL)的知识,什么是动态链接库和如何创建一个动态链接库。
原理:
     如果你的程序足够长,你将发现:你通常为一个程序写代码的时候有一些程序代码是相同的。每当你为一个新程序编码时,重写它们只不过是在浪费时间。回到从前DOS的日子,程序员储存那些经常用到的相同的代码在一个或多个库文件中。当他们想用这些函数时,他们仅需要把这个库文件链接到目标文件中并且链接器从库文件中取出这些函数并把他们插入在最后的可执行文件中。这种处理方式被称为静态链接。C 运行库就是很好的例子。
      这种方法唯一的缺点,就是在每一个调用库文件的程序中,可能存在相同的程序代码。存储这些函数的多份复件浪费了你的磁盘空间。但是对于DOS程序员,这种方法是完全是可接受的,因为通常仅有一道程序运行在内存中,所以宝贵的内存并没有被浪费。
    在windows中,这种状况变得非常危急,因为你能同时的运行多道程序。如果你的程序非常大,很快内存就会被消耗完    windows为这类问题提供了解决办法:它就是动态链接库。
    一个动态链接库是一类公有函数池。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
;-----------------------------------------------------------------
;           This is a dummy function  这是一个空函数
; It does nothing. I put it here to show where you can insert  functions into
; a DLL. 它什么都没有,我放置在这是为了告诉你能在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:在你的链接器中,像这样:
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
;--------------------------------------------------------------------------
; Call LoadLibrary with the name of the desired DLL. If the call is successful
; it will return the handle to the library (DLL). If not, it will return NULL
; You can pass the library handle to GetProcAddress or any function that requires
; a library handle as a parameter.
传递你想要的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
;-----------------------------------------------------------------------------
; When you get the library handle, you pass it to GetProcAddress with the address
; of the name of the function in that DLL you want to call. It returns the address
; of the function if successful. Otherwise, it returns NULL
; Addresses of functions don't change unless you unload and reload the library.
; So you can put them in global variables for future use.
当你得到动态链接库句柄时,你传递一个在dll文件中你想调用的函数名的地址给GetProcAddress函数。如果成功,它将返回函数的地址。其它情况,它返回NULL。
除非你卸载了库文件或者是重新加载库文件,否则函数的地址不会改变。所以你能将它们放在一全局变量中已备将来使用。
;------------------------------------------------------------------------------
                .if eax==NULL
                        invoke MessageBox,NULL,addr FunctionNotFound,addr AppName,MB_OK
                .else
                        mov TestHelloAddr,eax
                        call [TestHelloAddr]
;----------------------------------------------------------------------------
; Next, you can call the function with a simple call with the variable containing
; the address of the function as the operand.
以后您就可以和调用其它函数一样调用该函数了。其中要把包含函数地址信息的变量用方括号括起来。
;--------------------------------------------------------------------------
                .endif
                invoke FreeLibrary,hLib
;--------------------------------------------------------------------------
; When you don't need the library anymore, unload it with FreeLibrary.
当你再也不需要这个库文件时,调用FreeLibrary卸载它。
;-------------------------------------------------------------------------
        .endif
        invoke ExitProcess,NULL
end start
      如你所见,使用LoadLibrary是有那么一点棘手,但是它有更多的灵活性。
阅读(792) | 评论(0) | 转发(0) |
0

上一篇:通用控件

下一篇:动态链接库

给主人留下些什么吧!~~