解决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
举个栗子:
假如一个可执行文件e1,e1在电脑上的/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文件夹里面。
1)New Copy File Phase->修改Destination为 Framworks ->点击下方的 +号,选择需要拷贝的*.framwork文件。
xcode会自动建立一个 Framworks文件夹,并把需要的文件都拷贝到里面来。
2)New Copy File Phase ->修改Destination为 Wrapper, SubPath填Contents/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,如果不修改依赖,也会引起报错。(每个框架文件里面也有一个可执行文件,它有可能依赖别的库)。
终于写完了,写得又长又臭,人家说如果不能用一句话把事情描述清楚的话,可能是你的理解还不到位。好吧,如果用一句话来说,它就是:打包你库,修改库依赖路径。