Chinaunix首页 | 论坛 | 博客
  • 博客访问: 56837
  • 博文数量: 27
  • 博客积分: 2000
  • 博客等级: 大尉
  • 技术积分: 300
  • 用 户 组: 普通用户
  • 注册时间: 2009-04-24 17:31
文章分类
文章存档

2011年(1)

2010年(8)

2009年(18)

我的朋友

分类: WINDOWS

2010-04-29 16:39:43

Linker Algorithm
作者:Matt Pietrek  
 
在这个专栏中,我经常讨论一些新技术,至少是还未被广泛使用的技术。然而,随着越来越多的开发者加入到Win32® 程序员队伍中来,有些对于老手来说是老生常谈的问题,对于新手来说却是神秘莫测的。链接器方面的主题就属于这个范畴。Visual Basic® 5.0就使用了一个链接器。事实上,它使用的链接器与Visual C++® 5.0的一样。但是Visual Basic 5.0很好地隐藏了这个事实。如果你仔细观察就会发现,它生成OBJ文件,然后把它们送往Microsoft 链接器。
什么是链接器?它是如何工作的呢?本月我就讨论一些这方面的内容。作为这个专栏研究的一部分,我试图去找一些以前的资料。有趣的是,看起来,这里我要讲的内容不是绝版了,就是不再包含于MSDN光盘中了,即使链接器技术几乎影响到每一个Windows程序员。
为这个专栏考虑,我用Microsoft 的LINK.EXE作为标准的链接器。(其它的链接器,例如Borland的TLINK32,可能与我这里描述的有少许不同。)在以后的专栏中,我会讲一些关于Microsoft 链接器方面更深入、更有趣、更有用的内容。首先,我需要给链接器一个概括的定义,然后再细化。链接器的工作是把一个或多个目标模块(典型地,就是OBJ文件)组合成一个可执行文件(也就是EXE或DLL)。但是这又引发一个问题:什么是目标模块呢?
目标模块是由一个程序产生的,这个程序把人类可读的文本转换成CPU可以理解的机器代码和数据。对于C++来说,C++编译器读取C++源文件。对于汇编语言来说,汇编程序(例如MASM)读取汇编语言(ASM)文件,这种文件包含与CPU使用的代码和数据等价的指令。在Visual Basic 5.0中,输入文件就是工程中的FRM、BAS和CLS文件。这个概念对于诸如Fortran之类其它大部分语言都是正确的。
目标模块中的主要部分是机器代码和数据。组成代码和数据的原始的字节被存储在连续的块中,这种块叫做节(section)。例如,Microsoft编译器把他们的机器代码放进一个叫做.text的节中,把数据放进一个叫做.data的节中。这些名字除了提示节的用途之外并没有什么特别的意义。其它编译器可以(并且也是这么做的)对他们处理的节使用不同的名字。如果你曾经为MS-DOS® 或16位 Windows®编写过程序,你把我前面讲的内容中的“节”都换成“段(segment)”,那么我讲的大部分内容也还是正确的。如果你系统中安装的有Visual C++,你可以使用DUMPBIN程序来看一看OBJ文件中的节。执行以下的命令行:
  DUMPBIN 目标文件名
目标文件名表示一个OBJ文件的名字。图1给出了一个常见节的片段。你可以用DUMPBIN对编译过的C++程序中的OBJ文件试一下,例如Visual C++\LIB目录中的CHKSTK.OBJ文件:
Dump of file CHKSTK.OBJ
File Type: COFF OBJECT
Summary
0 .data
2F .text
 
 
图 1 常见节

描述
.text
机器代码指令。
.data
已初始化的数据。
.rdata
只读数据。OLE的GUID就保存在这里,还有其它内容。
.rsrc
资源。由资源编译器生成,被放进RES文件中。链接器把它复制到可执行文件中。
.reloc
基址重定位信息,它由链接器产生。OBJ文件中并没有。
.edata
导出函数表。由链接器创建,被放进EXP文件中。链接器把它复制到可执行中。
.idata
可执行文件中的导入函数表。
.idata$XXX
导入函数表的一部分。生成库的程序在导入库中创建这些节。链接器在生成可执行文件时把它们组合成最终的.idata节。
.CRT
可执行文件中的初始化表和关闭指针,它们供Visual C++运行时库使用。
.CRT$XXX
在链接器把它们组合进可执行文件之前存在于OBJ文件中的初始化和关闭指针。
.bss
未初始化数据。
.drectve
OBJ文件中包含链接器指令的节。它们并不被复制到可执行文件中。
.debug$XXX
OBJ文件中的COFF符号表信息。

