将C ++库与Swift集成-如何构建

在“ Objective-C和Swift —友好相处”一文中,我谈到了如何使用Objective-C在Speculid中将C ++库与Swift集成在一起。 今天,我将讨论在XCode项目中使用C ++库的挑战。

Speculid是一个完全开源的应用程序,主要在Swift中使用最新版本的Xcode(10.1)构建(我将主要而不是仅解释其中的原因)。 借助Speculid,您可以拍摄一张图形并将其构建为完整的应用程序图标或图像集。

因此,这意味着将所有必需的依赖项打包在.App包中。 最初,我考虑将InkscapeImageMagick包含在安装中。 但是, Inkscape具有其他依赖项(例如XQuartz)这一事实意味着这太麻烦了。 因此,开罗和librsvg成为显而易见的选择。

将Cairo和librsvg与Swift集成

Speculid的第一个版本需要安装两个软件包:Inkscape和ImageMagick。 每个应用程序将以某种组合方式用于读取,操作和导出SVG,PNG,JPEG或PDF文件。 结果, 需要这些软件包使用户体验笨拙且困难。 但是,由于它们是完整的应用程序,包括这两个程序包也将很麻烦。 这就是为什么我着眼于集成C ++库的原因-特别是其中的两个: Cairo libRSVG

Cairolibrsvg由多个软件包使用,是图形和SVG操作软件开发的领先库,尤其是在开源和Linux社区中。 因此,在Speculid v2.0中,我将这些库集成并打包为读取PNG和SVG以及导出PDF和PNG文件的主要方法 。 最重要的是,可以使用以下命令通过HomeBrew在Mac上安装这两个库:

brew install cairo librsvg

现在,我们需要确保复制和链接文件。

正确链接和复制C ++库

为了使用C ++库, 我们可以将每段C ++代码作为它们各自的库编译在Speculid中,也可以集成已编译的库。 最初,似乎编译代码是最干净,最有效的方法,除了每个库都有很多要求以及用于编译每个库的特殊标志的事实。 因此,我决定采取另一种方法,并自己使用编译的库。

使用HomeBrew安装库之后,将Cairo和librsvg的目录复制到您的项目文件夹中。 HomeBrew将其应用程序和库存储在:

 /usr/local/Cellar 

因此,例如, 开罗将位于:

 /usr/local/Cellar/cairo 

将其复制到您的项目后,文件需要处于构建阶段的三个位置

  • 与库链接-所有.dylib文件都需要列出
  • 复制文件-所有.dylib文件都需要复制到Frameworks文件夹
  • 标头 -所有.h文件都需要在Project下列出

之后,您应该会很好。 如果您在构建应用程序时遇到任何问题, 请仔细检查Xcode项目中构建阶段下列出的所有正确文件

在依赖地狱中保持井井有条

通过链接和嵌入C ++库,您可以运行该应用程序,并且可以顺利运行。 因此,然后您将产品存档并打包,​​然后将其发送给他人运行,他们将得到如下信息:

 dyld: Library not loaded: @loader _path/../lib/libintl.8.dylib 

诸如Cairo和librsvg之类的Unfortunatley库通常具有所需的依赖项。 但是,有一些命令可以帮助您:

  • otool -L
    如果我们想找出实际的依赖关系,此命令将显示特定库所需和使用的库的名称和版本号。
  • brew deps —tree
    为了确保在开发人员机器上安装了所有依赖项,我们可以使用HomeBrew查找与软件包一起安装的依赖项。 安装它们后,我们可以将它们添加到我们的项目中,并将其包含在App包中
  • install_name_tool
    复制依赖关系并将其包含在我们的应用程序中之后,我们需要更新用于查找依赖关系的路径。

收集依赖

首先,我们需要通过添加otool -L描述的动态库来确保已将所有依赖项包含在您的App中。 例如,如果我们在开罗库上运行otool -L ,我们将得到以下信息:

 $ otool -L libcairo.2.dylib /usr/local/Cellar/cairo/1.14.12/lib/libcairo.2.dylib: /usr/local/opt/cairo/lib/libcairo.2.dylib (compatibility version 11403.0.0, current version 11403.12.0) /usr/local/opt/pixman/lib/libpixman-1.0.dylib (compatibility version 35.0.0, current version 35.0.0) /usr/local/opt/fontconfig/lib/libfontconfig.1.dylib (compatibility version 12.0.0, current version 12.1.0) /usr/local/opt/freetype/lib/libfreetype.6.dylib (compatibility version 22.0.0, current version 22.0.0) /usr/local/opt/libpng/lib/libpng16.16.dylib (compatibility version 51.0.0, current version 51.0.0) /usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.11) /System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices (compatibility version 1.0.0, current version 50.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.0.0) /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1450.16.0) /System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics (compatibility version 64.0.0, current version 1129.5.0) /System/Library/Frameworks/CoreText.framework/Versions/A/CoreText (compatibility version 1.0.0, current version 1.0.0) 

因此,我们看到Cairo需要一些像pixman的库。 幸运的是, 我们可以使用以下命令获取HomeBrew依赖关系树
brew deps —tree

 $ brew deps -tree cairo fontconfig freetype gettext glib libffi libpng pcre pixman 

