.lib是一种档案名称后缀,代表的是静态数据连线库,在windows作业系统中起到连结程式和函式(或子过程)的作用,相当于Linux中的·a或·o、.so档案。
基本介绍
- 中文名lib档案
- 外文名lib File
- 选择通过vc自带的depends查看dll接口
- 分类静态与动态之分
意义
LIB档案中存放的是函式调用的信息,值得一提的是资料库有静态资料库(.lib档案)和动态资料库(.dll档案)。
静态编译
静态编译将导出声明和实现都放在lib中。编译后所有代码都嵌入到宿主程式。
静态编译的优点是编写出来的程式不需要调用DLL和载入函式,直接可以当成程式的一部分来使用。
静态编译的缺点也是显而易见的,使用静态编译的程式体积会比动态编译大,原因是函式的实现被嵌入为程式代码的一部分。
动态编译
动态LIB档案相当于一个C语言中的h档案,是函式导出部分的声明,而不将实现过程嵌入到程式本身中,编译后只是将函式地址存在宿主程式中,运行到调用函式是调用DLL并载入函式来实现函式的具体操作。
详细说明
LIB档案是不对外公开的,除非有专门的LIB查看工具,否则不能查看LIB档案中对函式的具体实现过程
有几个选择
1、如果你查看有同名的dll档案,可以通过vc自带的depends查看dll接口
2、通过msdn看你使用的该lib包含的函式名,来查找其对应的头档案,头档案里面有整个lib的函式声明(可能不全)
3、查看vc或者其他工具安装目录下的src目录,查看函式的代码
4、使用lib档案的方法
1-在object/library modules使用全路径名;
2-把.lib放在VC的Lib目录中
3-修改project setting的Link->Input中的Addtional library path,加入你的目录。
LIB档案是库档案(与DLL档案相类似),供其它程式调用的,直接打不开。
5、查看LIB和DLL档案都可以通过OLLYDBG中LOADDLL外挂程式来反彙编查看各个函式的过程。
内容
一个lib档案是obj档案的集合。,其中还夹杂着其他一些辅助信息,目的是为了让编译器能够準确找到对应的obj档案。我们可以通过tlib.exe(在tc2.0下的根目录)来对lib档案进行操作,你可以把自己生成的obj档案通过tlib命令加入到一个lib档案中,也可以把lib档案内的obj档案进行删除操作,还可以把内部的obj档案给提取出来。明白了lib档案的大致结构以及对它的具体操作,在学习C语言的过程中,就会又多了一个切入点对C语言具体实现进行研究。
使用步骤
在command下,把当前目录设定为tlib.exe所在目录,然后输入tlib命令回车,此时显示的内容就是对tlib命令的详细解释,语法如下
Syntax: TLIB libname [/C] [/E] commands, listfile
libname library file pathname
commands sequence of operations to be performed (optional)
listfile file name for listing file (optional)
A command is of the form: <symbol>modulename, where <symbol> is:
+ add modulename to the library
- remove modulename from the library
extract modulename without removing it
-+ or +- replace modulename in library
- or - extract modulename and remove it
/C case-sensitive library
/E create extended dictionary
具体解释
tlib libname [/C] [/E] commands, listfile
/C:大小写敏感标誌。该选项不常用,此参数为可选项。
/E:建立扩展字典。建立扩展字典可以加速大的库档案的连线过程,此参数同样为可选项。
操作命令(可选项)
+ obj档案名称 把指定obj档案添加到lib档案中
- obj档案名称 把指定obj档案从lib档案中删除
obj档案名称 导出指定的obj档案(导出后对应的obj档案在lib档案内仍然存在)
-+ obj档案名称 替换指定的obj档案(前提是在lib档案中存在与指定obj档案同名的obj)
- obj档案名称 导出指定的obj档案(导出后把对应的obj档案从lib档案内删除)
lib档案中obj档案列表(可选项)
此参数说明了命令运行后,生成的对应lib档案的列表档案名称。它记录了当前lib档案内obj档案列表
与dll区别
(1)lib是编译时需要的,dll是运行时需要的。
如果要完成原始码的编译,有lib就够了。
如果要使动态连线的程式运行起来,有dll就够了。
在开发和调试阶段,最好都有。
(2)一般的动态库程式有lib档案和dll档案。lib档案是必须在编译期就连线到应用程式中的,而dll档案是运行期才会被调用的。如果有dll档案,那幺对应的lib档案一般是一些索引信息,具体的实现在dll档案中。如果只有lib档案,那幺这个lib档案是静态编译出来的,索引和实现都在其中。静态编译的lib档案有好处给用户安装时就不需要再挂动态库了。但也有缺点,就是导致应用程式比较大,而且失去了动态库的灵活性,在版本升级时,要发布新的应用程式才行。
(3)在动态库的情况下,有两个档案,一个是引入库(.LIB)档案,一个是DLL档案,引入库档案包含被DLL导出的函式的名称和位置,DLL包含实际的函式和数据,应用程式使用LIB档案连结到所需要使用的DLL档案,库中的函式和数据并不複製到执行档中,在应用程式的执行档中,存放的不是被调用的函式代码,而是DLL中所要调用的函式的记忆体地址,这样当一个或多个应用程式运行时再把程式代码和被调用的函式代码连结起来,从而节省了记忆体资源。从上面的说明可以看出,DLL档案必须随应用程式一起发行,否则应用程式将会产生错误。
载入方法
直接加入
在VC中打开File View一页,选中工程名,单击滑鼠右键,然后选中"Add Files to Project"选单,在弹出的档案对话框中选中要加入DLL的LIB档案即可。
设定
打开工程的 Project Settings选单,选中Link,然后在Object/library modules下的文本框中输入DLL的LIB档案。
程式代码
加入预编译指令#pragma comment (lib,".lib"),这种方法优点是可以利用条件预编译指令连结不同版本的LIB档案。因为,在Debug方式下,产生的LIB档案是Debug版本,如Regd.lib;在Release方式下,产生的LIB档案是Release版本,如Regr.lib。
当应用程式对DLL的LIB档案载入后,还需要把DLL对应的头档案(.h)包含到其中,在这个头档案中给出了DLL中定义的函式原型,然后声明。
详解
节的概念
Lib格式只有四种类型的节(Section),即First Sec,Second Sec,Longname Sec和Obj Sec;其中Second Sec与Longname Sec是可选节,很多Lib档案中都没有。而开头的Singature只是一个标识,它相当于COFF目标档案中的魔法数字。它是一个长度为8的字元串,值为“!<arch>\n”。
First Sec 顾名思义,就是第一个节。它包含了库中所有的符号名以及这些符号所在的目标档案在库中的位置(绝对偏移)。
Second Sec 就是第二节。它的内容和First Sec是相同的。不同的是,Second Sec是一个有序表,通过它来查找库中的符号比通过First Sec来查找要快很多。
Longname Sec 是长名称节。这一节是一个字元串表。它包含了所有长目标档案名称。如果后面的Obj Sec中没有给出相应的目标档案名称,我们就要到这一节中来查找。
Obj Sec 就是目标档案节。这些节中存储着不同的目标档案的原始数据。
在库档案中,每一节都有两个部分。一个部分是头,另一个部分才是该节的数据;数据紧跟在头的后面。头描述了该节数据的类型、长度等信息。这些头的格式都是相同的。其结构用C语言描述如下
typedef struct {
char Name[16]; // 名称
char Time[12]; // 时间
char UserID[6]; // 用户ID
char GroupID[6]; // 组ID
char Mode[8]; // 模式
char Size[10]; // 长度
char EndOfHeader[2];// 结束符
} SectionHeader;
可以看到,头中的数据全都是字元串。用字元串的好处是可以提高格式的兼容性,因为在不同的机器上,数据的排列方式是不同的。有的机器是以Little-Endian方式工作,还有的是以Big-Endian方式工作,它们互不兼容(这两种方式的区别!?请看我的《COFF格式》一文,其中的档案头一节有说明)。用字元串就不会有这种问题(后面我们将会遇到)。但它也有不方便的地方,就是必须把字元串转换成数值,多了一个步骤。
在这个结构中,最常用的Name、Size以及EndOfHeader三个成员。Name就是节的名称啦!Size也很好理解,就是该节数据的长度。其内容为“`\n”(注意,这里没有打错,是两个字元“`”和“\n”)。怎幺样?有点奇怪吧?为什幺要有这个结束符?每一节的头长度一定,每节中的数据长度也知道。按顺序向下读不行吗?答案是不行!因为每一节之间存在间隙!通常是一个位元组或零个位元组。如果是零个位元组倒好,按顺序向下读是OK的。可是如果不为零的话,这样读就要错位了。要知道错位没有,只好用一个结束符来定位了。如果在读头的时候发现结束符不对,那就要一个位元组一个位元组地向下查找,直到找到结束符,才能算是对齐了。切记!切记!
,通过First Sec或Second Sec中给出的偏移来读数据就不存在这个问题。不会发生错位,放心读吧!
First Sec
第一节,通常就是Lib中的每一个小节。它的名称是“/”。其数据部分的结构如下
typedef struct {
unsigned long SymbolNum; // 库中符号的数量
unsigned long SymbolOffset[n]; // 符号所在目标节的偏移
char StrTable[m]; // 符号名称字元串表
}FirstSec;
第一个成员SymbolNum是符号的数量。注意!它是以Big-Endian方式储存的(x86平台上的数据是以Little-Endian方式储存的。这里应该注意转换。后面给出的convert函式可以在Little-Endian格式与Big-Endian格式之间进行相互转换)。
第二个成员SymbolOffset是一个数组,它的长度n就是符号的数量,也就是SymbolNum。这个数组储存了每一个符号所在的目标节的偏移。我们可以方便地通过它来查找符号所在的目标档案。注意!它也是以Big-Endian格式储存的。
第三个成员StrTable是一个字元串表,它的长度m就是SectionHeader.Size的值减去(SymbolNum+1)4。其结构很简单,就是一堆以‘\0’结尾的字元串(和COFF档案中的字元串表结构相同)。在有的系统中,它还可能是以“/\n”这两个字元结尾的字元串的集合。
很简单的一个结构,不过有两个成员的长度是不定的。怎幺才能方便地从Lib中读出这些数据,留给大家自己想吧!下面我只给出一个进行Little-Endian与Big-Endian互转的函式。
inline void convert(void p // 要转换的数据的指针
,size_tsize = 4 // 数据的长度,long为4,short为2
) {
char buf=(char)p;
char temp;
for ( size_t i=0;i<size/2;i++ ) {
temp=buf[i];
buf[i]=buf[size-i-1];
buf[size-i-1]=temp;
}
}
Second Sec
第二节
这一节与第一节很相似!它通常也就是Lib档案的第二个节。它的名字也是“/”(注意档案中第一个叫“/”的节是第一节,第二个就是第二节)。不过它的结构与第一节有些不同,如下
typedef struct {
unsigned long ObjNum; // Obj Sec的数量
unsigned long ObjOffset[x]; // 每一个Obj Sec的偏移
unsigned long SymbolNum; // 库中符号的数量
unsigned short SymbolIdx[n]; // 符号在ObjOffset表中的索引
char StrTable[m]; // 符号名称字元串表
}SecondSec;
第一个成员ObjNum是库中Obj Sec的数量。
第二个成员ObjOffset是一个偏移表,它记录了库中所有Obj Sec的偏移。这个表的记录数x就是ObjNum。
第三个成员SymbolNum与First Sec中的SymbolNum意义相同。
第四个成员SymbolIdx变成了一个索引,它记录了相应名称字元串在ObjOffset这个表中的位置,我们要通过两次索引才能找到我们所要符号的Obj Sec位置。它的项目数n为SymbolNum。但请注意,这个索引是unsigned short型,不再是unsigned long型。
第五个成员StrTable结构与First Sec中的一样。不过,它的长度m为SectionHeader.Size的值减去((ObjNum+1)4+(SymbolNum+2)2)。
值得注意的是,这里的所有数据都是Little-Endian格式的。千万不要弄错了!Longname Sec
这个小节就是一个字元串表,它的名称为“//”,其结构同FirstSec.StrTable。这里就不多说了。
Obj Sec
这一节中的数据就是COFF档案的原始数据,把它读出来存成档案,就是一个COFF档案。它的格式请参考《COFF格式》一文。
要指出的是它的命名方式有些特殊。如果Obj档案的名称少于16个字元,它就会被保存在SectionHeader的Name成员中,以‘/’字元结尾。如果无法保存在Name成员中,则Name成员的第一个字元就为‘/’,之后再跟上这个名称在Longname Sec中的偏移。
例如
!<arch>\n
……
LongName Sec:
This_Is_Long_Name0001\0
This_Is_Long_Name0002\0
……
Obj Sec1:
Name[16]“shortname/”
……
Obj Sec2:
Name[16]“/0” // 这里使用了第一个长档案名称This_Is_Long_Name0001
……
Obj Sec3:
Name[16]“/22” // 这里使用了第二个长档案名称This_Is_Long_Name0002