编译器或汇编器的输出有一个奇怪的名字叫做编译单元。然而我们大部分人都认为它们只不过是OBJ文件。链接器最重要的工作就是收集所有编译单元并且从不同的编译单元中组合所有的节。当然,如果事情真的这么简单,那么链接器顶多不过是一个连接数据的奇特程序。事实上,链接器工作中的复杂部分是处理修正(重定位)问题。后面将详细叙述。
你可能想知道链接器是如何决定在最终的可执行文件中排列各个OBJ文件中的代码和数据节的。很明显,链接器有一整套详细规则要遵守。事实上,链接器的任务太复杂,因此它不得不对输入文件进行两遍处理。链接器在第一遍处理时通篇查看要做的工作。在第二遍处理中,它应用所有规则产生可执行文件。
虽然对链接器规则的描述不能面面俱到,但我仍然会涉及它的主要部分。链接器的主要规则就是从各个OBJ文件中提取代码和数据,并把它们放进最后的可执行文件中。假设你给链接器三个OBJ文件,这三个文件中的代码与数据最后按某种方式被组合进可执行文件中。然而链接器并不是简单地把各个文件中的所有原始节一个挨一个地放在一起。相反,链接器把所有名字相同的节组合(也就是连接)在一起。例如,如果三个OBJ文件中每个都有.text节,最后的可执行文件中只有一个.text节,链接器按它们出现的顺序把它们组合在一起。
链接器遵守的另一个规则就是,可执行文件中节的顺序是由链接器处理节时遇到每一个节的顺序决定的。链接器严格按照命令行给出的OBJ文件的顺序进行处理。但是组合具有相同名字的节这条规则优先。
图2显示了三个OBJ文件,A.OBJ、B.OBJ和C.OBJ。每个文件都有三个节,其中.text节和.data节是三者共有的,但是在不同的文件中的节的位置不同。它们都有一个和它们的源文件(也就是a.asm、b.asm和c.asm)相关的节。调用LINK,使用下面的命令行:
  LINK A.OBJ B.OBJ C.OBJ
节的顺序(以及名字相同的节是如何被组合的)如图2所示。你可以从下载源文件和OBJ文件。之所以提供OBJ文件主要是因为,可能你没有MASM或与其兼容的汇编程序,你可以使用OBJ文件,用“LINK B.OBJ A.OBJ C.OBJ”这样的命令行来测试一下。

图 2 A.OBJ,B.OBJ,和C.OBJ
记住了这两个规则,再理解链接器在MS-DOS和16位Windows上是如何工作的就容易了。但是,Win32链接器又在前面讲的内容上加了几条规则。首先就是包含“$”字符的节名规则。如果一个节名中包含“$”字符(例如.idata$4),“$”字符及其后续字符在可执行文件中都被移除了。然而在链接器修改这些名字之前,它是以“$”字符之前的字符作为节名来进行组合的。“$”字符之后的字符用以对OBJ文件的节进行排序以产生最终的可执行文件。这些节是按“$”字符之后的字符的字母顺序来存储的。例如三个分别叫做foo$c、foo$a和foo$b的节在最终的可执行文件中将被组合成一个叫做foo的节。这个节中最前面的是foo$a中的数据,接着是foo$b中的数据,最后是foo$c中的数据。这种名字中包含“$”字符的节的自动组合有多种用途。后面我讨论导入函数时,你会看到一个例子。它也被用于创建C++构造函数和析构函数静态初始化时所需的数据表。
除了“$”字符这个组合规则外,Win32链接器还有一些其它规则。拥有代码属性的节(.text节)有特别的优先权,它们被放在可执行文件的最前面,紧接着代码的是由在编译时未指定初始值的全局数据(例如在C++中int i;语句定义的全局变量)组成的未初始化数据节(.bss节),接下来是已初始化的数据(.data节),以及链接器产生的数据节(例如.reloc节)。
未初始化的数据通常被编译器放在一个叫做.bss的节中。现在已很少能在可执行文件中看到.bss节。Microsoft链接器把.bss节合并到了.data节中,而.data节是被编译器使用的主要的已初始化的数据节。但是请注意,这是针对希望运行于非Posix子系统,并且子系统版本大于3.5的可执行文件来说的。其它未初始化数据的节由链接器单独处理(也就是说,它们并未被合并)。
现在倒着来看可执行文件。如果在OBJ文件中有.debug节,那么它被放在文件的最后。如果没有,链接器就把.reloc节放在最后,因为在大多数情况下,Win32加载器不需要读取重定位信息。减少需要读取的可执行文件的内容可以减少加载时间。关于重定位的内容将在后面讨论。
Win32下另外一个不遵守两个基本规则的是可移除节。这些节存在于OBJ文件中,但是链接器并不把它们复制到可执行文件中。这些节通常有LINK_REMOVE和LINK_INFO属性(见WINNT.H文件),并且被命名为.drectve。Microsoft编译器产生这些节是为了向链接器传递信息。看一下由Visual C++ 编译后产生的OBJ文件,你会看到在.drectve节中的数据就像下面这个样子:
  -defaultlib:LIBC -defaultlib:OLDNAMES
