Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1804321
  • 博文数量: 290
  • 博客积分: 10653
  • 博客等级: 上将
  • 技术积分: 3178
  • 用 户 组: 普通用户
  • 注册时间: 2007-10-24 23:08
文章存档

2013年(6)

2012年(15)

2011年(25)

2010年(86)

2009年(52)

2008年(66)

2007年(40)

分类:

2008-01-09 00:33:24

Tutorial 17: Dynamic Link Libraries

第十七课:动态链接库


In this tutorial, we will learn about DLLs , what are they and how to create them.

在这一课中,我们将学习关于动态链接库(DLL)的知识,什么是动态链接库和如何创建一个动态链接库。
Theory:

原理:

If you program long enough, you'll find that the programs you wrote usually have some code routines in common. It's such a waste of time to rewrite them everytime you start coding new programs. Back in the old days of DOS, programmers store those commonly used routines in one or more libraries. When they want to use the functions, they just link the library to the object file and the linker extracts the functions from the library and inserts them into the final executable file. This process is called static linking. C runtime libraries are good examples. The drawback of this method is that you have identical functions in every program that calls them. Your disk space is wasted storing several identical copies of the functions. But for DOS programs, this method is quite acceptable since there is usually only one program that's active in memory. So there is no waste of precious memory.
Under Windows, the situation becomes much more critical because you can have several programs running simultaneously. Memory will be eat up quickly if your program is quite large. Windows has a solution for this type of problem: dynamic link libraries. A dynamic link library is a kind of common pool of functions. Windows will not load several copies of a DLL into memory so even if there are many instances of your program running at the same time, there'll be only one copy of the DLL that program uses in memory. And I should clarify this point a bit. In reality, all processes that use the same dll will have their own copies of that dll. It will look like there are many copies of the DLL in memory. But in reality, Windows does it magic with paging and all processes share the same DLL code.So in physical memory, there is only one copy of DLL code. However, each process will have its own unique data section of the DLL.

如果你的程序足够长,你将发现:你通常为一个程序写代码的时候有一些程序代码是相同的。每当你为一个新程序编码时,重写它们只不过是在浪费时间。回到从前DOS的日子,程序员储存那些经常用到的相同的代码在一个或多个库文件中。当他们想用这些函数时,他们仅需要把这个库文件链接到目标文件中并且链接器从库文件中取出这些函数并把他们插入在最后的可执行文件中。这种处理方式被称为静态链接。C 运行库就是很好的例子。这种方法唯一的缺点,就是在每一个调用库文件的程序中,可能存在相同的程序代码。存储这些函数的多份复件浪费了你的磁盘空间。但是对于DOS程序员,这种方法是完全是可接受的,因为通常仅有一道程序运行在内存中,所以宝贵的内存并没有被浪费。

 

windows中,这种状况变得非常危急,因为你能同时的运行多道程序。如果你的程序非常大,很快内存就会被消耗完(吃光)windows为这类问题提供了解决办法:它就是动态链接库。一个动态链接库是一类公有函数池。Windows将不能加载一个DLL文件的几个复件到内存中,所以即使你的程序在同一时间运行了很多实例,在内存中程序用到的Dll文件也仅只有一个拷贝。不过我还要澄清一点。实际上,所有用到相同dll文件的程序,它们自己都有那个dll文件复件。这看起来像有很多dll文件的复件在内存中。但是事实上,windows用它魔法般的内存分页技术让所有的进程共享同一个dll代码。所以在实际的内存中,也仅有一个dll代码复件。然而每一个进程将有它自己唯一的dll数据节区。(数据段)


The program links to a DLL at runtime unlike the old static library. That's why it's called dynamic link library. You can also unload a DLL at runtime as well when you don't need it. If that program is the only one that uses the DLL, it'll be unloaded from memory immediately. But if the DLL is still used by some other program, the DLL remains in memory until the last program that uses its service unloads it.

程序在运行时才链接到动态链接库的这种方式并不像古老的静态链接。这也是为什么叫动态链接库的原因。当你不需要它时,你最好在运行时刻卸载这个DLL文件。如果仅有一个程序使用这个DLL文件,它将立即从内存中释放出来。但是如果动态链接库还被其它程序使用,那么这个DLL文件将保留在内存里,只到最后一个使用它的程序卸载它的服务。
However, the linker has a more difficult job when it performs address fixups for the final executable file. Since it cannot "extract" the functions and insert them into the final executable file, somehow it must store enough information about the DLL and functions into the final execuable file for it to be able to locate and load the correct DLL at runtime.

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


