VC中有一個(gè)關(guān)鍵字__declspec(dllexport), 其目的很簡(jiǎn)單,就是導(dǎo)出符號(hào),供其它可執(zhí)行模塊使用,主要用于動(dòng)態(tài)鏈接庫(kù)(DLL), 然而也可用于EXE模塊
與此相對(duì)應(yīng),__declspec(dllimport), 將指明由動(dòng)態(tài)鏈接庫(kù)導(dǎo)入符號(hào),除了導(dǎo)入全局變量/類的靜態(tài)成員變量,很多時(shí)候可以忽略, 這個(gè)時(shí)候?qū)ふ曳?hào)的優(yōu)先級(jí)將有所不同
當(dāng)這兩個(gè)關(guān)鍵字配合類使用,將會(huì)產(chǎn)生比較尷尬的問(wèn)題了
DLL:
struct __declspec(dllexport) A
{
void TestInline()
{
printf(“from dll\n”);
}
};
EXE:
struct __declspec(dllimport) A
{
void TestInline()
{
printf(“from exe\n”);
}
};
int main()
{
A obj;
obj.TestInline(); //輸出 “from dll”!
return 0;
}
危害1:
A中的內(nèi)聯(lián)函數(shù)全部失效
這當(dāng)然可以從符號(hào)鏈接表中發(fā)現(xiàn)鏈接符號(hào)的蹤跡來(lái)表明內(nèi)聯(lián)函數(shù)失效了,不過(guò)有一個(gè)更簡(jiǎn)單的方法,我們可以在EXE的代碼中把”TestInline”內(nèi)聯(lián)函數(shù)的定義代碼加以簡(jiǎn)單修改,讓其輸出其它的字符串測(cè)試,這個(gè)時(shí)候運(yùn)行會(huì)發(fā)現(xiàn),輸出的字符串仍然是”from dll”(注意編譯為”Release”發(fā)行版, DEBUG版會(huì)將內(nèi)聯(lián)函數(shù)作為普通函數(shù)處理), 這就說(shuō)明了,內(nèi)聯(lián)函數(shù)被完全忽略了。如果這個(gè)時(shí)候,還能想到”TestInline”的定義代碼還有什么作用的話,那就是白白消耗編譯的時(shí)間,編譯期間編譯器辛辛苦苦生成的代碼,鏈接器會(huì)毫不留情地忽略掉。
很顯然,忽略掉內(nèi)聯(lián)函數(shù)的代碼,會(huì)有一個(gè)直接的副作用,那就是使應(yīng)用程序的運(yùn)行期變緩慢,特別是內(nèi)聯(lián)函數(shù)被大量在循環(huán)內(nèi)調(diào)用,而這個(gè)內(nèi)聯(lián)當(dāng)成普通函數(shù)處理了
C++運(yùn)行庫(kù)中有大量的內(nèi)聯(lián)成員函數(shù),除了模板類以外(當(dāng)然,非完全特化的模板類不可能支持導(dǎo)出的),基本上都是以導(dǎo)出類的形式導(dǎo)出,這樣也就意味著,動(dòng)態(tài)鏈接C++運(yùn)行庫(kù)的程序,這類內(nèi)聯(lián)函數(shù)全部失效,編譯時(shí)間可一點(diǎn)沒(méi)少。
危害2:
私有成員函數(shù),以及原本不想導(dǎo)出的函數(shù)(沒(méi)有多少重用價(jià)值,導(dǎo)出后還要承擔(dān)維護(hù)版本兼容性的責(zé)任),也被”不知不覺(jué)”
地導(dǎo)出了,對(duì)于私有(private)成員函數(shù), 導(dǎo)出到外面有什么用呢?占據(jù)了符號(hào)表的一個(gè)位置,可是誰(shuí)又能調(diào)用到呢?
如果要想調(diào)用,看來(lái)要強(qiáng)制把private變?yōu)閜ublic了,但是VC的符號(hào)形成機(jī)制包含了public/private/protected,
也就是通過(guò)了編譯也無(wú)法通過(guò)鏈接,唯一可能的辦法就是,修改DLL頭文件,在類里面增加”friend”, 在EXE端,
改變共享庫(kù)DLL頭文件的辦法,很顯然不是個(gè)正規(guī)方案
另外,導(dǎo)出了這些“無(wú)法調(diào)用”的函數(shù),也影響了鏈接器的優(yōu)化。對(duì)于這些函數(shù),如果DLL模塊內(nèi)部也沒(méi)有調(diào)用到,
本可以完全把這些代碼優(yōu)化掉,但是由于導(dǎo)出,鏈接器將無(wú)能為力, 這也直接增加了DLL文件的體積
由此想到的:
MFC/ATL動(dòng)態(tài)庫(kù)似乎了解到這個(gè)問(wèn)題,所以,它們寧可一個(gè)一個(gè)成員函數(shù)進(jìn)行符號(hào)導(dǎo)出,也沒(méi)有進(jìn)行整個(gè)類的導(dǎo)出,
為什么它們不把這個(gè)方式推薦給VC動(dòng)態(tài)運(yùn)行庫(kù)呢?
VC傳統(tǒng)靜態(tài)鏈接運(yùn)行庫(kù)的方式雖然有某種弊端,但是卻可以完全避開上述提到的問(wèn)題,而且脫離了”版本型”DLL的依賴(MSVCR71.dll, MSVCP71.dll, MSVCR90.dll, MSVCP90.dll…), 對(duì)于這類DLL的依賴,將給以后程序升級(jí)帶來(lái)隱含并難以解決的問(wèn)題,大家可以考慮如下情形
版本1:A.exe依賴于B.dll, 并且它們同時(shí)依賴于msvcp71.dll
版本2: 由于A采用了新的VC版本進(jìn)行編譯,A.exe現(xiàn)在依賴于msvcp90.dll了,B.dll沒(méi)有升級(jí),仍然依賴msvcp71.dll
這個(gè)時(shí)候,進(jìn)程中的內(nèi)存結(jié)構(gòu)產(chǎn)生了微妙的變化,舉例來(lái)說(shuō),原來(lái)msvcp71.dll中的全局變量(例如cout)同時(shí)被A.exe和B.dll使用。現(xiàn)在呢,A.exe使用的是msvcp90.dll中的cout, B.dll使用的還是msvcp71.dll中的cout, 這種耦合的變化,將會(huì)導(dǎo)致程序執(zhí)行邏輯的變化(如果你運(yùn)氣好,也許會(huì)沒(méi)有問(wèn)題的),例如,如果你在A中設(shè)置了cout的格式,然后調(diào)用B的接口,最后B的接口通過(guò)cout輸出,版本2與版本1將會(huì)拿到不同的執(zhí)行效果(源代碼可一點(diǎn)沒(méi)變哦,僅僅是換了個(gè)編譯器編譯)。大型程序邏輯將是非常復(fù)雜的,看來(lái)還是不要升級(jí)編譯器進(jìn)行編譯的好,因?yàn)檫@將導(dǎo)致dll依賴關(guān)系的變化,這真是個(gè)無(wú)奈的選擇
混合編譯選項(xiàng) ”/clr” 看來(lái)要考慮允許靜態(tài)鏈接運(yùn)行庫(kù)了(現(xiàn)在的VC版本只能動(dòng)態(tài)鏈接運(yùn)行庫(kù)的),因?yàn)檫\(yùn)行庫(kù)DLL存在版本和效率缺陷
大家有沒(méi)有發(fā)現(xiàn)LINUX操作系統(tǒng)下的so文件一般比windows下的要大很多,其中一個(gè)原因是這個(gè)模型相當(dāng)于默認(rèn)全部導(dǎo)出,即使是全局函數(shù)也是一樣,哪怕你臨時(shí)寫一個(gè)沒(méi)意義的函數(shù),忘記去掉了,也一并導(dǎo)出,雖然你并沒(méi)有給”外界”頭文件,告訴這個(gè)接口如何被調(diào)用,但是這個(gè)接口已經(jīng)事實(shí)存在了。雖然這樣會(huì)簡(jiǎn)化一些工作,但是個(gè)人覺(jué)得,是非常不負(fù)責(zé)任的,特別是現(xiàn)代的應(yīng)用大都是多任務(wù)模塊化的,這樣做潛在地多吃掉不少內(nèi)存和硬盤,而且對(duì)于版本兼容性,將帶來(lái)麻煩。我對(duì)LINUX了解不多,不知道是否有支持指定導(dǎo)出函數(shù)的方法呢?
COM(Component Object Model), 也許有很多其它弊端,但卻解決了上述問(wèn)題,而且可以拿掉DLL文件名中描述版本的丑陋數(shù)字
個(gè)人認(rèn)為,發(fā)展C++的二進(jìn)制接口標(biāo)準(zhǔn)ABI(Application Binary Interface)是非常重要的,COM應(yīng)該算一個(gè)ABI標(biāo)準(zhǔn)吧。總是靜態(tài)地依賴源代碼作為可重用模塊,真的弊端很大,多任務(wù)時(shí)占用的磁盤和內(nèi)存也會(huì)增加。托管編程模型毫無(wú)疑問(wèn)解決了這個(gè)問(wèn)題,當(dāng)然也帶來(lái)了其它方面的一些問(wèn)題。C++在編程模型上可以說(shuō)比托管編程模型要簡(jiǎn)單很多,但缺少一個(gè)二進(jìn)制標(biāo)準(zhǔn)。解決這個(gè)問(wèn)題將比在語(yǔ)言層面增加幾個(gè)關(guān)鍵字要重要很多