这些数据是传递到链接器的命令行参数。当你使用C++的__declspec(dllexport)修饰符时,会看到更多这方面的证据。例如:
  void __declspec(dllexport) ExportMe( void ){...}
将导致.drectve节包含:
  -export:_ExportMe
可以看一下LINK的命令行参数列表,绝对能看到-export也是其中的一个参数。
 
修正和重定位
为什么编译器不直接由源文件生成可执行文件,从而省略链接器呢?主要原因是,大部分程序并不是仅包含一个源文件。编译器专注于由单个的源文件产生等价的机器代码。由于一个源文件可能引用其它源文件中的代码或数据,而编译器无法精确地产生调用那个函数或访问那个变量的正确代码(编译时,其他源文件可能还没写出来呢)。编译器唯一的选择就是在它产生的文件中附加包含描述外部代码或数据的额外信息。这种对外部代码或数据的描述信息就是修正(Fixup)。说得更明白一点就是,编译器产生的访问外部函数或变量的代码是不正确的,必须在后面修正。
假设在C++中调用一个名为Foo的函数:
//...
Foo();
//...

由32位C++编译器产生的机器代码应该是:
E8 00 00 00 00
 
0xE8是CALL指令的机器码。接下来的DWORD应该是Foo函数的偏移(相对于CALL指令的偏移)。很明显,Foo函数相对于CALL指令的偏移不是0字节。如果你执行这段代码,它并不会按你原本期望的方式运行。编译器产生的这段代码有错误,它需要被修正。
在上面的例子中,链接器需要把CALL指令的机器码后面的DWORD替换成Foo函数的正确地址。在可执行文件中,链接器将用Foo函数的相对地址改写这个DWORD。链接器怎么知道它需要被修正呢?是修正记录(Fixup Record)告诉它的。链接器是怎么知道Foo函数的地址的?链接器知道可执行文件中的所有符号的相关信息,因为正是它负责排列和组合可执行文件中的各个部分的。
现在来看一下修正记录。对于基于Intel的OBJ文件来说,通常遇到的修正记录有三种。第一种是32位相对修正,也就是REL32修正。(它对应于WINNT.H中的IMAGE_REL_I386_REL32这个宏定义。)在上面的例子中,对Foo函数的调用应该有一个REL32类型的修正记录,并且这个记录中应该包含一个DWORD类型的偏移,链接器需要用合适的地址值替换这个偏移处里面的内容。如果你对由上面的代码产生的OBJ文件运行
  DUMPBIN /RELOCATIONS

你会看到类似下面的内容:
Offset    Type         Applied To         Symbol Index     Symbol Name
-------- -----------  --------------     -------------    ------------        00000004  REL32        00000000           7                _Foo
 
  这个修正记录表示链接器需要计算函数Foo的相对偏移,并把它写到这个节内的偏移0x00000004处。这个修正记录仅在链接器创建可执行文件之前需要,之后它就被丢弃了,不会出现在可执行文件中。既然这样,那为什么大部分可执行文件中还一个.reloc节呢?这正是第二种类型的修正记录发挥作用的地方。设想以下程序:
int i;  //全局未初始化变量
int main()
{
  i = 0x12345678;
}
 
Visual C++将为上述赋值语句生成以下机器指令:
MOV DWORD PTR [00406280],12345678

