意義
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