Windows 32位汇编语言程序设计教程 之动态链接库
http://www.feiesoft.com/win32asm/win32asm-11-2.html
第11章 动态链接库和钩子11.1 动态链接库(1)
11.1.1 动态链接库的概念
在DOS环境下编过程序的读者一定知道静态库的含义——程序员将实现各种功能的代码写成一个个子程序(函数),编译成
obj文件后,将多个obj文件组合成一个lib文件,当程序中要用到这些函数的时候,只需要指定函数名称,编译器就可以从库
中抽出对应的子程序代码插入到可执行文件中去,这样就可以不必一遍遍地重写相同的功能代码。这种链接方法就是静态链
接,
静态链接的缺点显而易见,如果有多个程序用到库中的同样函数,那么所有这些可执行文件中都会包含一份同样的代码,对
于每个程序几乎必须使用的一些函数来说,如果硬盘上有一万个程序用到这个函数,那么就存在一万份相同的代码,这显然
是很浪费空间的。静态链接的另外一个缺点是:如果某个函数因为发现有错或更新算法等种种原因需要升级版本时,必须把
所有用到此函数的可执行文件都找回来重新编译一遍,遗漏的程序中存在的还是旧版本的代码。
DOS操作系统是单任务的操作系统,每时每刻只能有一个程序在运行,所以使用静态链接浪费的空间仅表现在磁盘空间的浪费
上;而Windows操作系统是多任务的,内存中会同时装入多个程序的代码,如果使用静态链接的话,意味着有多份相同的代码
被装入内存,这种浪费代价将是更昂贵的。
Windows的解决办法就是使用动态链接库,动态链接库从表面上看也是提供了一大堆通用的函数,也可以被多个程序使用,但
它和静态库的使用上有很多的不同点。
静态库仅在编译的时候使用,编译完成后,可执行文件就可以脱离库文件单独使用了,而动态链接库中的代码在程序编译的
时候并不会被插入到可执行文件中,在程序运行的时候才将整个库的代码调入内存,所以称为“动态链接”。如果有多个程
序用到同一个动态链接库,Windows在物理内存中只保留一份库的代码,仅通过分页机制将这份代码映射到不同进程的地址空
间中,这样不管有多少程序在使用一个库,库代码实际占用的物理内存永远只有一份。当然,这时候库使用的数据段还是会
被映射到不同的物理内存中,多少个程序在使用动态链接库就会有多少份数据段。DLL的工作方式在图1.6中就已经有所演示
了。
当应用程序装载动态链接库的时候,程序中仅包括库的名称和函数的名称,这些信息是动态寻找对应函数所必须的,程序在
编译和链接的时候必须插入这些定位信息,定位信息取自导入库文件,这一点在前面的编程中已经多次涉及。
动态链接库的缩写为DLL,大部分动态链接库是以扩展名为dll的文件形式存在的,但并不是只有dll扩展名的文件才是动态链
接库,系统中的某些exe文件、字体文件(*.fon)、一些驱动程序(*.drv)、各种控件(*.ocx)和输入法模块(*.ime)等
都是动态链接库。实际上,系统中大部分包含公用代码的模块——不管扩展名是什么——都有可能是动态链接库。
一个文件是否是动态链接库取决于它的文件结构,动态链接库文件和可执行文件同样使用标准的PE文件格式,仅文件头中的
属性位不同而已,所以exe文件的一些特征也存在于动态链接库中,比如在动态链接库中也可以定义并使用各种资源,可以导
入并使用其他动态链接库中的函数等。
有一个最重要的概念一定要牢记:动态链接库是被映射到其他应用程序的地址空间中执行的,它和应用程序可以看成是“一
体”的,动态链接库可以使用应用程序的资源,它所拥有的资源也可以被应用程序使用,它的任何操作都是代表应用程序进
行的,当动态链接库进行打开文件、分配内存和创建窗口等操作后,这些文件、内存和窗口都是为应用程序所拥有的。
11.1.2 编写动态链接库
与前面一些例子程序相比,写动态链接库程序应该算是很简单的,程序中并不需要用到新的函数,只是在程序的结构上和链
接时的选项有些区别而已。让我们通过一个简单的例子来说明,例子代码在所附光盘的Chapter11\Dll\Dll目录中,包括汇编
源文件Counter.asm和定义文件Counter.def。上一层子目录Chapter11\Dll中存放的是调用DLL的例子,其中的内容将在下一
节中分析。
Counter.asm的内容如下:
.386
.model flat, stdcall
option casemap :none
;Include 文件定义
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
.data?
dwCount dd ?
.code
DllEntry proc _hInstance,_dwReason,_dwReserved
mov eax,TRUE
ret
DllEntry Endp
_SetText proc _hWnd,_dwID,_dwCount
invoke SetDlgItemInt,_hWnd,_dwID,_dwCount,TRUE
ret
_SetText endp
_IncCount proc _hWnd,_dwID
inc dwCount
invoke _SetText,_hWnd,_dwID,dwCount
mov eax,dwCount
ret
_IncCount endp
_DecCount proc _hWnd,_dwID
dec dwCount
invoke _SetText,_hWnd,_dwID,dwCount
mov eax,dwCount
ret
_DecCount endp
End DllEntry
程序很简单,既没有创建窗口的代码也没有创建对话框的代码,总之,程序没有任何和创建界面有关的代码,仅定义了4个子
程序:DllEntry,_SetText,_IncCount和_DecCount。其中程序的入口点由最后一句End语句定义到了DllEntry处。而
_SetText子程序被最后两个子程序调用,功能是将指定的数值显示到指定对话框的一个子窗口控件中。最后两个子程序的功
能分别是对dwCount变量进行递增或递减的操作,并把操作结果显示在参数指定的对话框中的子窗口控件中。
一眼看上去,程序比较莫名其妙——入口的代码什么都没有做,仅返回一个TRUE;也没有地方用到_IncCount和_DecCount函
数,这是为什么呢?请记住,dll文件的设计不是供自己使用的,而是被映射到其他应用程序的地址空间中代表“宿主”程序
执行的,这两个函数就是供其他程序使用的函数,实际上对于“宿主”程序来说,虽然这两个函数仅包含3行代码,但它们的
级别和User32.dll中的CreateWindowEx与DefWindowProc等极其复杂的函数没有任何区别。
1. 入口点和初始化代码
与可执行文件一样,动态链接库需要一个入口点,动态链接库的入口点是一个函数,函数的名称并不重要,例子代码中的入
口函数命名为“DllEntry”,读者也可以把它取名为其他任何合法的名字,但入口函数的格式是有规定的。
库的入口函数对调用动态链接库的应用程序来说是不可见的,它仅供操作系统使用。Windows在库装载、卸载、进程中线程的
创建和结束等时候调用入口函数,以便动态链接库可以采取相应的动作。在入口函数中可以通过参数来判别Windows的本次调
用究竟是在哪种情况下发生的。入口函数的结构一般如下面所示:
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
Windows会传给入口函数3个参数,dwReason参数的值表示本次调用的原因,它可能是下面的四种情况之一。
当dwReason的值是DLL_PROCESS_ATTACH的时候,表示动态链接库刚被映射到某个进程的地址空间,程序可以在这里进行一些
初始化的工作,并返回TRUE表示初始化成功,返回FALSE表示初始化出错,这样库的装入就会失败。这给了动态链接库一个机
会来阻止自己被装入。比如库程序可以在这里申请并保留一些内存,如果申请失败的话就可以返回FALSE告诉Windows,库无
法正常工作。
当dwReason的值是DLL_PROCESS_DETACH的时候则相反,表示动态链接库将被卸载,库程序可以在这里进行一些资源的释放工
作,如将初始化时申请的内存释放,将打开的文件关闭等。 以DLL_PROCESS_ATTACH和DLL_PROCESS_DETACH值进行的调用在库
的生命周期中只可能出现一次。
当dwReason的值是DLL_THREAD_ATTACH的时候,表示应用程序创建了一个新的线程。当某个线程正常终止的时候,dwReason的
值是DLL_PROCESS_DETACH。如果应用程序不是以多线程方式工作的话,就不会有这两种原因的调用;反之,如果应用程序频
繁地创建和结束线程,那么入口函数将不断被调用。
hInstDll是动态链接库的模块实例句柄。当使用这个句柄来装入资源的时候,表示资源是定义在库文件中的,对于动态链接
库来说,获取这个句柄的惟一途径就是保留入口函数的这个参数,如果在DLL_PROCESS_ATTACH时不将这个句柄保存下来的话
,运行时可能就没有其他方法可以获取了。dwReserved参数是系统保留的参数,可以不必理会。
读者可能会问:不是可以通过GetModuleHandle(NULL)函数来获取模块实例句柄吗?是的,但是动态链接库是代表应用程序
运行的,所以,如果在库中调用这个函数,得到的仍然是“宿主”程序的实例句柄,而不是库程序的实例句柄。
在例子程序中,不需要初始化工作,所以仅返回一个TRUE,表示任何情况下,Windows都可以装入这个库文件。动态链接库有
一种很“极端”的应用:纯资源库,这些库仅包含资源而没有任何的功能函数,如字体文件等,对于这些库来说,库中的全
部代码仅是入口函数中用来返回TRUE的那几句,这是库能被正常装入所必须的代码。
2. 导出函数
与写普通的可执行文件相比,动态链接库的设计流程中多了一个文件,那就是定义文件 *.def,源代码目录中还包括一个
Counter.def文件,它的内容是:
EXPORTS _IncCount
_DecCount
文件内容总共只有两行:一个EXPORTS关键字加上两个库中函数的名称,这是用来告诉链接器这两个函数需要导出,也就是说
这两个函数可以被其他程序调用。动态链接库的文件格式是PE格式,每个PE格式文件的文件头中都可以有一个导出表,只有
导出表中列出的函数才可以被其他程序调用,链接器根据def文件的内容在导出表中加入由EXPORTS关键字指定的函数名。
如果库文件中的函数没有在def文件中指定(如例子中的_SetText函数),那么这个函数就仅能被库文件中的代码调用,而无
法在其他应用程序中使用,这是因为库文件的导出表中没有列出它的名称,这样其他程序根本不会知道它的存在。对于这些
函数,可以把它们叫做私有函数。
3. 链接选项
为了生成动态链接库文件,在链接的时候必须使用合适的选项,来看看Counter库文件例子使用的Makefile文件:
DLL = Counter
ML_FLAG = /c /coff
LINK_FLAG = /subsystem:windows /Dll
$(DLL).dll: $(DLL).obj $(DLL).def
Link $(LINK_FLAG) /Def:$(DLL).def $(DLL).obj
.asm.obj:
ml $(ML_FLAG) $<
.rc.res:
rc $<
clean:
del *.obj
del *.exp
del *.lib
编译的时候,使用Ml.exe编译器的方法并没有什么不同,但是使用Link.exe链接程序的时候,必须使用/Dll和/Def选
项,/Dll选项告诉链接器输出文件的格式是动态链接库,/Def:filename.def选项用来指定定义了导出函数名称的def文件名
,在这个例子中,库文件中没有包含资源,如果包含资源的话,链接时还可以指定资源文件名,一个完整的链接参数如下所
示:
Link /DLL /subsystem:windows /Def:filename.def filename.obj filename.res
4. 生成库文件
当使用Link.exe链接器完成链接工作后,链接器生成3个文件,它们分别以dll,lib和exp为扩展名。dll文件就是动态链接库
,而lib文件是供程序开发用的导入库。
回想一下:当在汇编源程序中用到某个动态链接库中的函数时,在源文件的一开始就要用includelib语句指定动态链接库的
导入库,这样链接的时候链接器才知道到哪个库中寻找指定的函数,如果开发的时候没有动态链接库的导入库文件,使用起
来就比较麻烦了。
为了在开发其他程序的时候使用自己编写的动态链接库,就必须提供这个动态链接库的导入库文件,Link.exe考虑了这一点
,所以在生成dll文件的同时也生成了导入库文件。如果dll文件是当做最终应用程序发布的,可以仅发布dll文件;如果是当
做插件供其他人做二次开发用的,那么就要为其他程序员同时提供dll文件和lib文件,并且根据情况提供不同语言使用的头
文件(最后,还要为每个导出函数写一个说明,包括参数的个数、类型和定义等)。目录中还有一个Counter.inc文件,它的
内容如下:
_IncCount proto :dword,:dword
_DecCount proto :dword,:dword
这是为了在汇编程序中使用Counter.dll库文件而书写的include文件。(这样读者就知道MASM32软件包中包含的lib文件和
inc文件是怎么来的了!)
exp文件是输出库文件,这是链接时的一个副产品,一般没有什么用途,我们可以直接将它删掉。
http://www.feiesoft.com/win32asm/win32asm-11-2.html
第11章 动态链接库和钩子11.1 动态链接库(1)
11.1.1 动态链接库的概念
在DOS环境下编过程序的读者一定知道静态库的含义——程序员将实现各种功能的代码写成一个个子程序(函数),编译成
obj文件后,将多个obj文件组合成一个lib文件,当程序中要用到这些函数的时候,只需要指定函数名称,编译器就可以从库
中抽出对应的子程序代码插入到可执行文件中去,这样就可以不必一遍遍地重写相同的功能代码。这种链接方法就是静态链
接,
静态链接的缺点显而易见,如果有多个程序用到库中的同样函数,那么所有这些可执行文件中都会包含一份同样的代码,对
于每个程序几乎必须使用的一些函数来说,如果硬盘上有一万个程序用到这个函数,那么就存在一万份相同的代码,这显然
是很浪费空间的。静态链接的另外一个缺点是:如果某个函数因为发现有错或更新算法等种种原因需要升级版本时,必须把
所有用到此函数的可执行文件都找回来重新编译一遍,遗漏的程序中存在的还是旧版本的代码。
DOS操作系统是单任务的操作系统,每时每刻只能有一个程序在运行,所以使用静态链接浪费的空间仅表现在磁盘空间的浪费
上;而Windows操作系统是多任务的,内存中会同时装入多个程序的代码,如果使用静态链接的话,意味着有多份相同的代码
被装入内存,这种浪费代价将是更昂贵的。
Windows的解决办法就是使用动态链接库,动态链接库从表面上看也是提供了一大堆通用的函数,也可以被多个程序使用,但
它和静态库的使用上有很多的不同点。
静态库仅在编译的时候使用,编译完成后,可执行文件就可以脱离库文件单独使用了,而动态链接库中的代码在程序编译的
时候并不会被插入到可执行文件中,在程序运行的时候才将整个库的代码调入内存,所以称为“动态链接”。如果有多个程
序用到同一个动态链接库,Windows在物理内存中只保留一份库的代码,仅通过分页机制将这份代码映射到不同进程的地址空
间中,这样不管有多少程序在使用一个库,库代码实际占用的物理内存永远只有一份。当然,这时候库使用的数据段还是会
被映射到不同的物理内存中,多少个程序在使用动态链接库就会有多少份数据段。DLL的工作方式在图1.6中就已经有所演示
了。
当应用程序装载动态链接库的时候,程序中仅包括库的名称和函数的名称,这些信息是动态寻找对应函数所必须的,程序在
编译和链接的时候必须插入这些定位信息,定位信息取自导入库文件,这一点在前面的编程中已经多次涉及。
动态链接库的缩写为DLL,大部分动态链接库是以扩展名为dll的文件形式存在的,但并不是只有dll扩展名的文件才是动态链
接库,系统中的某些exe文件、字体文件(*.fon)、一些驱动程序(*.drv)、各种控件(*.ocx)和输入法模块(*.ime)等
都是动态链接库。实际上,系统中大部分包含公用代码的模块——不管扩展名是什么——都有可能是动态链接库。
一个文件是否是动态链接库取决于它的文件结构,动态链接库文件和可执行文件同样使用标准的PE文件格式,仅文件头中的
属性位不同而已,所以exe文件的一些特征也存在于动态链接库中,比如在动态链接库中也可以定义并使用各种资源,可以导
入并使用其他动态链接库中的函数等。
有一个最重要的概念一定要牢记:动态链接库是被映射到其他应用程序的地址空间中执行的,它和应用程序可以看成是“一
体”的,动态链接库可以使用应用程序的资源,它所拥有的资源也可以被应用程序使用,它的任何操作都是代表应用程序进
行的,当动态链接库进行打开文件、分配内存和创建窗口等操作后,这些文件、内存和窗口都是为应用程序所拥有的。
11.1.2 编写动态链接库
与前面一些例子程序相比,写动态链接库程序应该算是很简单的,程序中并不需要用到新的函数,只是在程序的结构上和链
接时的选项有些区别而已。让我们通过一个简单的例子来说明,例子代码在所附光盘的Chapter11\Dll\Dll目录中,包括汇编
源文件Counter.asm和定义文件Counter.def。上一层子目录Chapter11\Dll中存放的是调用DLL的例子,其中的内容将在下一
节中分析。
Counter.asm的内容如下:
.386
.model flat, stdcall
option casemap :none
;Include 文件定义
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
.data?
dwCount dd ?
.code
DllEntry proc _hInstance,_dwReason,_dwReserved
mov eax,TRUE
ret
DllEntry Endp
_SetText proc _hWnd,_dwID,_dwCount
invoke SetDlgItemInt,_hWnd,_dwID,_dwCount,TRUE
ret
_SetText endp
_IncCount proc _hWnd,_dwID
inc dwCount
invoke _SetText,_hWnd,_dwID,dwCount
mov eax,dwCount
ret
_IncCount endp
_DecCount proc _hWnd,_dwID
dec dwCount
invoke _SetText,_hWnd,_dwID,dwCount
mov eax,dwCount
ret
_DecCount endp
End DllEntry
程序很简单,既没有创建窗口的代码也没有创建对话框的代码,总之,程序没有任何和创建界面有关的代码,仅定义了4个子
程序:DllEntry,_SetText,_IncCount和_DecCount。其中程序的入口点由最后一句End语句定义到了DllEntry处。而
_SetText子程序被最后两个子程序调用,功能是将指定的数值显示到指定对话框的一个子窗口控件中。最后两个子程序的功
能分别是对dwCount变量进行递增或递减的操作,并把操作结果显示在参数指定的对话框中的子窗口控件中。
一眼看上去,程序比较莫名其妙——入口的代码什么都没有做,仅返回一个TRUE;也没有地方用到_IncCount和_DecCount函
数,这是为什么呢?请记住,dll文件的设计不是供自己使用的,而是被映射到其他应用程序的地址空间中代表“宿主”程序
执行的,这两个函数就是供其他程序使用的函数,实际上对于“宿主”程序来说,虽然这两个函数仅包含3行代码,但它们的
级别和User32.dll中的CreateWindowEx与DefWindowProc等极其复杂的函数没有任何区别。
1. 入口点和初始化代码
与可执行文件一样,动态链接库需要一个入口点,动态链接库的入口点是一个函数,函数的名称并不重要,例子代码中的入
口函数命名为“DllEntry”,读者也可以把它取名为其他任何合法的名字,但入口函数的格式是有规定的。
库的入口函数对调用动态链接库的应用程序来说是不可见的,它仅供操作系统使用。Windows在库装载、卸载、进程中线程的
创建和结束等时候调用入口函数,以便动态链接库可以采取相应的动作。在入口函数中可以通过参数来判别Windows的本次调
用究竟是在哪种情况下发生的。入口函数的结构一般如下面所示:
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
Windows会传给入口函数3个参数,dwReason参数的值表示本次调用的原因,它可能是下面的四种情况之一。
当dwReason的值是DLL_PROCESS_ATTACH的时候,表示动态链接库刚被映射到某个进程的地址空间,程序可以在这里进行一些
初始化的工作,并返回TRUE表示初始化成功,返回FALSE表示初始化出错,这样库的装入就会失败。这给了动态链接库一个机
会来阻止自己被装入。比如库程序可以在这里申请并保留一些内存,如果申请失败的话就可以返回FALSE告诉Windows,库无
法正常工作。
当dwReason的值是DLL_PROCESS_DETACH的时候则相反,表示动态链接库将被卸载,库程序可以在这里进行一些资源的释放工
作,如将初始化时申请的内存释放,将打开的文件关闭等。 以DLL_PROCESS_ATTACH和DLL_PROCESS_DETACH值进行的调用在库
的生命周期中只可能出现一次。
当dwReason的值是DLL_THREAD_ATTACH的时候,表示应用程序创建了一个新的线程。当某个线程正常终止的时候,dwReason的
值是DLL_PROCESS_DETACH。如果应用程序不是以多线程方式工作的话,就不会有这两种原因的调用;反之,如果应用程序频
繁地创建和结束线程,那么入口函数将不断被调用。
hInstDll是动态链接库的模块实例句柄。当使用这个句柄来装入资源的时候,表示资源是定义在库文件中的,对于动态链接
库来说,获取这个句柄的惟一途径就是保留入口函数的这个参数,如果在DLL_PROCESS_ATTACH时不将这个句柄保存下来的话
,运行时可能就没有其他方法可以获取了。dwReserved参数是系统保留的参数,可以不必理会。
读者可能会问:不是可以通过GetModuleHandle(NULL)函数来获取模块实例句柄吗?是的,但是动态链接库是代表应用程序
运行的,所以,如果在库中调用这个函数,得到的仍然是“宿主”程序的实例句柄,而不是库程序的实例句柄。
在例子程序中,不需要初始化工作,所以仅返回一个TRUE,表示任何情况下,Windows都可以装入这个库文件。动态链接库有
一种很“极端”的应用:纯资源库,这些库仅包含资源而没有任何的功能函数,如字体文件等,对于这些库来说,库中的全
部代码仅是入口函数中用来返回TRUE的那几句,这是库能被正常装入所必须的代码。
2. 导出函数
与写普通的可执行文件相比,动态链接库的设计流程中多了一个文件,那就是定义文件 *.def,源代码目录中还包括一个
Counter.def文件,它的内容是:
EXPORTS _IncCount
_DecCount
文件内容总共只有两行:一个EXPORTS关键字加上两个库中函数的名称,这是用来告诉链接器这两个函数需要导出,也就是说
这两个函数可以被其他程序调用。动态链接库的文件格式是PE格式,每个PE格式文件的文件头中都可以有一个导出表,只有
导出表中列出的函数才可以被其他程序调用,链接器根据def文件的内容在导出表中加入由EXPORTS关键字指定的函数名。
如果库文件中的函数没有在def文件中指定(如例子中的_SetText函数),那么这个函数就仅能被库文件中的代码调用,而无
法在其他应用程序中使用,这是因为库文件的导出表中没有列出它的名称,这样其他程序根本不会知道它的存在。对于这些
函数,可以把它们叫做私有函数。
3. 链接选项
为了生成动态链接库文件,在链接的时候必须使用合适的选项,来看看Counter库文件例子使用的Makefile文件:
DLL = Counter
ML_FLAG = /c /coff
LINK_FLAG = /subsystem:windows /Dll
$(DLL).dll: $(DLL).obj $(DLL).def
Link $(LINK_FLAG) /Def:$(DLL).def $(DLL).obj
.asm.obj:
ml $(ML_FLAG) $<
.rc.res:
rc $<
clean:
del *.obj
del *.exp
del *.lib
编译的时候,使用Ml.exe编译器的方法并没有什么不同,但是使用Link.exe链接程序的时候,必须使用/Dll和/Def选
项,/Dll选项告诉链接器输出文件的格式是动态链接库,/Def:filename.def选项用来指定定义了导出函数名称的def文件名
,在这个例子中,库文件中没有包含资源,如果包含资源的话,链接时还可以指定资源文件名,一个完整的链接参数如下所
示:
Link /DLL /subsystem:windows /Def:filename.def filename.obj filename.res
4. 生成库文件
当使用Link.exe链接器完成链接工作后,链接器生成3个文件,它们分别以dll,lib和exp为扩展名。dll文件就是动态链接库
,而lib文件是供程序开发用的导入库。
回想一下:当在汇编源程序中用到某个动态链接库中的函数时,在源文件的一开始就要用includelib语句指定动态链接库的
导入库,这样链接的时候链接器才知道到哪个库中寻找指定的函数,如果开发的时候没有动态链接库的导入库文件,使用起
来就比较麻烦了。
为了在开发其他程序的时候使用自己编写的动态链接库,就必须提供这个动态链接库的导入库文件,Link.exe考虑了这一点
,所以在生成dll文件的同时也生成了导入库文件。如果dll文件是当做最终应用程序发布的,可以仅发布dll文件;如果是当
做插件供其他人做二次开发用的,那么就要为其他程序员同时提供dll文件和lib文件,并且根据情况提供不同语言使用的头
文件(最后,还要为每个导出函数写一个说明,包括参数的个数、类型和定义等)。目录中还有一个Counter.inc文件,它的
内容如下:
_IncCount proto :dword,:dword
_DecCount proto :dword,:dword
这是为了在汇编程序中使用Counter.dll库文件而书写的include文件。(这样读者就知道MASM32软件包中包含的lib文件和
inc文件是怎么来的了!)
exp文件是输出库文件,这是链接时的一个副产品,一般没有什么用途,我们可以直接将它删掉。