真正有趣的是指令中的[00406280]这一部分。它引用的是内存中的一个固定位置,假定可执行文件的默认加载地址0x400000,包含变量i的那个DWROD应该位于可执行文件的默认加载地址之上的0x6280字节处。现在,想象一下,如果可执行文件不能被加载在默认加载地址,那会怎么样呢?假设Win32加载器把它加载到默认加载地址2M之上的地方(也就是,被加载在地址0x600000处)。如果是这样,指令中的[00406280]应该被调整为[00606280]。
  这正是DIR32(Direct32)大显身手的时候。它们能够表示哪里需要相对于实际地址(直接地址)做一些修改。这也意味着可执行文件的加载地址很重要。当创建可执行文件时,加载器利用OBJ文件中的DIR32类型的修正记录来创建.reloc节。在OBJ文件上运行
  DUMPBIN /RELOCATIONS
会出现类似下面的重定位内容:
Offset    Type         Applied To         Symbol Index     Symbol Name
-------- -----------  --------------     -------------    ------------ 00000005  DIR32        00000000           4                _i
 
这个修正记录表示链接器需要计算变量i的32位绝对地址,并且把它写到节内偏移0x00000005处。
可执行文件中的.reloc节基本上就是可执行文件中的一系列地址,这些地址是由于默认加载地址与实际加载地址不同,需要被修正的地方。默认情况下,Win32加载器并不需要.reloc节。然而当Win32加载器需要加载可执行文件到一个不同于其默认加载地址的地址时,.reloc节可令那些包含了(代码与数据的)绝对地址的机器指令得以修正。
第三种类型在Intel平台上的OBJ文件中比较常见,它就是DIR32NB(Direct32,No Base),供调试信息使用。链接器的次要工作之一就是创建调试信息,这种信息中包含函数和变量名称以及它们的地址。由于只有链接器知道函数和变量到哪里结束,所以DIR32NB修正记录被用来指示调试信息中需要函数或变量地址的地方。DIR32和DIR32NB的关键区别在于DIR32NB中的修正值不包含可执行文件的默认加载地址。
 
在一些情况下,把两个或多个OBJ文件组合成单个文件,然后送往链接器更有价值。这方面的经典例子就是C++运行时库(RTL)。C++ RTL是由许多源文件被编译之后,产生的由所有OBJ文件组合而成的一个库。对Visual C++ 来说,标准的单线程静态运行时库是LIBC.LIB。RTL库还有其它版本,诸如调试版(例如LIBCD.LIB)和多线程版(LIBCMT.LIB)。
库文件通常以.LIB为扩展名。由库文件头和包含在OBJ文件中的原始数据组成。库文件头用于告诉链接器哪个符号(函数和变量)可以在该库文件里面的OBJ文件中找到,同时也指明这个符号存在于哪个OBJ中。你可以通过使用DUMPBIN /LINKERMEMBER来观察库文件的内容。不用知道其中缘由,你会发现如果你指定选项:1或:2,DUMPBIN的输出会更具可读性。例如用Visual C++ 5.0的PENTER.LIB文件,使用以下命令行
  DUMPBIN /LINKERMEMBER:1 PENTER.LIB
 
它的部分输出结果如下:
6 public symbols
180 _DumpCAP@0
180 _StartCAP@0
180 _StopCAP@0
180 _VERSION
180 __mcount
180 __penter
 
