1. 程式人生 > >解決庫依賴,讓你無需每次都把庫檔案拷貝到系統資料夾。

解決庫依賴,讓你無需每次都把庫檔案拷貝到系統資料夾。

解決MAC執行時庫依賴報錯問題

本文只探討執行時出錯,編譯不通過請自行google解決。

1、庫依賴報錯有幾種,這裡只介紹動態連線庫 *.dylib、框架*.framwork,其餘的依賴報錯解決辦法類似。

2、找出庫依賴

xcode會報出庫依賴的錯誤,但是可能不夠詳細。

命令列輸入 otool -L  <object file>可以查詢可執行檔案所依賴的庫。

例:(為了節省時間,大神們可以只看小標題)

1)生成你的app

開啟xcode,編譯、執行出錯的工程,由於找不到依賴的庫,執行時會卡住,這時候不管它,先停止執行。

2)找到該app檔案中的可執行檔案。

xcode

左側找到Products資料夾,選中   *.app檔案(*代表你生成的產品名稱) ->滑鼠右鍵單擊它,選中“Show in finder->在開啟的資料夾中,右鍵點選 *.app 檔案,選中“顯示包內容” -> 開啟“Contents”資料夾,->開啟 MacOS 資料夾 ->裡面住著一位(有可能多位)黑不溜秋,略帶綠色熒光的傢伙,它就是可執行檔案了。

3)查詢依賴的庫

otool -L 該可執行檔案

  開啟終端, 輸入: otool -L [空格]

注意, -L後面輸入一個空格,然後把剛剛找到的可執行檔案拖到終端來。

拖完之後,終端顯示類似的資訊:

otool -L /Users/userName/Library/Developer/Xcode/DerivedData/Bluepoint-fapwtpqiamwycgdslbokwedxpcss/Build/Products/Debug/Bluepoint.app/Contents/MacOS/Bluepoint 

按回車開始查詢。

例子中找到如下資訊:

/usr/local/lib/libPowerMeterLib.dylib (compatibility version 1.0.0, current version 1.0.0)

/Library/Frameworks/LuCamSDK.framework/Versions/A/LuCamSDK (

compatibility version 1.0.0, current version 1.0.0)

@rpath/BeamProfileKit.framework/Versions/A/BeamProfileKit (compatibility version 1.0.0, current version 1.0.0)

libSMC6490_Lib.dylib (compatibility version 0.0.0, current version 0.0.0)

/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 1349.25.0)

/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)

/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 307.4.0)

/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1238.0.0)

/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit(compatibility version 45.0.0, current version 1504.75.0)

/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation(compatibility version 150.0.0, current version 1348.28.0)

每一行就是一個依賴項,

現在我來科普一下每行的內容。

第一行,/usr/local/lib/libPowerMeterLib.dylib(compatibility version 1.0.0, current version 1.0.0)

,後面括號裡面的內容是版本資訊,暫時不用管它。括號前面是一個檔案的絕對路徑,這個檔案是一個.dylib結尾的檔案,這是一個動態連線庫(類似Windows裡面的 dll )。

哎,問題就在這裡,這個絕對路徑是庫的開發者有意無意設定的,執行時,Xcode會去該路徑查詢該檔案,找不到該檔案就報錯了。聰明的小夥伴們已經想到了,工程裡面是有這個檔案的,(不然編譯的時候極有可能報錯,為什麼不是必然報錯?後面有機會再講  。)既然有這個檔案,我們告訴xcode去哪裡找這個東西就OK了。

第二行,它依賴 /Library/Frameworks/ 下的 LuCamSDK.framework檔案, LuCamSDK.framework下的可執行檔案路徑是/Versions/A/LuCamSDK,

這是一個可執行檔案依賴框架檔案的栗子。

第三行同是一個框架檔案依賴,只是路徑是 @rpath下的 BeamProfileKit.framework檔案。 

@rpath 是一個儲存了多個路徑的變數,可以用編譯器指定,這樣在執行時,會遍歷多個路徑,直至找到存放指定檔案為止。

第四行,libSMC6490_Lib.dylib ,木有路徑?這個沒關係,沒有路徑的,預設在/usr/local/lib/,只是修改依賴路徑的時候,old 引數需直接傳  libSMC6490_Lib.dylib

第五行及以下行是系統庫依賴,如果工程沒有對系統庫進行修改,請不要更改依賴路徑,

還有些情況,就是如果用到了跟目前系統版本不同的庫,也可以通過修改庫路徑來相容不同版本的作業系統。

3、修改依賴路徑

MAC下,修改可執行檔案依賴路徑的命令是:

install_name_tool [-change old new] input

舉個栗子:

假如一個可執行檔案e1e1在電腦上的/pathC(完整路徑是:  /pathC/e1  (就是input))

它原來依賴  /usr/local/lib/下面的 A.dylib檔案 ,(完整路徑是:  /usr/local/lib/A.dylib(也是old))

