为iOS项目编译外部C++库

24

我完全不会使用C++库,所以我明白这可能对我的情况有些具体(如果需要,让我知道我可以提供更多细节)。

我有一个外部的C++库,我正在尝试将其与iOS项目一起使用。该库遵循配置、构建、生成 .a 库文件的模式。当我尝试将此库文件添加到Xcode时,我会收到以下错误消息:

忽略文件 /Users/Developer/iOS/TestProj/libpresage.a,该文件是为未连接的体系结构(i386)而构建的归档文件:

/Users/Developer/iOS/TestProj/libpresage.a

根据这个问题,我尝试将 Build Active Architecture Only 设为“NO”,但我仍然得到相同的错误。这使我怀疑我可能已经针对错误的架构编译了库。

在 .a 文件上运行 lipo -info 命令的结果如下:

输入文件 libpresage.a 不是一个通用文件 非通用文件: libpresage.a

是体系结构: x86_64

由于它既不是 armv7s、armv7 也不是 arm64,我尝试使用以下参数重新编译 C++ 库:

1) 尝试

./configure CC="gcc -arch armv7s" \
                 CXX="g++ -arch armv7s" \
                 CPP="gcc -E" CXXCPP="g++ -E"

编译时发生错误,出现以下信息:

ld: library not found for -lcrt1.3.1.o
clang: error: linker command failed with exit code 1 (use -v to see invocation)

2) 尝试

./configure CC="gcc -arch arm64" \
                 CXX="g++ -arch arm64" \
                 CPP="gcc -E" CXXCPP="g++ -E"

编译错误,我收到以下消息:

  

ld: 警告:ld: 警告:忽略文件   /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/lib/libSystem.dylib,   缺少所需的体系结构arm64在文件中   /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/lib/libSystem.dylib   (2个切片)忽略文件   /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/lib/libstdc++.dylib,   缺少所需的体系结构arm64在文件中   /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/lib/libstdc++.dylib   (2个切片)

     

ld: 动态主执行文件必须链接arm64架构的libSystem.dylib文件clang: 错误:链接器命令失败退出   1(使用-v查看调用)

我是否漏掉了一些明显的东西?

编辑:

感谢回复,我已经将库作为自定义构建目标添加到Xcode中,并将“make”命令指向库的MakeFile。这个构建很好。

我的下一步:

  • 将Objective C iOS应用程序目标的依赖项添加到自定义构建目标。
  • 引用库并创建Objective C++包装器。
  • 这似乎很好,直到我需要调用外部C++库,然后编译时出现错误:
  

体系结构armv7的未定义符号:    “ Presage :: Presage(PresageCallback *)”,引用自:        - [PresageBridge init]中的PresageBridge.o    “ Presage ::〜Presage()”,引用自:        - [PresageBridge init]中的PresageBridge.o   ld:找不到符号(s)适用于体系结构armv7   clang:错误:链接器命令失败退出1(使用-v查看调用)

  • 我的Objective C++包装器(链接外部C++库头文件presage.h):

#import "PresageBridge.h"
#include "presage.h"

@implementation PresageBridge