每个符号前面的180表示那个符号(例如)可以在从库文件开头算起的0x180字节处的OBJ文件中找到。如你所见,PENTER.LIB里面只有一个OBJ文件。更复杂的LIB文件包含有多个OBJ文件,因此符号前面的偏移会有所不同。
不像在命令行上传递OBJ文件那样,链接器并不把一个库文件里面包含的所有OBJ文件都链接到最终的可执行文件中。事实上,源文件从哪些OBJ文件中引用了符号,链接器才会将这些OBJ文件链接到最终的可执行文件中。换句话说,链接器命令行中明确指定的OBJ文件一定会被链接到最终的可执行文件中(不管实际有没有用到),而LIB文件中的OBJ文件只有在被引用时才被链接进去。
库中的符号可以三种方式被引用。首先,直接访问明确指定的OBJ文件中的符号。例如,如果在我的一个源文件中调用C++的printf函数,编译器在我的OBJ文件中将会产生一个引用(和修正)。当创建可执行文件时,链接器会搜索它的LIB文件以查找包含printf代码的OBJ文件,并且链接相应的OBJ文件。
第二,可能存在一个间接引用。“间接”意味着通过第一种方法包含的OBJ引用了库中另外一个OBJ文件中的符号。而这第二个OBJ文件可能又引用了库中第三个OBJ文件中的符号。链接器的艰苦工作之一就是,即使符号通过49级间接引用,它也必须跟踪并且包含引用的每一个OBJ文件。
当查找符号时,链接器按命令行上给出的LIB文件的顺序进行搜索。一旦在某个库中找到一个符号,那么这个库就变成了首选库,首先在它里面搜索其它所有符号。一旦某个符号在这个库中找不到,这个库就失去了它的首选地位。此时链接器搜索它的列表中的下一个库。(要获得更详细的技术信息,请参考Microsoft知识库文章Q31998,网址为。)
现在,我们把目光转向导入库。在结构上,导入库与普通库并无区别。在解析符号时,链接器并不知道导入库与普通库的区别。它们的关键区别在于,导入库中的OBJ文件并没有相应的编译单元(没有相应的源文件)。实际上,是链接器基于正在创建的可执行文件所导出的符号产生导入库的。换句话说,链接器在创建可执行文件的导出表的同时也创建了相应的导入库来引用这些符号。这引出了下一个主题——导入表。
 
创建导入表
Win32最基础的特性之一就是能够从其它可执行文件中导入函数。关于导入的DLL和函数的所有信息都存储在可执行文件中的导入表(Import Table)中。当它单独成节时,这个节的名字叫.idata
导入对Win32可执行文件来说是至关重要的,因此,如果说链接器并不知道导入表方面的专业知识,那真是令人难以置信。但事实的确如此。换句话说,链接器并不知道、也不关心你调用的函数是在另外的DLL中,还是在导入库中。链接器在这一点表现得特别聪明。它仅仅简单地依照上面描述的节组合规则和符号解析规则就创建了导入表,看起来好像它并没有意识到这个表的重要性。
让我们看一下一些导入库的片段,看一看链接器是怎样出色地完成这个任务的。图3是对USER32.LIB导入库运行DUMPBIN时的部分输出结果。假设你调用了ActivateKeyboardLayout这个API。在你的OBJ文件中可以找到一个的修正记录。从USER32.LIB的头部信息,链接器知道这个函数可以在文件偏移0xEA14处的OBJ中找到。因此,链接器忠实地在最终的可执行文件中包含了这个OBJ文件中指定的内容(见图3)。
 
图 3 导入表
1121 public symbols
EA14 _ActivateKeyboardLayout@8
...
...
Archive member name at EA14: USER32.dll/
...
...
SECTION HEADER #2
.text name
RAW DATA #2
00000000 FF 25 00 00 00 00 .%....
...
...
SECTION HEADER #4
.idata$5 name
RAW DATA #4
00000000 00 00 00 00 ....
...
...
SECTION HEADER #5
.idata$4 name
RAW DATA #5
00000000 00 00 00 00 ....
...
...
SECTION HEADER #6
.idata$6 name
RAW DATA #6
00000000 00 00 41 63 74 69 76 61 | 74 65 4B 65 79 62 6F 61 ..Activa|teKeyboa
00000010 72 64 4C 61 79 6F 75 74 | 00 00 rdLayout|..
...
...
COFF SYMBOL TABLE
...
...
003 00000000 SECT2 notype () External | _ActivateKeyboardLayout@8
 
从图3可以看出,牵涉到了OBJ文件中的许多节,包括.text.idata$5.idata$4.idata$6。在.text节中是一个JMP指令(机器码0xFF 0x25)。从图3最后的COFF符号表可以看出,_ActivateKeyboardLayout@8被解析到了.text节的这个JMP指令上。因此链接器把你对ActivateKeyboardLayout的调用转换成了对导入库的OBJ中的.text节的JMP指令的调用。
在可执行文件中,链接器把所有的.idata$XXX节组合成单个的.idata节。现在回忆一下链接器组合节名中带有“$”字符的节时要遵守的规则。如果从USER32.LIB导入的还有其它函数,它们的.idata$4.idata$5.idata$6这些节也要放入其中。结果就形成了所有的.idata$4节组成了一个数组,所有的.idata$5节组成了另一个数组。如果你熟悉“导入地址表(Import Address Table,IAT)”的话,这实际上就是它的创建过程。
最后,注意节.idata$6的原始数据中包含了字符串“ActivateKeyboardLayout”。这就是导入地址表中有被导入函数的名字的原因。重要的一点是,对链接器来说,创建导入表并非难事。它只是依照我前面描述的规则,做它自己的工作而已。
 
