干貨 | 資深程序猿教你如何縮短開(kāi)發(fā)周期
來(lái)源:原創(chuàng) 時(shí)間:2017-04-13 瀏覽:0 次
關(guān)鍵詞:(¬_¬)程序猿 縮短開(kāi)發(fā)周期
一,問(wèn)題引入
在當(dāng)下的開(kāi)發(fā)中,應(yīng)用的功能做的越來(lái)越復(fù)雜,工程也越來(lái)越大,所以為了盡可能縮短開(kāi)發(fā)周期,不可避免的會(huì)用到許多第三方庫(kù),隨之而來(lái)的也會(huì)遇到好 多問(wèn)題。比如,程序調(diào)用函數(shù) funa,funa 函數(shù)從在于兩個(gè)庫(kù) liba.a,libb.a 中,并且程序執(zhí)行需要連接這兩個(gè)庫(kù),那么程序執(zhí)行時(shí)是調(diào)用 liba.a 中 funa 還是調(diào)用的 libb.a 中的 funa 呢?
其實(shí)這個(gè)取決于鏈接時(shí)的順序,比如先鏈接的 liba.a,這個(gè)時(shí)候通過(guò) liba.a 的導(dǎo)出符號(hào)表就可以找到 funa 在 liba.a 中定義,并加入符號(hào)表中;鏈接 libb.a 的時(shí)候發(fā)現(xiàn)符號(hào)表已經(jīng)存在 funa,就不會(huì)再次更新符號(hào)表,所以調(diào)用的始終是 liba.a 中的 funa 函數(shù)。
這里的調(diào)用嚴(yán)重的依賴(lài)于鏈接庫(kù)加載的順序,很大程度上會(huì)導(dǎo)致混亂。作為 SDK 的提供者,我們尤其要避免這點(diǎn)。
正常我們使用的庫(kù)中包含了好多符號(hào)信息,如圖 1 所示:
圖1
這些符號(hào)信息有以下幾個(gè)弊端:
1、增大了庫(kù)的體積;
2、隱蔽性較差;
3、容易帶來(lái)沖突。
在開(kāi)發(fā)過(guò)程中第三點(diǎn)帶來(lái)的問(wèn)題尤其嚴(yán)重,特別是當(dāng)我們提供的 SDK 用到第三方庫(kù)的時(shí)候(因?yàn)槭褂梦覀?SDK 的客戶(hù)也有可能用到跟我們一樣的第三方庫(kù))。
二,解決辦法
1、對(duì)第三方庫(kù)處理
下面繼續(xù)以 x264(下文以 libx264.a 帶過(guò))為例說(shuō)明如何編譯第三方的庫(kù)。 沒(méi)有隱藏符號(hào)的第三方庫(kù)如“圖 1”所示,函數(shù)前面會(huì)帶有 external 的標(biāo)示。
在最終對(duì)外發(fā)布的 SDK 中_x264_predict_16x16_dc_c 還是打著 external 的標(biāo)簽, 及對(duì)外可見(jiàn)。
如圖 2 所示:
圖2
隱藏符號(hào)后,在 libx264.a 中,原先打上 external 標(biāo)簽的函數(shù),會(huì)以 private external 標(biāo)識(shí)。
如圖 3 所示:
那么如何才能得到我們想要的、打上 private external 標(biāo)簽的庫(kù)呢,有兩種方 法可以做到。
1)對(duì)每個(gè)函數(shù)加屬性 __attribute__((visibility(“hidden”))) void funa_hidden() {
printf(“hidden symbol
”); }
void funa_visible() {
printf(“exported symbol”); }
這樣做的好處是可以根據(jù)需要對(duì)每個(gè)函數(shù)做定制處理。但若我們用到的三方 庫(kù)代碼量大,這種方法就是費(fèi)時(shí)費(fèi)力了。
2)編譯庫(kù)時(shí)統(tǒng)一處理
利用 gcc 的擴(kuò)展屬性,編譯庫(kù)時(shí)加上-fvisibility=hidden。 a) 靜態(tài)庫(kù)
gcc –static –o libtest.a –fvisibility=hidden –c test.c
b) 動(dòng)態(tài)庫(kù)
gcc –dynamic –o libtest.so –fvisility=hidden –c test.c
其中 dynamic 為 clang 的寫(xiě)法,大部分 gcc 寫(xiě)法為 shared。
上邊兩種方法只處理了 c/c++,因?yàn)檎Z(yǔ)法問(wèn)題,匯編需要做特殊里,但也是 在函數(shù)頭加屬性,但它的屬性寫(xiě)法為.private_extern。
.macro function name, export=0, .macro endfunc
ELF .size
ame, . -
ame FUNC .endfunc
.purgem endfunc .endm
.text
.align lign .if export
.global EXTERN_ASM
ame
ELF .type EXTERN_ASM
ame, %function FUNC .func EXTERN_ASM
ame EXTERN_ASM
ame:
.private_extern
EXTERN_ASM
ame
.else
ELF .type
FUNC .func
ame:
.endif .endm
ame, %function
ame
因?yàn)樾枰幚淼膮R編文件較少,所以對(duì)匯編采用了直接編輯源文件的方法。 其實(shí)個(gè)人覺(jué)得也應(yīng)該能在編譯時(shí)做統(tǒng)一處理,有興趣的可以自己找一下方法。
2、對(duì) xcode工程的處理
對(duì) xcode 工程處理相對(duì)直觀、簡(jiǎn)單了許多。只需在工程的設(shè)置里做如下處理。
.1) 打開(kāi)工程設(shè)置,跳轉(zhuǎn)到 build setting 頁(yè)面;
.2) 搜索 hidden;
.3) 將 Symbols Hidden by Default 設(shè)置 Yes;
圖4
其實(shí)通過(guò)觀察編譯的過(guò)程可以發(fā)現(xiàn),通過(guò)上述設(shè)置,蘋(píng)果最終將其轉(zhuǎn)化為步驟 1 的命令進(jìn)行編譯。編譯的結(jié)果也是在庫(kù)里加了 private external 而已。
3、符號(hào)剝離
最后一步,也是最關(guān)鍵的一步,就是真正將步驟 1 或步驟 2 中打上 private external 標(biāo)簽的函數(shù)做最終的處理,把它們從要發(fā)布的庫(kù)里剝離。
1) 首先設(shè)置 prelink
在 target 的 build setting 里搜索 prelink,將 Perform Single-Object Prelink 置為
Yes,然后把該工程需要的庫(kù)都直接拖到 Prelink libraries 中。
如圖 5 所示:
圖5
將 Deployment Postprocessing 置為 Yes。
如圖 6 所示:
圖6
2) 設(shè)置 post process
將 Strip Style 設(shè)置為 Non-Global Symbols。
如圖 7 所示:
圖7
到目前為止,所有的設(shè)置都已經(jīng)完成,接下來(lái)編譯。有興趣的同學(xué)可以觀察一下編譯的過(guò)程,會(huì)發(fā)現(xiàn)通過(guò)設(shè)置 prelink,xcode 會(huì)將庫(kù)里所有的目標(biāo)文件根據(jù) 你支持的 architecture 分類(lèi)打包,如 libxxx-armv7-master.o/libxxx-arm64-master.o, 最后一步執(zhí)行 Strip 命令將所有需要隱藏的符號(hào)剝離。