- (instancetype)init
{
    if(self = [super init])
    {

       Presage hello(&callback);
    }

    return self;
}
  • 基于上述代码,看起来我没有丢失头文件。有趣的是,我也尝试创建外部库中其他类的实例,它们似乎可以工作,这表明Xcode无法正确链接presage.h。


  • 1
    如果您有该库的源代码,则只需将其包含在您的项目中即可。 - Cy-4AH
    谢谢 - 我没意识到这么显然!我已经在做这个方面取得了一些成功,但是现在在尝试调用类时出现链接错误。 - HHHH
    关于您最近在帖子中的编辑,请注意在Xcode中有一个选项称为“仅构建活动架构”。在调试模式下,您将只构建1个架构,而在发布模式下,您将构建所有架构。如果您更改了标志,您是否仍然会发现armv7符号未找到?(顺便说一句,您的预测库是为armv7编译还是为armv7s编译的?) - MichaelCMS
    1
    你把这个库整合到你的项目里了吗? - Massimo Piazza
    6个回答

    41

    我在iOS项目中使用了许多第三方C++库。人们有不同的策略来处理这个问题。正如一些人已经提到的,您可以直接将代码包含在项目中,使用Xcode构建静态库,或者使用命令行进行构建。对于使用GNU配置和构建系统的跨平台C++库,我更喜欢使用命令行。您只需要构建一次,仅在需要更新版本或添加新的架构片段时才需要重新构建。

    您想要的通用方法是:

    • 确定要用于构建每个片段的正确配置参数。通常,您只需要专注于使其中一个arm以及i386工作即可。一旦完成此操作,其余部分都很容易。在某些情况下,您实际上需要修改配置文件以添加主机或进行其他调整。

    • 一旦您可以构建所有片段,您就希望运行lipo来构建fat二进制文件。

    最好的方法是创建一个构建脚本,该脚本将为您完成所有工作。这样,重新做会更容易。更重要的是,您可以重复使用脚本或排列它以构建其他外部库。

    您可以使用许多方式构建脚本。以下是其中一种方式。我碰巧有几个此类脚本的变体。该脚本用于构建cURL。它或多或少适用于presage,需要进行很少的修改(即更改curl为presage)。请注意,我没有在Xcode中测试过它(即链接并运行它)。我发现我必须禁用sqlite,否则它会构建不正确的工具项。如果需要,可以解决这一部分。

    有许多方法可以使其更加流畅。例如使用数组存储所有架构。这只是蛮力方法。

    该脚本的关键点是:

    1. 获取最新的SDK
    2. 构建每个片段
    3. 然后运行lipo

    请注意,它应该可以直接使用,但是您自己的情况可能有所不同。如有必要,请准备调试它。例如,我尚未确认主机类型,但通常都是这样使用的。您希望将其放置在presage目录中(与configure文件相同的目录)。完成后,所有架构都在输出目录中。通用库位于presage目录中。

    还要记住,您有责任正确链接通用库,并正确定义头文件搜索路径。

    #!/bin/bash
    
    PLATFORMPATH="/Applications/Xcode.app/Contents/Developer/Platforms"
    TOOLSPATH="/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin"
    export IPHONEOS_DEPLOYMENT_TARGET="8.0"
    pwd=`pwd`
    
    findLatestSDKVersion()
    {
        sdks=`ls $PLATFORMPATH/$1.platform/Developer/SDKs`
        arr=()
        for sdk in $sdks
        do
           arr[${#arr[@]}]=$sdk
        done
    
        # Last item will be the current SDK, since it is alpha ordered
        count=${#arr[@]}
        if [ $count -gt 0 ]; then
           sdk=${arr[$count-1]:${#1}}
           num=`expr ${#sdk}-4`
           SDKVERSION=${sdk:0:$num}
        else
           SDKVERSION="8.0"
        fi
    }
    
    buildit()
    {
        target=$1
        hosttarget=$1
        platform=$2
    
        if [[ $hosttarget == "x86_64" ]]; then
            hostarget="i386"
        elif [[ $hosttarget == "arm64" ]]; then
            hosttarget="arm"
        fi
    
        export CC="$(xcrun -sdk iphoneos -find clang)"
        export CPP="$CC -E"
        export CFLAGS="-arch ${target} -isysroot $PLATFORMPATH/$platform.platform/Developer/SDKs/$platform$SDKVERSION.sdk -miphoneos-version-min=$SDKVERSION"
        export AR=$(xcrun -sdk iphoneos -find ar)
        export RANLIB=$(xcrun -sdk iphoneos -find ranlib)
        export CPPFLAGS="-arch ${target}  -isysroot $PLATFORMPATH/$platform.platform/Developer/SDKs/$platform$SDKVERSION.sdk -miphoneos-version-min=$SDKVERSION"
        export LDFLAGS="-arch ${target} -isysroot $PLATFORMPATH/$platform.platform/Developer/SDKs/$platform$SDKVERSION.sdk"
    
        mkdir -p $pwd/output/$target
    
         ./configure --prefix="$pwd/output/$target" --disable-shared --disable-sqlite --host=$hosttarget-apple-darwin
    
        make clean
        make
        make install
    }
    
    findLatestSDKVersion iPhoneOS
    
    buildit armv7 iPhoneOS
    buildit armv7s iPhoneOS
    buildit arm64 iPhoneOS
    buildit i386 iPhoneSimulator
    buildit x86_64 iPhoneSimulator
    
    LIPO=$(xcrun -sdk iphoneos -find lipo)
    $LIPO -create $pwd/output/armv7/lib/libpresage.a  $pwd/output/armv7s/lib/libpresage.a $pwd/output/arm64/lib/libpresage.a $pwd/output/x86_64/lib/libpresage.a $pwd/output/i386/lib/libpresage.a -output libpresage.a
    

    @Mobile Ben,你能解释一下第22-26行在做什么吗?特别是第24和25行? (count=${#arr[@]} if [ $count -gt 0 ]; then sdk=${arr[$count-1]:${#1}} num=expr ${#sdk}-4 SDKVERSION=${sdk:0:$num}) - nsg
    1
    #被用来获取长度。${arr[$count-1]:${#1}}是子字符串提取。语法是${string:position}。所以在这种情况下,string是数组中的最后一个元素(最高的操作系统版本)。${#1}findLatestSDKVersion参数的长度,即iPhoneOS(长度为8)。如果我们说arr[$count-1]是iPhoneOS8.3.sdk,则sdk将是8.3.sdk。我们的最后一行将num分配为剩余部分(8.3.sdk)的长度并减去4(以补偿.sdk)。最后,我们进行了另一个子字符串提取。这次它是${string:position:length} - Mobile Ben
    2
    小提示: 似乎hostarget="i386"缺少一个t - André Fratelli
    你为什么要在这些if语句中切换平台?这个脚本难道不能编译64位平台吗? - André Fratelli
    1
    我会在CFLAGSCPPFLAGS中都加入-fembed-bitcode,否则在构建存档时会出现问题。 我还对构建freetype库进行了轻微的修改:https://github.com/fysiskhund/mjEngineCPP/blob/master/etc/buildFreetype2ForiOS.sh; 不幸的是,在升级到Sierra后,所有这些都停止工作了,所以我必须保留一个旧的ElCapitán OSX安装程序来处理这个问题。 - Alejandro
    显示剩余2条评论

    7
    考虑到您对C++库还比较陌生,我想您需要进行更多的研究。
    不过,我会尝试概述一些需要考虑的步骤:
    - 您需要确保同时为静态库(.a)和项目编译相同的架构。 - 根据您的错误,您需要将静态库编译为i386或将项目更改为x86_64(这些体系结构之间的差异有点复杂,但现在先说i386表示桌面32位而x86_64表示桌面64位)。 - ARM体系结构是为iPhone而设计的,而不是为您的MacOS(这就是为什么它无法找到具有ARM体系结构的库的原因),而且解决这些问题有多种方法。
    对于第一个问题,我建议将静态库包含到工作区,并将其添加为构建目标的依赖项。为此,您需要了解XCode构建。
    我猜您实际上正在尝试制作一款手机应用程序,所以对于第三个选项,您需要配置g++构建,在链接ARM目标时查找来自XCode的iPhoneSDK(在此之后查找iPhoneOS.platform)。
    创建ARM版本仅适用于iPhone。如果要使其在模拟器上运行,则需要将静态库链接到iPhoneSimulator.platform中的库。
    如果要使静态库适用于iPhone和iPhone模拟器,则需要制作一个Fat lib(基本上是包含两个平台符号的库)。
    如果您缺少这些平台,可以从XCode中下载它们(但我相信它们已经存在)。
    正如您所看到的,随着时间的推移,事情会变得越来越复杂,因此我强烈建议使用XCode编译静态库(不过使用g++仍然可行)。
    我相信以下概念对您进行研究将很有帮助:
    - ARM、x86、x86_64 - 静态库 - 静态链接 - Fat lib(通用库) - 具有多个项目的XCode工作区
    希望这会有所帮助 :)。

    3
    以下是我在 Xcode 9 上为 iOS 设备(iPhone X)编译 dylib 的方法:
    1)使用以下标志编译 dylib:
    a)"安装目录": @executable_path/Frameworks b)"运行路径搜索路径": @executable_path/Frameworks
    参见下图:
    Xcode 9 中的 dylib 设置
    2)在使用/链接 dylib 的 Xcode 项目中:
    a)"运行路径搜索路径":
    @executable_path/Frameworks
    b)在 "Build Phase->Embed Libraries" 中,确保选择 "Destination" 为 "Executables",Subpath 为 "Frameworks",并选中 "Code sign on copy":
    设置 iOS 应用程序的链接
    此方法已在 Xcode 9.2 和 iPhone X 上进行了测试和使用。

    2
    $LIPO -create $pwd/output/armv7/lib/libpresage.a  $pwd/output/armv7s/lib/libpresage.a $pwd/output/arm64/lib/libpresage.a $pwd/output/x86_64/lib/libpresage.a $pwd/output/i386/lib/libpresage.a -output libpresage.a
    

    to:

    for t in `ls $pwd/output/armv7/lib/*.a` ;
    do
            export LIB=`basename $t`
            export ARCHSTRING=""
            for a in `ls $pwd/output`
            do
                    export ARCSTRING="$ARCSTRING $pwd/output/$a/lib/$LIB "
            done
            $LIPO -create $ARCSTRING  -output $LIB
    done
    

    注意:我不想用新的代码功能编辑Mobile Ben的答案,将其添加为评论会丢失格式。

    0

    C++可以在iOS上运行,您只需将其添加到项目中即可。或者,如果您真的想要自己的动态库,可以使用Xcode编译并指定目标架构。


    请问您能否详细说明一下?我通过将C++库添加为自定义构建,并将make命令指向库的makefile目录,成功使构建工作。然后,我在我的项目目标和库的目标之间添加了依赖关系,一切似乎都很好 - 直到我尝试引用外部库头文件(Presage.h)。我遇到了以下错误:Undefined symbols for architecture armv7: "Presage::Presage(PresageCallback*)", referenced from: -[PresageBridge init] in PresageBridge.o请问我做错了什么吗? - HHHH
    你在构建阶段将头文件(toto.h)添加为公共文件了吗?编辑:好的,不要标记回复... :D - debug

    0

    将您的架构设置回默认值,然后尝试以下操作。 1. 在Build Phases->Link Binary With Libraries中添加libz.dylib和libxml2.dylib库到您的项目中。 2. 在BuildSettings->Search Paths中将Always Search User Paths设置为Yes,并在Framework Search Paths下添加您的框架的正确路径。使用终端获取您的框架的正确路径。 3. 尝试将“Compiler Source As”设置为C++,或者使用试错法并检查所有选项。Complier Source As也在BuildSettings下。使用cmd+f进行搜索。

    尝试这些并让我知道,还告诉我您正在尝试在项目中使用的框架或SDK。


    尝试了一下,不幸的是我遇到了链接错误 - 请参见我上面的编辑。这个框架是Presage,一个预测文本输入平台。 - HHHH

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接