That's where import library comes in. An import library contains the information about the DLL it represents. The linker can extract the info it needs from the import libraries and stuff it into the executable file. When Windows loader loads the program into memory, it sees that the program links to a DLL so it searches for that DLL and maps it into the address space of the process as well and performs the address fixups for the calls to the functions in the DLL.

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

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

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

  • It doesn't need an import library so you can load and use any DLL even if it comes with no import library. However, you still have to know about the functions inside it, how many parameters they take and the likes.

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

  • When you let the loader load the DLL for your program, if the loader cannot find the DLL it will report "A required .DLL file, xxxxx.dll is missing" and poof! your program doesn't have a chance to run even if that DLL is not essential to its operation. If you load the DLL yourself, when the DLL cannot be found and it's not essential to the operation, your program can just tell the user about the fact and go on.

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

  • You can call *undocumented* functions that are not included in the import libraries. Provided that you know enough info about the functions.

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

  • If you use LoadLibrary, you have to call GetProcAddress for every function that you want to call. GetProcAddress retrieves the entrypoint address of a function in a particular DLL. So your code might be a little bit larger and slower but by not much.

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

Seeing the advantages/disadvantages of LoadLibrary call, we go into detail how to create a DLL now.
The following code is the DLL skeleton.

观看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
 

The above program is the DLL skeleton. Every DLL must have an entrypoint function. Windows will call the entrypoint function everytime that:

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

  • The DLL is first loaded   第一次装载这个dll文件。
  • The DLL is unloaded       dll文件被卸载
  • A thread is created in the same process 同一进程的一个线程被创建
  • A thread is destroyed in the same process

同一进程的一个线程被销毁。

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

You can name the entrypoint function anything you wish so long as you have a matching END . This function takes three parameters, only the first two of which are important.

你能用任何你想要的字串来为入口点函数命名只要你让它和END标志想匹配。END<函数入口点名称>.这个函数有三个参数,仅有开始的两个是重要的。
hInstDLL is the module handle of the DLL. It's not the same as the instance handle of the process. You should keep this value if you need to use it later. You can't obtain it again easily.

HinstDlldll的模块句柄。它并不同于进程的实例句柄。如果你稍后将用到它你应该保存它的值。你能不费力的再次获得它。

reason can be one of the four values:

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

  • DLL_PROCESS_ATTACH The DLL receives this value when it is first injected into the process address space. You can use this opportunity to do initialization.

DLL_PROCESS_ATTACH当它第一次注入到进程的地址空间时,dll文件接收到这个值。你能使用这个机会做一些初始化的工作。

  • DLL_PROCESS_DETACH The DLL receives this value when it is being unloaded from the process address space. You can use this opportunity to do some cleanup such as deallocate memory and so on.

DLL_PROCESS_DETACH 当它从进程地址空间中卸载时,dll接收到这个值。你能利用这个机会做一些清理工作。例如:释放内存等等。

  • DLL_THREAD_ATTACH The DLL receives this value when the process creates a new thread.

DLL_THEAD_ATTACH 当进程创建一个新线程时,dll接收到这个值。

  • DLL_THREAD_DETACH The DLL receives this value when a thread in the process is destroyed.

DLL_THREAD_DETACH 当进程中的线程被销毁时,dll接收到这个值。

You return TRUE in eax if you want the DLL to go on running. If you return FALSE, the DLL will not be loaded. For example, if your initialization code must allocate some memory and it cannot do that successfully, the entrypoint function should return FALSE to indicate that the DLL cannot run.

如果你想dll继续运行,在eax中返回TRUE,如果返回FALSE,这个dll将不被装载。例如:如果初始化代码必须分配一些内存而不成功时,这个入口函数应该返回FALSE来指示dll文件不能运行。


You can put your functions in the DLL following the entrypoint function or before it. But if you want them to be callable from other programs, you must put their names in the export list in the module definition file (.def).

你能把你的函数放在dll文件的入口函数之后或者之前。但是如果你想让它们能被其它程序随时支取,你必须把它们的名字放在模块定义文件的输出表中。
A DLL needs a module definition file in its developmental stage. We will take a look at it now.

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

LIBRARY   DLLSkeleton
EXPORTS   TestFunction

Normally you must have the first line.The LIBRARY statement defines the internal module name of the DLL. You should match it with the filename of the DLL.

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


The EXPORTS statement tells the linker which functions in the DLL are exported, that is, callable from other programs. In the example, we want other modules to be able to call TestFunction, so we put its name in the EXPORTS statement.

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


Another change is in the linker switch. You must put /DLL switch and /DEF: in your linker switches like this:

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

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

The assembler switches are the same, namely /c /coff /Cp. So after you link the object file, you will get .dll and .lib. The .lib is the import library which you can use to link to other programs that use the functions in the DLL.

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


Next I'll show you how to use LoadLibrary to load a 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

So you can see that using LoadLibrary is a little more involved but it's also more flexible.

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


This article come from Iczelion's asm page

风向改变翻译于2008-1-9

 

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