可是,我電腦上的A.dylib檔案在目錄/pathB,我要把e1的依賴修改成  /pathB/A.dylib   new

完整的命令如下:

install_name_tool -change /usr/local/lib/A.dylib  /pathB/A.dylib  /pathC/e1 

改完之後,可以 otool -L  /pathC/e1 看一下改好沒有。

下面我們就可以來修改依賴路徑了^_^

等一下!!!我們每次編譯,都會重新生成.app檔案的,現在修改了依賴路徑,如果重新編譯它會不會(。 ́︿ ̀。)

還有,我們工程裡面有這個檔案,但是編譯之後,我們要把.app安裝到別人的電腦上,別人的電腦沒有這個工程,當然也不會存在這個檔案,會不會執行報錯(。 ́︿ ̀。)

為了解決以上問題,我們可以考慮

1、在每次生成.app之後修改依賴路徑

2、把依賴的檔案,從工程裡拷貝打包到.app檔案裡面。

由於修改路徑需要該被依賴檔案事先存在,我們把順序調換一下:

1、把依賴的檔案,從工程裡拷貝打包到.app檔案裡面。

2、在每次生成.app之後修改依賴路徑。

xcode打包時,可以自動把一些檔案拷貝到.app裡面,具體做法是在 target Build Phases裡面點選上方的 +號,選擇 New Copy File Phase,增加一個檔案拷貝欄位,xcode在編譯完成之後,會自動拷貝欄位裡面提到的檔案。

為了規範,我們把需要的框架檔案拷貝到 Contents資料夾下面的Framworks資料夾裡面。

把需要的動態連線庫拷貝到Contents資料夾下面的Dylib資料夾裡面。

1New Copy File Phase->修改Destination Framworks ->點選下方的 +號,選擇需要拷貝的*.framwork檔案。 

xcode會自動建立一個 Framworks資料夾,並把需要的檔案都拷貝到裡面來。

2New Copy File Phase  ->修改Destination Wrapper, SubPathContents/Dylib,->選擇需要拷貝的 *.dylib檔案。

3 New Run Script Phase ->填上如下shell程式碼:

install_name_tool -change /usr/local/lib/libPowerMeterLib.dylib @executable_path/../Dylib/libPowerMeterLib.dylib "$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/MacOS/$PRODUCT_NAME"

install_name_tool -change libSMC6490_Lib.dylib @executable_path/../Dylib/libSMC6490_Lib.dylib "$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/MacOS/$PRODUCT_NAME"

install_name_tool -change /Library/Frameworks/LuCamSDK.framework/Versions/A/LuCamSDK @executable_path/../Frameworks/LuCamSDK.framework/Versions/A/LuCamSDK "$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/MacOS/$PRODUCT_NAME"

 install_name_tool -change /Library/Frameworks/LuCamSDK.framework/Versions/A/LuCamSDK @rpath/LuCamSDK.framework/Versions/A/LuCamSDK "$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/Frameworks/BeamProfileKit.framework/Versions/A/BeamProfileKit"

注意,每個引數之間有空格,每句命令之間用回車分隔即可。

4)然後就可以啦,編譯執行,一切正常。

解釋一下:

第一句指令碼,

/usr/local/lib/libPowerMeterLib.dylib 對應 old

@executable_path/../Dylib/libPowerMeterLib.dylib 對應new

"$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/MacOS/$PRODUCT_NAME"對應input

old沒什麼好說的,原來的依賴路徑,

new這裡引用了一個 @executable_path變數,這個是指工程啟動的執行檔案所在的路徑,一般固定為 XXX/*.app/Contents/MacOS/,這個路徑是執行的時候產生的。

input 這裡引用了兩個變數, $TARGET_BUILD_DIR   $PRODUCT_NAME 。顧名思義,這兩個變數分別是生成目標(.app)的目錄,和(.app)的名稱。

意思就是把app裡入口可執行檔案的  /usr/local/lib/libPowerMeterLib.dylib依賴路徑改成

入口可執行檔案的上一層目錄的/Dylib/libPowerMeterLib.dylib目錄。 ..)是上一層的意思。

第二第三句沒什麼說的,

第四句, input"$TARGET_BUILD_DIR/$PRODUCT_NAME.app/Contents/Frameworks/BeamProfileKit.framework/Versions/A/BeamProfileKit"

為什麼要新增這句呢?

因為BeamProfileKit.framework裡面的可執行檔案BeamProfileKit也依賴了LuCamSDK,如果不修改依賴,也會引起報錯。(每個框架檔案裡面也有一個可執行檔案,它有可能依賴別的庫)。

終於寫完了,寫得又長又臭,人家說如果不能用一句話把事情描述清楚的話,可能是你的理解還不到位。好吧,如果用一句話來說,它就是打包你庫,修改庫依賴路徑。