创建导出表
除了为可执行文件创建导入表外,链接器还负责创建导出表。这项工作难易参半。在第一遍中,链接器的任务是收集关于所有导出符号的信息并创建导出函数表。在此期间,链接器创建导出表,并且把它写入到OBJ文件中一个叫.edata的节中。这个OBJ文件除了扩展名是.EXP而不是.OBJ外,其它地方都符合标准。你使用DUMPBIN检查一下这些EXP文件的内容就知道了。
在第二遍中,链接器的工作就很轻松了。它只是把EXP文件当作普通的OBJ文件来对待。这也意味着OBJ文件中的节.edata应该被包含到可执行文件中。如果你在可执行文件看到.edata节,它就是导出表。但是近来很少能找到.edata节了。好像是如果可执行文件使用了Win32控制台或GUI子系统,链接器就会自动合并.edata节和.rdata节,如果其中一个存在的话。
 
总结
  很明显链接器做的工作要比我这里描述的多得多。例如生成某种类型的调试信息(例如CodeView信息)是链接器全部工作中的重要部分。但是生成调试信息并不是必须的,因此我没有花什么时间来描述它。同样,链接器应该能够创建MAP文件,这种文件包含可执行文件中公共符号的列表,但是它同样不是必须的功能。
虽然我提供了许多复杂的背景知识,但是链接器的核心是简单地把多个编译单元组合成可执行文件。第一个基本功能是组合节;第二个功能是解析节之间的相互引用(修正)。结合一下诸如导出表之类系统特定的数据结构方面的知识,你就基本上掌握了这个功能强大且重要的工具。
 
 

译者:SmartTech 电子信箱:zhzhtst@163.com

 

http://blog.csdn.net/SmartTech/archive/2007/04/17/1567192.aspx 原帖,很多经典译文
_________________________________________________________________________

目前以lib后缀的库有两种,一种为静态链接库文件(Static Libary,以下简称“静态库”),另一种为动态连接库文件(DLL)的导入库(Import Libary,以下简称“导入库”)。

静态库是一个或者多个obj文件的打包,所以有人干脆把从obj文件生成lib的过程称为Archive,即合并到一起。静态lib好比是个箱子。你链接一个静态库,如果其中有错,会准确的找到是哪个obj有错。

动态库一般会有对应的导入库,方便程序象使用静态库那样载入动态链接库,否则你可能就需要自己调LoadLibary加载入DLL文件,然后再手工GetProcAddress获得对应函数了。有了导入库,你只需要链接导入库后按照头文件函数接口的声明调用函数就可以了。

导入库和静态库
的区别很大,他们实质是不一样的东西。静态库本身就包含了实际执行代码、符号表等等,而对于导入库而言,其实际的执行代码位于动态库中,导入库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息。(导入库是dll库的辅助品,有了更方便,没有也行。导入库可以象使用静态库那样引用dll里的函数,这样连接操作得以完成(即使此时还没有对应的dll),用户程序加载运行时候再真正加载dll文件进内存。)

以下是实际上很多开源代码发布的几种惯用方式:

1. 预编译的开发包:包含一些.dll文件和一些.lib文件。其中这里的.lib就是导入库,而不要错以为是静态库。但是引入方式和静态库一样,要在链接路径上添加这些.lib的路径。而.dll则最好放到最后产生的应用程序exe执行文件相同的目录。这样运行时,就会自动调入动态链接库。

2. 用户自己编译:下载的是源代码,按照readme自己编译。生成很可能也是.dll + .lib(导入库)的库文件

3. 如果你只有dll,并且你知道dll中函数的函数原型,那么你可以不用导入库,直接在自己程序中使用LoadLibary调入DLL文件,GetProcAddress 获得函数的地址。

DLL:
动态链接库 (DLL) 是用作共享函数库的可执行文件。动态链接提供了一种方法,使进程可以调用不属于该进程可执行代码的函数。函数的可执行代码位于一个 DLL 中,该 DLL 包含一个或多个已被编译、链接的函数,并与使用它们的进程分开存储。DLL 还有助于共享数据和资源。多个应用程序可同时访问内存中单个 DLL 副本的内容。

