C++静态库与动态库
C++靜態(tài)庫(kù)與動(dòng)態(tài)庫(kù)
這次分享的宗旨是——讓大家學(xué)會(huì)創(chuàng)建與使用靜態(tài)庫(kù)、動(dòng)態(tài)庫(kù),知道靜態(tài)庫(kù)與動(dòng)態(tài)庫(kù)的區(qū)別,知道使用的時(shí)候如何選擇。這里不深入介紹靜態(tài)庫(kù)、動(dòng)態(tài)庫(kù)的底層格式,內(nèi)存布局等,有興趣的同學(xué),推薦一本書(shū)《程序員的自我修養(yǎng)——鏈接、裝載與庫(kù)》。
什么是庫(kù)
庫(kù)是寫(xiě)好的現(xiàn)有的,成熟的,可以復(fù)用的代碼。現(xiàn)實(shí)中每個(gè)程序都要依賴(lài)很多基礎(chǔ)的底層庫(kù),不可能每個(gè)人的代碼都從零開(kāi)始,因此庫(kù)的存在意義非同尋常。
本質(zhì)上來(lái)說(shuō)庫(kù)是一種可執(zhí)行代碼的二進(jìn)制形式,可以被操作系統(tǒng)載入內(nèi)存執(zhí)行。庫(kù)有兩種:靜態(tài)庫(kù)(.a、.lib)和動(dòng)態(tài)庫(kù)(.so、.dll)。
所謂靜態(tài)、動(dòng)態(tài)是指鏈接。回顧一下,將一個(gè)程序編譯成可執(zhí)行程序的步驟:
圖:編譯過(guò)程
靜態(tài)庫(kù)
之所以成為【靜態(tài)庫(kù)】,是因?yàn)樵阪溄与A段,會(huì)將匯編生成的目標(biāo)文件.o與引用到的庫(kù)一起鏈接打包到可執(zhí)行文件中。因此對(duì)應(yīng)的鏈接方式稱(chēng)為靜態(tài)鏈接。
試想一下,靜態(tài)庫(kù)與匯編生成的目標(biāo)文件一起鏈接為可執(zhí)行文件,那么靜態(tài)庫(kù)必定跟.o文件格式相似。其實(shí)一個(gè)靜態(tài)庫(kù)可以簡(jiǎn)單看成是一組目標(biāo)文件(.o/.obj文件)的集合,即很多目標(biāo)文件經(jīng)過(guò)壓縮打包后形成的一個(gè)文件。靜態(tài)庫(kù)特點(diǎn)總結(jié):
l?靜態(tài)庫(kù)對(duì)函數(shù)庫(kù)的鏈接是放在編譯時(shí)期完成的。
l?程序在運(yùn)行時(shí)與函數(shù)庫(kù)再無(wú)瓜葛,移植方便。
l?浪費(fèi)空間和資源,因?yàn)樗邢嚓P(guān)的目標(biāo)文件與牽涉到的函數(shù)庫(kù)被鏈接合成一個(gè)可執(zhí)行文件。
下面編寫(xiě)一些簡(jiǎn)單的四則運(yùn)算C++類(lèi),將其編譯成靜態(tài)庫(kù)給他人用,頭文件如下所示:
| StaticMath.h頭文件 |
| #pragmaonce classStaticMath { public: ???StaticMath(void); ???~StaticMath(void); ? ???staticdouble add(double a, double b);//加法 ???staticdouble sub(double a, double b);//減法 ???staticdouble mul(double a, double b);//乘法 ???staticdouble div(double a, double b);//除法 ? ???void print(); }; |
?
Linux下使用ar工具、Windows下vs使用lib.exe,將目標(biāo)文件壓縮到一起,并且對(duì)其進(jìn)行編號(hào)和索引,以便于查找和檢索。一般創(chuàng)建靜態(tài)庫(kù)的步驟如圖所示:
圖:創(chuàng)建靜態(tài)庫(kù)過(guò)程
Linux下創(chuàng)建與使用靜態(tài)庫(kù)
Linux靜態(tài)庫(kù)命名規(guī)則
Linux靜態(tài)庫(kù)命名規(guī)范,必須是"lib[your_library_name].a":lib為前綴,中間是靜態(tài)庫(kù)名,擴(kuò)展名為.a。
創(chuàng)建靜態(tài)庫(kù)(.a)
通過(guò)上面的流程可以知道,Linux創(chuàng)建靜態(tài)庫(kù)過(guò)程如下:
l?首先,將代碼文件編譯成目標(biāo)文件.o(StaticMath.o)
| g++ -c StaticMath.cpp |
注意帶參數(shù)-c,否則直接編譯為可執(zhí)行文件
l?然后,通過(guò)ar工具將目標(biāo)文件打包成.a靜態(tài)庫(kù)文件
| ar -crv libstaticmath.a StaticMath.o |
生成靜態(tài)庫(kù)libstaticmath.a。
大一點(diǎn)的項(xiàng)目會(huì)編寫(xiě)makefile文件(CMake等等工程管理工具)來(lái)生成靜態(tài)庫(kù),輸入多個(gè)命令太麻煩了。
使用靜態(tài)庫(kù)
編寫(xiě)使用上面創(chuàng)建的靜態(tài)庫(kù)的測(cè)試代碼:
| 測(cè)試代碼: |
| #include"StaticMath.h" #include<iostream> usingnamespace std; ? int main(intargc,char*argv[]) { ???double a = 10; ???double b = 2; ? ???cout << "a + b = " <<StaticMath::add(a, b) << endl; ???cout << "a - b = " <<StaticMath::sub(a, b) << endl; ???cout << "a * b = " <<StaticMath::mul(a, b) << endl; ???cout << "a / b = " <<StaticMath::div(a, b) << endl; ? ???StaticMath sm; ???sm.print(); ? ???system("pause"); ???return 0; } |
Linux下使用靜態(tài)庫(kù),只需要在編譯的時(shí)候,指定靜態(tài)庫(kù)的搜索路徑(-L選項(xiàng))、指定靜態(tài)庫(kù)名(不需要lib前綴和.a后綴,-l選項(xiàng))。
# g++ TestStaticLibrary.cpp -L../StaticLibrary -lstaticmath
l?-L:表示要連接的庫(kù)所在目錄
l?-l:指定鏈接時(shí)需要的動(dòng)態(tài)庫(kù),編譯器查找動(dòng)態(tài)連接庫(kù)時(shí)有隱含的命名規(guī)則,即在給出的名字前面加上lib,后面加上.a或.so來(lái)確定庫(kù)的名稱(chēng)。
?
Windows下創(chuàng)建與使用靜態(tài)庫(kù)
創(chuàng)建靜態(tài)庫(kù)(.lib)
如果是使用VS命令行生成靜態(tài)庫(kù),也是分兩個(gè)步驟來(lái)生成程序:
l?首先,通過(guò)使用帶編譯器選項(xiàng)?/c?的?Cl.exe?編譯代碼 (cl /c StaticMath.cpp),創(chuàng)建名為“StaticMath.obj”的目標(biāo)文件。
l?然后,使用庫(kù)管理器?Lib.exe?鏈接代碼 (lib StaticMath.obj),創(chuàng)建靜態(tài)庫(kù)StaticMath.lib。
當(dāng)然,我們一般不這么用,使用VS工程設(shè)置更方便。創(chuàng)建win32控制臺(tái)程序時(shí),勾選靜態(tài)庫(kù)類(lèi)型;打開(kāi)工程“屬性面板”è”配置屬性”è”常規(guī)”,配置類(lèi)型選擇靜態(tài)庫(kù)。
圖:vs靜態(tài)庫(kù)項(xiàng)目屬性設(shè)置
Build項(xiàng)目即可生成靜態(tài)庫(kù)。
使用靜態(tài)庫(kù)
測(cè)試代碼Linux下面的一樣。有3種使用方法:
方法一:
在VS中使用靜態(tài)庫(kù)方法:
l?工程“屬性面板”è“通用屬性”è “框架和引用”è”添加引用”,將顯示“添加引用”對(duì)話框。 “項(xiàng)目”選項(xiàng)卡列出了當(dāng)前解決方案中的各個(gè)項(xiàng)目以及可以引用的所有庫(kù)。在“項(xiàng)目”選項(xiàng)卡中,選擇 StaticLibrary。單擊“確定”。
l?添加StaticMath.h頭文件目錄,必須修改包含目錄路徑。打開(kāi)工程“屬性面板”è”配置屬性”è “C/C++”è”常規(guī)”,在“附加包含目錄”屬性值中,鍵入StaticMath.h頭文件所在目錄的路徑或?yàn)g覽至該目錄。
編譯運(yùn)行OK。
圖:靜態(tài)庫(kù)測(cè)試結(jié)果(vs)
如果引用的靜態(tài)庫(kù)不是在同一解決方案下的子工程,而是使用第三方提供的靜態(tài)庫(kù)lib和頭文件,上面的方法設(shè)置不了。還有2中方法設(shè)置都可行。
方法二:
打開(kāi)工程“屬性面板”è”配置屬性”è “鏈接器”è”命令行”,輸入靜態(tài)庫(kù)的完整路徑即可。
方法三:
l?“屬性面板”è”配置屬性”è “鏈接器”è”常規(guī)”,附加依賴(lài)庫(kù)目錄中輸入,靜態(tài)庫(kù)所在目錄;
l?“屬性面板”è”配置屬性”è “鏈接器”è”輸入”,附加依賴(lài)庫(kù)中輸入靜態(tài)庫(kù)名StaticLibrary.lib。
動(dòng)態(tài)庫(kù)
通過(guò)上面的介紹發(fā)現(xiàn)靜態(tài)庫(kù),容易使用和理解,也達(dá)到了代碼復(fù)用的目的,那為什么還需要?jiǎng)討B(tài)庫(kù)呢?
為什么還需要?jiǎng)討B(tài)庫(kù)?
為什么需要?jiǎng)討B(tài)庫(kù),其實(shí)也是靜態(tài)庫(kù)的特點(diǎn)導(dǎo)致。
l?空間浪費(fèi)是靜態(tài)庫(kù)的一個(gè)問(wèn)題。
l?另一個(gè)問(wèn)題是靜態(tài)庫(kù)對(duì)程序的更新、部署和發(fā)布頁(yè)會(huì)帶來(lái)麻煩。如果靜態(tài)庫(kù)liba.lib更新了,所以使用它的應(yīng)用程序都需要重新編譯、發(fā)布給用戶(對(duì)于玩家來(lái)說(shuō),可能是一個(gè)很小的改動(dòng),卻導(dǎo)致整個(gè)程序重新下載,全量更新)。
動(dòng)態(tài)庫(kù)在程序編譯時(shí)并不會(huì)被連接到目標(biāo)代碼中,而是在程序運(yùn)行是才被載入。不同的應(yīng)用程序如果調(diào)用相同的庫(kù),那么在內(nèi)存里只需要有一份該共享庫(kù)的實(shí)例,規(guī)避了空間浪費(fèi)問(wèn)題。動(dòng)態(tài)庫(kù)在程序運(yùn)行是才被載入,也解決了靜態(tài)庫(kù)對(duì)程序的更新、部署和發(fā)布頁(yè)會(huì)帶來(lái)麻煩。用戶只需要更新動(dòng)態(tài)庫(kù)即可,增量更新。
動(dòng)態(tài)庫(kù)特點(diǎn)總結(jié):
l?動(dòng)態(tài)庫(kù)把對(duì)一些庫(kù)函數(shù)的鏈接載入推遲到程序運(yùn)行的時(shí)期。
l?可以實(shí)現(xiàn)進(jìn)程之間的資源共享。(因此動(dòng)態(tài)庫(kù)也稱(chēng)為共享庫(kù))
l?將一些程序升級(jí)變得簡(jiǎn)單。
l?甚至可以真正做到鏈接載入完全由程序員在程序代碼中控制(顯示調(diào)用)。
Window與Linux執(zhí)行文件格式不同,在創(chuàng)建動(dòng)態(tài)庫(kù)的時(shí)候有一些差異。
l?在Windows系統(tǒng)下的執(zhí)行文件格式是PE格式,動(dòng)態(tài)庫(kù)需要一個(gè)DllMain函數(shù)做出初始化的入口,通常在導(dǎo)出函數(shù)的聲明時(shí)需要有_declspec(dllexport)關(guān)鍵字。
l?Linux下gcc編譯的執(zhí)行文件默認(rèn)是ELF格式,不需要初始化入口,亦不需要函數(shù)做特別的聲明,編寫(xiě)比較方便。
與創(chuàng)建靜態(tài)庫(kù)不同的是,不需要打包工具(ar、lib.exe),直接使用編譯器即可創(chuàng)建動(dòng)態(tài)庫(kù)。
Linux下創(chuàng)建與使用動(dòng)態(tài)庫(kù)
?
linux動(dòng)態(tài)庫(kù)的命名規(guī)則
動(dòng)態(tài)鏈接庫(kù)的名字形式為 libxxx.so,前綴是lib,后綴名為“.so”。
l?針對(duì)于實(shí)際庫(kù)文件,每個(gè)共享庫(kù)都有個(gè)特殊的名字“soname”。在程序啟動(dòng)后,程序通過(guò)這個(gè)名字來(lái)告訴動(dòng)態(tài)加載器該載入哪個(gè)共享庫(kù)。
l?在文件系統(tǒng)中,soname僅是一個(gè)鏈接到實(shí)際動(dòng)態(tài)庫(kù)的鏈接。對(duì)于動(dòng)態(tài)庫(kù)而言,每個(gè)庫(kù)實(shí)際上都有另一個(gè)名字給編譯器來(lái)用。它是一個(gè)指向?qū)嶋H庫(kù)鏡像文件的鏈接文件(lib+soname+.so)。
創(chuàng)建動(dòng)態(tài)庫(kù)(.so)
編寫(xiě)四則運(yùn)算動(dòng)態(tài)庫(kù)代碼:
| DynamicMath.h頭文件 |
| #pragma once class DynamicMath { public: ??????? DynamicMath(void); ??????? ~DynamicMath(void); ? ??????? static double add(double a, double b);//?ó·¨ ??????? static double sub(double a, double b);//??·¨ ??????? static double mul(double a, double b);//3?·¨ ??????? static double div(double a, double b);//3y·¨ ??????? void print(); }; |
?
l?首先,生成目標(biāo)文件,此時(shí)要加編譯器選項(xiàng)-fpic
| g++ -fPIC -c DynamicMath.cpp |
-fPIC 創(chuàng)建與地址無(wú)關(guān)的編譯程序(pic,position independent code),是為了能夠在多個(gè)應(yīng)用程序間共享。
l?然后,生成動(dòng)態(tài)庫(kù),此時(shí)要加鏈接器選項(xiàng)-shared
| g++ -shared -o libdynmath.so DynamicMath.o |
-shared指定生成動(dòng)態(tài)鏈接庫(kù)。
其實(shí)上面兩個(gè)步驟可以合并為一個(gè)命令:
| g++ -fPIC -shared -o libdynmath.so DynamicMath.cpp |
?
使用動(dòng)態(tài)庫(kù)
編寫(xiě)使用動(dòng)態(tài)庫(kù)的測(cè)試代碼:
| 測(cè)試代碼: |
| #include "../DynamicLibrary/DynamicMath.h" ? #include <iostream> using namespace std; ? int main(int argc, char* argv[]) { ??? double a = 10; ??? double b = 2; ? ??? cout << "a + b = " << DynamicMath::add(a, b) << endl; ??? cout << "a - b = " << DynamicMath::sub(a, b) << endl; ??? cout << "a * b = " << DynamicMath::mul(a, b) << endl; ??? cout << "a / b = " << DynamicMath::div(a, b) << endl; ? ??? DynamicMath dyn; ? ? dyn.print(); ??? return 0; } |
引用動(dòng)態(tài)庫(kù)編譯成可執(zhí)行文件(跟靜態(tài)庫(kù)方式一樣):
| g++ TestDynamicLibrary.cpp -L../DynamicLibrary -ldynmath |
然后運(yùn)行:./a.out,發(fā)現(xiàn)竟然報(bào)錯(cuò)了!!!
可能大家會(huì)猜測(cè),是因?yàn)閯?dòng)態(tài)庫(kù)跟測(cè)試程序不是一個(gè)目錄,那我們驗(yàn)證下是否如此:
發(fā)現(xiàn)還是報(bào)錯(cuò)!!!那么,在執(zhí)行的時(shí)候是如何定位共享庫(kù)文件的呢?
1)???????當(dāng)系統(tǒng)加載可執(zhí)行代碼時(shí)候,能夠知道其所依賴(lài)的庫(kù)的名字,但是還需要知道絕對(duì)路徑。此時(shí)就需要系統(tǒng)動(dòng)態(tài)載入器(dynamic linker/loader)。
2)???????對(duì)于elf格式的可執(zhí)行程序,是由ld-linux.so*來(lái)完成的,它先后搜索elf文件的 DT_RPATH段—環(huán)境變量LD_LIBRARY_PATH—/etc/ld.so.cache文件列表—/lib/,/usr/lib目錄找到庫(kù)文件后將其載入內(nèi)存。
如何讓系統(tǒng)能夠找到它:
l?如果安裝在/lib或者/usr/lib下,那么ld默認(rèn)能夠找到,無(wú)需其他操作。
l?如果安裝在其他目錄,需要將其添加到/etc/ld.so.cache文件中,步驟如下:
n?編輯/etc/ld.so.conf文件,加入庫(kù)文件所在目錄的路徑
n?運(yùn)行ldconfig,該命令會(huì)重建/etc/ld.so.cache文件
我們將創(chuàng)建的動(dòng)態(tài)庫(kù)復(fù)制到/usr/lib下面,然后運(yùn)行測(cè)試程序。
Windows下創(chuàng)建與使用動(dòng)態(tài)庫(kù)
創(chuàng)建動(dòng)態(tài)庫(kù)(.dll)
與Linux相比,在Windows系統(tǒng)下創(chuàng)建動(dòng)態(tài)庫(kù)要稍微麻煩一些。首先,需要一個(gè)DllMain函數(shù)做出初始化的入口(創(chuàng)建win32控制臺(tái)程序時(shí),勾選DLL類(lèi)型會(huì)自動(dòng)生成這個(gè)文件):
| dllmain.cpp入口文件 |
| // dllmain.cpp : Defines the entry point for the DLL application. #include"stdafx.h" ? BOOLAPIENTRY DllMain(HMODULEhModule, ??????????????????????DWORD?ul_reason_for_call, ??????????????????????LPVOIDlpReserved ????????????????????) { ???switch (ul_reason_for_call) ???{ ???caseDLL_PROCESS_ATTACH: ???caseDLL_THREAD_ATTACH: ???caseDLL_THREAD_DETACH: ???caseDLL_PROCESS_DETACH: ???????break; ???} ???returnTRUE; } |
通常在導(dǎo)出函數(shù)的聲明時(shí)需要有_declspec(dllexport)關(guān)鍵字:
| DynamicMath.h頭文件 |
| #pragmaonce classDynamicMath { public: ???__declspec(dllexport) DynamicMath(void); ???__declspec(dllexport) ~DynamicMath(void); ? ???static__declspec(dllexport)double add(double a, double b);//加法 ???static__declspec(dllexport)double sub(double a, double b);//減法 ???static__declspec(dllexport)double mul(double a, double b);//乘法 ???static__declspec(dllexport)double div(double a, double b);//除法 ? ???__declspec(dllexport)void print(); }; |
生成動(dòng)態(tài)庫(kù)需要設(shè)置工程屬性,打開(kāi)工程“屬性面板”è”配置屬性”è”常規(guī)”,配置類(lèi)型選擇動(dòng)態(tài)庫(kù)。
圖:v動(dòng)態(tài)庫(kù)項(xiàng)目屬性設(shè)置
Build項(xiàng)目即可生成動(dòng)態(tài)庫(kù)。
使用動(dòng)態(tài)庫(kù)
創(chuàng)建win32控制臺(tái)測(cè)試程序:
| TestDynamicLibrary.cpp測(cè)試程序 |
| #include"stdafx.h" #include"DynamicMath.h" ? #include<iostream> usingnamespace std; ? int_tmain(intargc,_TCHAR*argv[]) { ???double a = 10; ???double b = 2; ? ???cout << "a + b = " <<DynamicMath::add(a, b) << endl; ???cout << "a - b = " <<DynamicMath::sub(a, b) << endl; ???cout << "a * b = " <<DynamicMath::mul(a, b) << endl; ???cout << "a / b = " <<DynamicMath::div(a, b) << endl; ? ???DynamicMath dyn; ???dyn.print(); ? ???system("pause"); ???return 0; } |
方法一:
l?工程“屬性面板”è“通用屬性”è “框架和引用”è”添加引用”,將顯示“添加引用”對(duì)話框。“項(xiàng)目”選項(xiàng)卡列出了當(dāng)前解決方案中的各個(gè)項(xiàng)目以及可以引用的所有庫(kù)。在“項(xiàng)目”選項(xiàng)卡中,選擇 DynamicLibrary。單擊“確定”。
l?添加DynamicMath.h頭文件目錄,必須修改包含目錄路徑。打開(kāi)工程“屬性面板”è”配置屬性”è “C/C++”è”常規(guī)”,在“附加包含目錄”屬性值中,鍵入DynamicMath.h頭文件所在目錄的路徑或?yàn)g覽至該目錄。
編譯運(yùn)行OK。
圖:動(dòng)態(tài)庫(kù)測(cè)試結(jié)果(vs)
方法二:
l?“屬性面板”è”配置屬性”è “鏈接器”è”常規(guī)”,附加依賴(lài)庫(kù)目錄中輸入,動(dòng)態(tài)庫(kù)所在目錄;
l?“屬性面板”è”配置屬性”è “鏈接器”è”輸入”,附加依賴(lài)庫(kù)中輸入動(dòng)態(tài)庫(kù)編譯出來(lái)的DynamicLibrary.lib。
這里可能大家有個(gè)疑問(wèn),動(dòng)態(tài)庫(kù)怎么還有一個(gè)DynamicLibrary.lib文件?即無(wú)論是靜態(tài)鏈接庫(kù)還是動(dòng)態(tài)鏈接庫(kù),最后都有lib文件,那么兩者區(qū)別是什么呢?其實(shí),兩個(gè)是完全不一樣的東西。
StaticLibrary.lib的大小為190KB,DynamicLibrary.lib的大小為3KB,靜態(tài)庫(kù)對(duì)應(yīng)的lib文件叫靜態(tài)庫(kù),動(dòng)態(tài)庫(kù)對(duì)應(yīng)的lib文件叫【導(dǎo)入庫(kù)】。實(shí)際上靜態(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ù)的一些基本地址信息。
動(dòng)態(tài)庫(kù)的顯式調(diào)用
上面介紹的動(dòng)態(tài)庫(kù)使用方法和靜態(tài)庫(kù)類(lèi)似屬于隱式調(diào)用,編譯的時(shí)候指定相應(yīng)的庫(kù)和查找路徑。其實(shí),動(dòng)態(tài)庫(kù)還可以顯式調(diào)用。【在C語(yǔ)言中】,顯示調(diào)用一個(gè)動(dòng)態(tài)庫(kù)輕而易舉!
在Linux下顯式調(diào)用動(dòng)態(tài)庫(kù)
#include <dlfcn.h>,提供了下面幾個(gè)接口:
l?void * dlopen( const char * pathname, int mode ):函數(shù)以指定模式打開(kāi)指定的動(dòng)態(tài)連接庫(kù)文件,并返回一個(gè)句柄給調(diào)用進(jìn)程。
l?void* dlsym(void* handle,const char* symbol):dlsym根據(jù)動(dòng)態(tài)鏈接庫(kù)操作句柄(pHandle)與符號(hào)(symbol),返回符號(hào)對(duì)應(yīng)的地址。使用這個(gè)函數(shù)不但可以獲取函數(shù)地址,也可以獲取變量地址。
l?int dlclose (void *handle):dlclose用于關(guān)閉指定句柄的動(dòng)態(tài)鏈接庫(kù),只有當(dāng)此動(dòng)態(tài)鏈接庫(kù)的使用計(jì)數(shù)為0時(shí),才會(huì)真正被系統(tǒng)卸載。
l?const char *dlerror(void):當(dāng)動(dòng)態(tài)鏈接庫(kù)操作函數(shù)執(zhí)行失敗時(shí),dlerror可以返回出錯(cuò)信息,返回值為NULL時(shí)表示操作函數(shù)執(zhí)行成功。
在Windows下顯式調(diào)用動(dòng)態(tài)庫(kù)
應(yīng)用程序必須進(jìn)行函數(shù)調(diào)用以在運(yùn)行時(shí)顯式加載 DLL。為顯式鏈接到 DLL,應(yīng)用程序必須:
l?調(diào)用LoadLibrary(或相似的函數(shù))以加載 DLL和獲取模塊句柄。
l?調(diào)用GetProcAddress,以獲取指向應(yīng)用程序要調(diào)用的每個(gè)導(dǎo)出函數(shù)的函數(shù)指針。由于應(yīng)用程序是通過(guò)指針調(diào)用 DLL的函數(shù),編譯器不生成外部引用,故無(wú)需與導(dǎo)入庫(kù)鏈接。
l?使用完 DLL后調(diào)用FreeLibrary。
顯式調(diào)用C++動(dòng)態(tài)庫(kù)注意點(diǎn)
對(duì)C++來(lái)說(shuō),情況稍微復(fù)雜。顯式加載一個(gè)C++動(dòng)態(tài)庫(kù)的困難一部分是因?yàn)?/strong>C++的name mangling;另一部分是因?yàn)闆](méi)有提供一個(gè)合適的API來(lái)裝載類(lèi),在C++中,您可能要用到庫(kù)中的一個(gè)類(lèi),而這需要?jiǎng)?chuàng)建該類(lèi)的一個(gè)實(shí)例,這不容易做到。
name mangling可以通過(guò)extern "C"解決。C++有個(gè)特定的關(guān)鍵字用來(lái)聲明采用C binding的函數(shù):extern "C"。用 extern "C"聲明的函數(shù)將使用函數(shù)名作符號(hào)名,就像C函數(shù)一樣。因此,只有非成員函數(shù)才能被聲明為extern "C",并且不能被重載。盡管限制多多,extern "C"函數(shù)還是非常有用,因?yàn)樗鼈兛梢韵?/span>C函數(shù)一樣被dlopen動(dòng)態(tài)加載。冠以extern "C"限定符后,并不意味著函數(shù)中無(wú)法使用C++代碼了,相反,它仍然是一個(gè)完全的C++函數(shù),可以使用任何C++特性和各種類(lèi)型的參數(shù)。
另外如何從C++動(dòng)態(tài)庫(kù)中獲取類(lèi),附上幾篇相關(guān)文章,但我并不建議這么做:
l?《LoadLibrary調(diào)用DLL中的Class》:http://www.cppblog.com/codejie/archive/2009/09/24/97141.html
l?《C++ dlopen mini HOWTO》:http://blog.csdn.net/denny_233/article/details/7255673
“顯式”使用C++動(dòng)態(tài)庫(kù)中的Class是非常繁瑣和危險(xiǎn)的事情,因此能用“隱式”就不要用“顯式”,能靜態(tài)就不要用動(dòng)態(tài)。
附件:Linux下庫(kù)相關(guān)命令
g++(gcc)編譯選項(xiàng)
l?-shared :指定生成動(dòng)態(tài)鏈接庫(kù)。
l?-static :指定生成靜態(tài)鏈接庫(kù)。
l?-fPIC :表示編譯為位置獨(dú)立的代碼,用于編譯共享庫(kù)。目標(biāo)文件需要?jiǎng)?chuàng)建成位置無(wú)關(guān)碼,念上就是在可執(zhí)行程序裝載它們的時(shí)候,它們可以放在可執(zhí)行程序的內(nèi)存里的任何地方。
l?-L. :表示要連接的庫(kù)所在的目錄。
l?-l:指定鏈接時(shí)需要的動(dòng)態(tài)庫(kù)。編譯器查找動(dòng)態(tài)連接庫(kù)時(shí)有隱含的命名規(guī)則,即在給出的名字前面加上lib,后面加上.a/.so來(lái)確定庫(kù)的名稱(chēng)。
l?-Wall :生成所有警告信息。
l?-ggdb :此選項(xiàng)將盡可能的生成gdb的可以使用的調(diào)試信息。
l?-g :編譯器在編譯的時(shí)候產(chǎn)生調(diào)試信息。
l?-c :只激活預(yù)處理、編譯和匯編,也就是把程序做成目標(biāo)文件(.o文件)。
l?-Wl,options :把參數(shù)(options)傳遞給鏈接器ld。如果options中間有逗號(hào),就將options分成多個(gè)選項(xiàng),然后傳遞給鏈接程序。
nm命令
有時(shí)候可能需要查看一個(gè)庫(kù)中到底有哪些函數(shù),nm命令可以打印出庫(kù)中的涉及到的所有符號(hào)。庫(kù)既可以是靜態(tài)的也可以是動(dòng)態(tài)的。nm列出的符號(hào)有很多,常見(jiàn)的有三種:
l?一種是在庫(kù)中被調(diào)用,但并沒(méi)有在庫(kù)中定義(表明需要其他庫(kù)支持),用U表示;
l?一種是庫(kù)中定義的函數(shù),用T表示,這是最常見(jiàn)的;
l?一種是所謂的弱態(tài)”符號(hào),它們雖然在庫(kù)中被定義,但是可能被其他庫(kù)中的同名符號(hào)覆蓋,用W表示。
$nm libhello.h
ldd命令
ldd命令可以查看一個(gè)可執(zhí)行程序依賴(lài)的共享庫(kù),例如我們編寫(xiě)的四則運(yùn)算動(dòng)態(tài)庫(kù)依賴(lài)下面這些庫(kù):
總結(jié)
二者的不同點(diǎn)在于代碼被載入的時(shí)刻不同。
l?靜態(tài)庫(kù)在程序編譯時(shí)會(huì)被連接到目標(biāo)代碼中,程序運(yùn)行時(shí)將不再需要該靜態(tài)庫(kù),因此體積較大。
l?動(dòng)態(tài)庫(kù)在程序編譯時(shí)并不會(huì)被連接到目標(biāo)代碼中,而是在程序運(yùn)行是才被載入,因此在程序運(yùn)行時(shí)還需要?jiǎng)討B(tài)庫(kù)存在,因此代碼體積較小。
動(dòng)態(tài)庫(kù)的好處是,不同的應(yīng)用程序如果調(diào)用相同的庫(kù),那么在內(nèi)存里只需要有一份該共享庫(kù)的實(shí)例。帶來(lái)好處的同時(shí),也會(huì)有問(wèn)題!如經(jīng)典的DLL Hell問(wèn)題,關(guān)于如何規(guī)避動(dòng)態(tài)庫(kù)管理問(wèn)題,可以自行查找相關(guān)資料。
總結(jié)
以上是生活随笔為你收集整理的C++静态库与动态库的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: ./configure 参数解析
- 下一篇: c++中的 extern C