值得庆幸的是,如果我们在开发机器上缺少pixman ,则可以使用brew install pixman进行brew install pixman ,然后将pixman的HomeBrew酒窖目录复制到我们的项目文件夹中。

再次确保动态库在构建阶段属于构建过程的一部分。 换句话说,在“ 构建阶段”下应该有类似以下内容:

使用install_name_tool更新参考

现在,所有必需的依赖项都包含在应用程序中,我们需要告诉Speculid在哪里寻找依赖项。在哪里寻找对应的依赖项。 这就是install_name_tool来源。

编写脚本以搜索和更新动态库

因此,对于此过程,我们将编写一个脚本来更新我们的依赖关系,它将需要执行以下操作:

  1. 使用系统未安装的otool -L查找依赖otool -L
  2. 更新每个动态库的ID和使用@rpath的路径,这是应用程序使用的运行时搜索路径。
  3. 浏览我们的Frameworks文件夹中的每个文件,然后…
  4. 再次更新ID以及使用@rpath的路径
  5. 使用otool -L查找该依赖项的每个依赖项
  6. 并更新搜索路径以使用@rpath

结果如下:

 #!/bin/sh LIBS=`otool -L "$1" | grep "/opt\|Cellar" | awk -F' ' '{ print $1 }'` for lib in $LIBS; do install_name_tool -id @rpath/`basename $lib` "`dirname $1`/Frameworks/`basename $lib`" install_name_tool -change $lib @rpath/`basename $lib` "$1" done FRAMEWORKS_FOLDER_PATH="`dirname $1`/Frameworks/" deps=`ls "$FRAMEWORKS_FOLDER_PATH" | awk -F' ' '{ print $1 }'` for lib in $deps; do install_name_tool -id @rpath/`basename $lib` "`dirname $1`/Frameworks/`basename $lib`" install_name_tool -change $lib @rpath/`basename $lib` "$1" dylib="`dirname $1`/Frameworks/`basename $lib`" deps=`otool -L "$dylib" | grep "/opt\|Cellar" | awk -F' ' '{ print $1 }'` for dependency in $deps; do install_name_tool -change $dependency @rpath/`basename $dependency` "$dylib" done done 

让我们分解一下……

分解更新动态库

 LIBS=`otool -L "$1" | grep "/opt\|Cellar" | awk -F' ' '{ print $1 }'` 
  1. 使用系统未安装的otool -L查找依赖otool -L
    $ 1是可执行文件或${TARGET_BUILD_DIR}/${EXECUTABLE_PATH}的路径。 otool -L将列出所有依赖项。 通过grep,我们可以过滤不是系统安装的结果,而是通过HomeBrew安装在/usr/local/opt位置的结果。 awk -F' ' '{ print $1 }'将结果打印为可在for循环中使用的格式。
 for lib in $LIBS; do install_name_tool -id @rpath/`basename $lib` "`dirname $1`/Frameworks/`basename $lib`" install_name_tool -change $lib @rpath/`basename $lib` "$1" done 

2.更新每个动态库的ID和使用@rpath的路径,这是应用程序使用的运行时搜索路径。
在这里,我们遍历每个依赖项,并更新标识名和路径,以在我们的框架中查找依赖项以使用@rpath

 FRAMEWORKS_FOLDER_PATH="`dirname $1`/Frameworks/" deps=`ls "$FRAMEWORKS_FOLDER_PATH" | awk -F' ' '{ print $1 }'` for lib in $deps; do 

3.浏览我们Frameworks文件夹中的每个文件,然后…

 install_name_tool -id @rpath/`basename $lib` "`dirname $1`/Frameworks/`basename $lib`" install_name_tool -change $lib @rpath/`basename $lib` "$1" 

4.再次更新ID以及使用@rpath的路径

 dylib="`dirname $1`/Frameworks/`basename $lib`" deps=`otool -L "$dylib" | grep "/opt\|Cellar" | awk -F' ' 

5.使用otool -L查找该依赖项的每个依赖项
计算依赖关系的路径,然后运行otool -L以获得其依赖关系。

 for dependency in $deps; do install_name_tool -change $dependency @rpath/`basename $dependency` "$dylib" done 

6.并更新搜索路径以使用@rpath

现在,应用程序和框架应包含我们的应用程序在另一台计算机上运行所需的所有内容。

您可以在 此处 找到该脚本的最新版本

结论

上一次我们学习了如何优化Objective-C和Swift代码,以便它们可以很好地协同工作。 今天,我们学习了如何:

  • 将HomeBrew的C ++库集成到我们的项目中
  • 验证是否包括所有依赖项
  • 使用install_name_tool和脚本修复引用

因此,使用Speculid,运行主接口的Swift代码可以与与C ++库接口的Objective-C对话。 无需外部应用程序或繁琐的安装。

如果有兴趣,请查看我在Ann Arbor Cocoaheads所做的有关此主题的演示文稿:

使用C ++库面临哪些挑战? 您是否曾经为iOS应用程序使用过任何库? 在评论中让我知道。