动态链接与静态链接的不同之处在于它允许可执行模块(.dll 文件或 .exe 文件)仅包含在运行时定位 DLL 函数的可执行代码所需的信息。在静态链接中,链接器从静态库获取所有被引用的函数,并将静态库同代码一起放到可执行文件中。

使用动态链接代替静态链接有若干优点。DLL 节省内存,减少交换操作,节省磁盘空间,更易于升级,提供售后支持,提供扩展 MFC 库类的机制,支持多语言程序,并使国际版本的创建轻松完成。

API 就是应用程序编程接口。它是能用来操作组件、应用程序或者操作系统的一组函数。典型的情况下,API 由一个或多个提供某种特殊功能的 DLL 组成。
这些DLL 文件中包含了在 Windows下运行的任何应用程序都可调用的函数。运行时,DLL 中的函数动态地链接到调用它的应用程序中。无论有多少应用程序调用 DLL 中的某个函数,在磁盘上只有一个文件包含该函数,且只在它调入内存时才创建该 DLL。

您听到最多的 API 可能是 Windows API,它包括构成 Windows 操作系统的各种 DLL。每个 Windows 应用程序都直接或间接地与 Windows API 互动。Windows API 保证 Windows 下运行的所有应用程序的行为方式一致。
注意 随着 Windows 操作系统的发展,现已发布了几个版本的 Windows API。Windows 3.1 使用 Win16 API。Windows NT、Windows 95 和 Windows 98 平台使用 Win32 API。

除 Windows API 外,其他一些 API 也已发布。例如,邮件应用程序编程接口 (MAPI) 是一组可用于编写电子邮件应用程序的 DLL。

API 传统上是为开发 Windows 应用程序的 C 和 C++ 程序员编写的,但其他的编程语言(包括VBA)也可以调用 DLL 中的函数。因为大部分 DLL 主要是为 C 和 C++ 程序员编写和整理说明的,所以调用 DLL 函数的方法与调用 VBA 函数会有所不同。在使用 API 时必须了解如何给 DLL 函数传递参数。

警告:调用 Windows API 和 其他 DLL 函数可能会给您的应用程序带来不良影响。从自己的代码中直接调用 DLL 函数时,您绕过了 VBA 通常提供的一些安全机制。如果在定义或调用 DLL 函数时出现错误(所有程序员都不可避免),可能会在应用程序中引起应用程序错误(也称为通用性保护错误,或 GPF)。最好的解决办法是在运行代码以前保存该项目,并确保了解 DLL 函数调用的原理。

LIB命令 创建标准库、导入库和导出文件,在生成 32 位程序时可将它们与 LINK 一起使用。LIB 在命令行下运行。
可在下列几种模式下使用 LIB命令:
1.生成或修改 COFF 库
2.将成员对象提取到文件中
3.创建导出文件和导入库
这些模式是互斥的;每次只能以一种模式使用 LIB命令。

____________________________________________________________________

转:DLL的导入库中是什么信息

当链接程序链接生成DLL文件时,链接程序要查找关于输出变量,函数,或C++类的信息,并自动生成一个lib文件--导入库。该lib文件包含一个DLL输出的符号列表。如果要链接引用该DLL的输出符号,该lib文件一般是需要的(使用LoadLibary + GetProcAddress除外)。
 
使用导入库的情况下,当应用程序调用一个DLL的函数时,是怎么进行的呢?
答案是在进程的主线程开始运行之前,由加载器完成。
加载器根据输入节(.idata)中DLL的名字,按照windows的搜索路径搜索该DLL,找到后DLL映射到进程的地址空间,这时该DLL中对应于输入节中的各个符号的地址就可以确定了,加载器在这个时候将地址重新填入可执行模块的输入节中,动态连接完成。

这就是使用lib命令就能将def文件生成lib文件的原因。
(1)做一个def文件mytest.def,内容如下:
LIBRARY
 foo.dll
EXPORTS
 foo

(2)生成lib文件:
c:\> lib /def: mytest.def /machine: i386 /out: mytest.lib
 
(3)做一个试验程序:(在工程文件中加入mytest.lib)
extern "C" void foo();
void main()
{
 foo();
}
 
编译连接都通过。
 
 
阅读(676) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~