目錄
Qt DLL總結(jié)【一】-鏈接庫(kù)預(yù)備知識(shí)
Qt DLL總結(jié)【二】-創(chuàng)建及調(diào)用QT的 DLL
Qt DLL總結(jié)【三】-VS2008+Qt 使用QPluginLoader訪問(wèn)DLL
1、鏈接庫(kù)概念
靜態(tài)鏈接庫(kù)和動(dòng)態(tài)鏈接庫(kù)介紹
我們可以創(chuàng)建一種文件里面包含了很多函數(shù)和變量的目標(biāo)代碼,鏈接的時(shí)候只要把這個(gè)文件指示給鏈接程序就自動(dòng)地從文件中查找符合要求的函數(shù)和變量進(jìn)行鏈接,整個(gè)查找過(guò)程根本不需要我們操心。
這個(gè)文件叫做 “庫(kù)(Libary)”,平時(shí)我們把編譯好的目標(biāo)代碼存儲(chǔ)到“庫(kù)”里面,要用的時(shí)候鏈接程序幫我們從庫(kù)里面找出來(lái)。
靜態(tài)鏈接庫(kù):
在早期庫(kù)的組織形式相對(duì)簡(jiǎn)單,里面的目標(biāo)代碼只能夠進(jìn)行靜態(tài)鏈接,所以我們稱為“靜態(tài)庫(kù)”,靜態(tài)庫(kù)的結(jié)構(gòu)比較簡(jiǎn)單,其實(shí)就是把原來(lái)的目標(biāo)代碼放在一起,鏈接程序根據(jù)每一份目標(biāo)代碼的符號(hào)表查找相應(yīng)的符號(hào)(函數(shù)和變量的名字),找到的話就把該函數(shù)里面需要定位的進(jìn)行定位,然后將整塊函數(shù)代碼放進(jìn)可執(zhí)行文件里,若是找不到需要的函數(shù)就報(bào)錯(cuò)退出。
靜態(tài)庫(kù)的兩個(gè)特點(diǎn):
1、鏈接后產(chǎn)生的可執(zhí)行文件包含了所有需要調(diào)用的函數(shù)的代碼,因此占用磁盤(pán)空間較大。
2、如果有多個(gè)(調(diào)用相同庫(kù)函數(shù)的)進(jìn)程在內(nèi)存中同時(shí)運(yùn)行,內(nèi)存中就存有多份相同的庫(kù)函數(shù)代碼,因此占用內(nèi)存空間較多。
動(dòng)態(tài)鏈接庫(kù):
動(dòng)態(tài)鏈接庫(kù)就是為了解決這些問(wèn)題而誕生的技術(shù),顧名思義,動(dòng)態(tài)鏈接的意思就是在程序裝載內(nèi)存的時(shí)候才真正的把庫(kù)函數(shù)代碼鏈接進(jìn)行確定它們的地址,并且就算有幾個(gè)程序同時(shí)運(yùn)行,內(nèi)存也只存在一份函數(shù)代碼。
動(dòng)態(tài)庫(kù)的代碼必須滿足這樣一種條件:能夠被加載到不同進(jìn)程的不同地址,所以代碼要經(jīng)過(guò)特別的編譯處理,我們把這種經(jīng)過(guò)特別處理的代碼叫做“位置無(wú)關(guān)代碼(Position independed Code .PIC)”.
根據(jù)載入程序何時(shí)確定動(dòng)態(tài)代碼的邏輯地址,可以把動(dòng)態(tài)裝載分為兩類。
1、靜態(tài)綁定(static binding)
使用靜態(tài)綁定的程序一開(kāi)始載入內(nèi)存的時(shí)候,載入程序就會(huì)把程序所有調(diào)用到的動(dòng)態(tài)代碼的地址算出確定下來(lái),這種方式使程序剛運(yùn)行的初始化時(shí)間較長(zhǎng),不過(guò)旦完成動(dòng)態(tài)裝載,程序的運(yùn)行速度就很快。
2、動(dòng)態(tài)綁定(dynamic binding)
使用這種方式的程序并不在一開(kāi)始就完成動(dòng)態(tài)鏈接,而是直到真正調(diào)用動(dòng)態(tài)庫(kù)代碼時(shí),載入程序才計(jì)算(被調(diào)用的那部分)動(dòng)態(tài)代碼的邏輯地址,然后等到某個(gè)時(shí)候,程序又需要調(diào)用另外某塊動(dòng)態(tài)代碼時(shí),載入程序又去計(jì)算這部分代碼的邏輯地址,所以,這種方式使程序初始化時(shí)間較短,但運(yùn)行期間的性能比不上靜態(tài)綁定的程序。
平時(shí)默認(rèn)進(jìn)行鏈接的標(biāo)準(zhǔn) C/C++ 函數(shù)就是動(dòng)態(tài)庫(kù)。
2、鏈接庫(kù)常識(shí)
目前以lib后綴的庫(kù)有兩種,一種為靜態(tài)鏈接庫(kù)(Static Libary,以下簡(jiǎn)稱“靜態(tài)庫(kù)”),另一種為動(dòng)態(tài)連接庫(kù)(DLL,以下簡(jiǎn)稱“動(dòng)態(tài)庫(kù)”)的導(dǎo)入庫(kù)(Import Libary,以下簡(jiǎn)稱“導(dǎo)入庫(kù)”)。
靜態(tài)庫(kù)是一個(gè)或者多個(gè)obj文件的打包,所以有人干脆把從obj文件生成lib的過(guò)程稱為Archive,即合并到一起。比如你鏈接一個(gè)靜態(tài)庫(kù),如果其中有錯(cuò),它會(huì)準(zhǔn)確的找到是哪個(gè)obj有錯(cuò),即靜態(tài)lib只是殼子。
動(dòng)態(tài)庫(kù)一般會(huì)有對(duì)應(yīng)的導(dǎo)入庫(kù),方便程序靜態(tài)載入動(dòng)態(tài)鏈接庫(kù),否則你可能就需要自己LoadLibary調(diào)入DLL文件,然后再手工GetProcAddress獲得對(duì)應(yīng)函數(shù)了。有了導(dǎo)入庫(kù),你只需要鏈接導(dǎo)入庫(kù)后按照頭文件函數(shù)接口的聲明調(diào)用函數(shù)就可以了。
導(dǎo)入庫(kù)和靜態(tài)庫(kù)的區(qū)別很大,他們實(shí)質(zhì)是不一樣的東西。靜態(tài)庫(kù)本身就包含了實(shí)際執(zhí)行代碼、符號(hào)表等等,而對(duì)于導(dǎo)入庫(kù)而言,其實(shí)際的執(zhí)行代碼位于動(dòng)態(tài)庫(kù)中,導(dǎo)入庫(kù)只包含了地址符號(hào)表等,確保程序找到對(duì)應(yīng)函數(shù)的一些基本地址信息。 這也是實(shí)際上很多開(kāi)源代碼發(fā)布的慣用方式:
1. 預(yù)編譯的開(kāi)發(fā)包:包含一些.dll文件和一些.lib文件。其中這里的.lib就是導(dǎo)入庫(kù),而不要錯(cuò)以為是靜態(tài)庫(kù)。但是引入方式和靜態(tài)庫(kù)一樣,要在鏈接路徑上添加找到這些.lib的路徑。而.dll則最好放到最后產(chǎn)生的應(yīng)用程序exe執(zhí)行文件相同的目錄。這樣運(yùn)行時(shí),就會(huì)自動(dòng)調(diào)入動(dòng)態(tài)鏈接庫(kù)。
2. 用戶自己編譯: 下載的是源代碼,按照readme自己編譯。生成很可能也是.dll + .lib(導(dǎo)入庫(kù))的庫(kù)文件
3. 如果你只有dll,并且你知道dll中函數(shù)的函數(shù)原型,那么你可以直接在自己程序中使用LoadLibary調(diào)入DLL文件,GetProcAddress
DLL:
動(dòng)態(tài)鏈接庫(kù) (DLL) 是作為共享函數(shù)庫(kù)的可執(zhí)行文件。動(dòng)態(tài)鏈接提供了一種方法,使進(jìn)程可以調(diào)用不屬于其可執(zhí)行代碼的函數(shù)。函數(shù)的可執(zhí)行代碼位于一個(gè) DLL 中,該 DLL 包含一個(gè)或多個(gè)已被編譯、鏈接并與使用它們的進(jìn)程分開(kāi)存儲(chǔ)的函數(shù)。DLL 還有助于共享數(shù)據(jù)和資源。多個(gè)應(yīng)用程序可同時(shí)訪問(wèn)內(nèi)存中單個(gè) DLL 副本的內(nèi)容。
動(dòng)態(tài)鏈接與靜態(tài)鏈接的不同之處在于它允許可執(zhí)行模塊(.dll 文件或 .exe 文件)僅包含在運(yùn)行時(shí)定位 DLL 函數(shù)的可執(zhí)行代碼所需的信息。在靜態(tài)鏈接中,鏈接器從靜態(tài)鏈接庫(kù)獲取所有被引用的函數(shù),并將庫(kù)同代碼一起放到可執(zhí)行文件中。
使用動(dòng)態(tài)鏈接代替靜態(tài)鏈接有若干優(yōu)點(diǎn)。擴(kuò)展了應(yīng)用程序的特性、、可以用許多種編程語(yǔ)言來(lái)編寫(xiě)、簡(jiǎn)化了軟件項(xiàng)目的管理、有助于節(jié)省內(nèi)存、有助于資源共享、有助于應(yīng)用程序的本地化、有助于解決平臺(tái)差異、可以用于一些特殊的目的。windows使得某些特性只能為DLL所用。
3、動(dòng)態(tài)鏈接庫(kù)的調(diào)用
有兩種類型的鏈接:隱式鏈接和顯式鏈接。
隱式鏈接
應(yīng)用程序的代碼調(diào)用導(dǎo)出 DLL 函數(shù)時(shí)發(fā)生隱式鏈接。 當(dāng)調(diào)用可執(zhí)行文件的源代碼被編譯或被匯編時(shí),DLL 函數(shù)調(diào)用在對(duì)象代碼中生成一個(gè)外部函數(shù)引用。 若要解析此外部引用,應(yīng)用程序必須與 DLL 的創(chuàng)建者所提供的導(dǎo)入庫(kù)(.LIB 文件)鏈接。
導(dǎo)入庫(kù)僅包含加載 DLL 的代碼和實(shí)現(xiàn) DLL 函數(shù)調(diào)用的代碼。 在導(dǎo)入庫(kù)中找到外部函數(shù)后,會(huì)通知鏈接器此函數(shù)的代碼在 DLL 中。 要解析對(duì) DLL 的外部引用,鏈接器只需向可執(zhí)行文件中添加信息,通知系統(tǒng)在進(jìn)程啟動(dòng)時(shí)應(yīng)在何處查找 DLL 代碼。
系統(tǒng)啟動(dòng)包含動(dòng)態(tài)鏈接引用的程序時(shí),它使用程序的可執(zhí)行文件中的信息定位所需的 DLL。 如果系統(tǒng)無(wú)法定位 DLL,它將終止進(jìn)程并顯示一個(gè)對(duì)話框來(lái)報(bào)告錯(cuò)誤。 否則,系統(tǒng)將 DLL 模塊映射到進(jìn)程的地址空間中。
如果任何 DLL 具有(用于初始化代碼和終止代碼的)入口點(diǎn)函數(shù),操作系統(tǒng)將調(diào)用此函數(shù)。 在傳遞到入口點(diǎn)函數(shù)的參數(shù)中,有一個(gè)指定用以指示 DLL 正在附帶到進(jìn)程的代碼。 如果入口點(diǎn)函數(shù)沒(méi)有返回 TRUE,系統(tǒng)將終止進(jìn)程并報(bào)告錯(cuò)誤。
最后,系統(tǒng)修改進(jìn)程的可執(zhí)行代碼以提供 DLL 函數(shù)的起始地址。
與程序代碼的其余部分一樣,DLL 代碼在進(jìn)程啟動(dòng)時(shí)映射到進(jìn)程的地址空間中,且僅當(dāng)需要時(shí)才加載到內(nèi)存中。 因此,由 .def 文件用來(lái)在 Windows 的早期版本中控制加載的 PRELOAD 和 LOADONCALL 代碼特性不再具有任何意義。
顯式鏈接
大部分應(yīng)用程序使用隱式鏈接,因?yàn)檫@是最易于使用的鏈接方法。 但是有時(shí)也需要顯式鏈接。 下面是一些使用顯式鏈接的常見(jiàn)原因:
直到運(yùn)行時(shí),應(yīng)用程序才知道需要加載的 DLL 的名稱。 例如,應(yīng)用程序可能需要從配置文件獲取 DLL 的名稱和導(dǎo)出函數(shù)名。
如果在進(jìn)程啟動(dòng)時(shí)未找到 DLL,操作系統(tǒng)將終止使用隱式鏈接的進(jìn)程。 同樣是在此情況下,使用顯式鏈接的進(jìn)程則不會(huì)被終止,并可以嘗試從錯(cuò)誤中恢復(fù)。 例如,進(jìn)程可通知用戶所發(fā)生的錯(cuò)誤,并讓用戶指定 DLL 的其他路徑。
如果使用隱式鏈接的進(jìn)程所鏈接到的 DLL 中有任何 DLL 具有失敗的 DllMain 函數(shù),該進(jìn)程也會(huì)被終止。 同樣是在此情況下,使用顯式鏈接的進(jìn)程則不會(huì)被終止。
因?yàn)?Windows 在應(yīng)用程序加載時(shí)加載所有的 DLL,故隱式鏈接到許多 DLL 的應(yīng)用程序啟動(dòng)起來(lái)會(huì)比較慢。 為提高啟動(dòng)性能,應(yīng)用程序可隱式鏈接到那些加載后立即需要的 DLL,并等到在需要時(shí)顯式鏈接到其他 DLL。
顯式鏈接下不需將應(yīng)用程序與導(dǎo)入庫(kù)鏈接。 如果 DLL 中的更改導(dǎo)致導(dǎo)出序號(hào)更改,使用顯式鏈接的應(yīng)用程序不需重新鏈接(假設(shè)它們是用函數(shù)名而不是序號(hào)值調(diào)用 GetProcAddress),而使用隱式鏈接的應(yīng)用程序必須重新鏈接到新的導(dǎo)入庫(kù)。
下面是需要注意的顯式鏈接的兩個(gè)缺點(diǎn):
如果 DLL 具有 DllMain 入口點(diǎn)函數(shù),則操作系統(tǒng)在調(diào)用 LoadLibrary 的線程上下文中調(diào)用此函數(shù)。 如果由于以前調(diào)用了 LoadLibrary 但沒(méi)有相應(yīng)地調(diào)用 FreeLibrary 函數(shù)而導(dǎo)致 DLL 已經(jīng)附加到進(jìn)程,則不會(huì)調(diào)用此入口點(diǎn)函數(shù)。 如果 DLL 使用 DllMain 函數(shù)為進(jìn)程的每個(gè)線程執(zhí)行初始化,顯式鏈接會(huì)造成問(wèn)題,因?yàn)檎{(diào)用 LoadLibrary(或 AfxLoadLibrary)時(shí)存在的線程將不會(huì)初始化。
如果 DLL 將靜態(tài)作用域數(shù)據(jù)聲明為 __declspec(thread),則在顯式鏈接時(shí) DLL 會(huì)導(dǎo)致保護(hù)錯(cuò)誤。 用 LoadLibrary 加載 DLL 后,每當(dāng)代碼引用此數(shù)據(jù)時(shí) DLL 就會(huì)導(dǎo)致保護(hù)錯(cuò)誤。 (靜態(tài)作用域數(shù)據(jù)既包括全局靜態(tài)項(xiàng),也包括局部靜態(tài)項(xiàng)。)因此,創(chuàng)建 DLL 時(shí)應(yīng)避免使用線程本地存儲(chǔ)區(qū),或者應(yīng)(在用戶嘗試動(dòng)態(tài)加載時(shí))告訴 DLL 用戶潛在的缺陷。
4、顯示鏈接和隱式鏈接的區(qū)別
一、Implicit Linking(隱式連接)
Implicit Linking(隱式連接) ,又叫靜態(tài)載入,所謂靜態(tài)載入是指程序在連接時(shí)期即與dlls所對(duì)應(yīng)的import libraries作靜態(tài)連接,于是可執(zhí)行文件中便對(duì)所有的dll函數(shù)都有一份重定位表格(relocation table)和待修正記錄(fixup record)。當(dāng)程序被windows載入器載入內(nèi)存中時(shí),載入器會(huì)自動(dòng)修正所有的fixup records,而這個(gè)fixup records 就是記錄DLL中所有輸出資源的正確位置地址,經(jīng)過(guò)這樣的程序動(dòng)態(tài)連接便自動(dòng)產(chǎn)生。也就是說(shuō),程序開(kāi)始執(zhí)行時(shí),會(huì)用靜態(tài)載入的方式時(shí)所使用的DLLs都載入到程序的內(nèi)存里。
靜態(tài)載入方式的優(yōu)點(diǎn)
1、靜態(tài)載入方式所使用的dll會(huì)在應(yīng)用程序執(zhí)行時(shí)載入,然后就可以調(diào)用所有dll中提供的函數(shù),就像是程序中一樣。
2、處理簡(jiǎn)單,載入的方法有編譯器負(fù)責(zé)處理,不需動(dòng)腦筋。
靜態(tài)載入方式的缺點(diǎn)
1、當(dāng)程序機(jī)構(gòu)態(tài)載入方式所使用的dll不存在時(shí),程序開(kāi)始就會(huì)報(bào)dll無(wú)法找到的錯(cuò)誤而使得程序無(wú)法運(yùn)行。
編譯時(shí)需要加入import library。
2、若調(diào)用的dll很多,載入應(yīng)用程序的速度就會(huì)很慢。
不同的c++編譯器靜態(tài)載入的方式也不一樣。
二、Explicit Linking(顯式連接)
所謂Explicit Link(顯式連接)又叫動(dòng)態(tài)載入,使用dll的可執(zhí)行文件必須明確調(diào)用載入和御載dll的函數(shù)調(diào)用(Function Call),并且存取dll的輸出函數(shù)。用戶端必須通過(guò)函數(shù)聲明調(diào)用函數(shù)。
可執(zhí)行文件可以使用任何一種連接方式的相同低dll。并且,這些機(jī)制之間并不會(huì)相互排斥,因此,當(dāng)一個(gè)可執(zhí)行文件隱式的連接dll時(shí),其他程序還可以顯示地連接它。
動(dòng)態(tài)載入方式的優(yōu)缺點(diǎn):
1、dll只有需要時(shí)才載入內(nèi)存中,這樣可以更有效地使用存儲(chǔ)空間。
2、應(yīng)用程序載入速度較隱式連接較快,因?yàn)楫?dāng)程序開(kāi)始載入時(shí)并不需要把dll載入到程序中。
3、編譯時(shí)不需要額外的import library。
4、可以讓用戶個(gè)清楚地知道dll的載入流程。
缺點(diǎn)就是必須多寫(xiě)一點(diǎn)代碼。
動(dòng)態(tài)載入基本流程
必須使用LoadLibrary這個(gè)Windows API來(lái)手動(dòng)載入DLL,并使用GetProcessAddress來(lái)取得所需要使用的函數(shù)的函數(shù)指針,最后用FreeLibrary將DLL釋放。所以學(xué)會(huì)動(dòng)態(tài)載入DLL時(shí),必須先知道函數(shù)指針的用法。