如何将 C 代码编译到 Swift Framework 中



我有一个 Swift 框架,我们编译并分发给第三方开发人员。我想在项目中包含一些 C 代码,但我能够使其工作的唯一方法是将 C 标头导入我的框架标头并将 C 标头设置为公共。这会将所有 C 代码公开给第三方开发人员,这不是我们想要的。

我能在网上找到的关于如何做到这一点的所有内容都是这种方法的变体:https://spin.atomicobject.com/2015/02/23/c-libraries-swift/

但这种方法的警告是"请注意,使用这种技术构建的任何框架的消费者也必须将模块添加到他们的 Swift 搜索路径中。我们已经尝试过这个,果然我们的第三方开发人员收到一个错误Missing required module 'CGeodesic'这是我们定义的指向 C 标头的模块。

我们如何才能将 C 代码直接编译到我们的框架中,同时保持代码的私有性,并且不需要第三方开发人员更改他们的构建设置以使其工作?我不介意在我们的项目中设置这些子模块,但归根结底,我希望它直接编译成一个平面框架二进制文件,没有动态链接或搜索路径或类似的东西。

编辑


该项目是与 Swift 代码并行的 C 代码。我的项目中有一个子目录,如下所示:

  • 测地线/
    • 测地线
    • 测地线
    • 测地线.swift

Swift 文件包装了 C 代码,使其更易于使用。我在那个目录中也有一个module.modulemap文件,但就像我之前说的,它不起作用。我更愿意将 C 代码与我的 Swift 代码并排保存,但我愿意放弃它以解决此问题。

只是为了改写你的问题:你基本上想用 Swift 包装 C 代码,并阻止直接访问封装的 C 代码。这意味着要阻止来自 Swift 和 Objective-C 的访问。

这是可能的,但需要对构建过程进行一些调整和一些后处理。

框架中的 C 和 Swift 互操作

任何想要包装在 Swift 中的 C 代码都需要在编译时通过 umbrella 标头或使用模块映射公开。这里重要的部分:在编译时。框架没有完整性检查,这意味着您可以对它们进行后处理(例如,使用 lipo 创建一个胖二进制框架)。这就是我们在这里需要做的。

准备生成

除了将所有 C 标头依赖项放入伞标头中,您还可以将它们放入 module.modulemap 文件中。这有一个优点:您可以在Headers构建阶段将标头设为私有。

因此,在项目中创建一个module.modulemap文件,并在Packaging部分的Module Map File构建设置中设置它的路径(在 xcconfig 文件中MODULEMAP_FILE),例如 $(SRCROOT)/MyFramework/module.modulemap .

您的module.modulemap文件应包含以下内容:

# module.modulemap
framework module MyFramework {
    umbrella header "MyFramework.h"  
    export *
    module * {export *}
    # All C Files you want to wrap in your Swift code
    header "A.h"
    header "B.h"
}

目前为止,一切都好。您现在应该能够构建代码并从 Swift 和 Objective-C 访问它。唯一的问题:你仍然可以在 Swift 和 Objective-C 中访问你的 C 头文件。

后处理

如果您查看最终的框架捆绑包,您会注意到所有私有头文件都已复制到MyFramework.framework/PrivateHeaders文件夹中。这意味着您仍然可以通过Objective-C中的#import <MyFramework/A.h>访问它们。

在 Swift 中,您仍然可以访问 C 代码,因为我们将头文件放在已复制到 MyFramework.framework/Modules/module.modulemapmodule.modulemap文件中。

幸运的是,我们可以通过一些后处理来摆脱这两个问题:

  1. 从框架捆绑包中删除PrivateHeaders文件夹。
  2. 创建一个单独的模块映射并删除所有header "XYZ.h"语句,例如,将其命名为 public.modulemap
  3. 将所有这些置于Run Script构建阶段

这是公共模块映射:

# public.modulemap
framework module MyFramework {
    umbrella header "MyFramework.h"  
    export *
    module * {export *}
    # No more C Headers here
}

以及应添加到框架构建阶段末尾的运行脚本:

# Delete PrivateHeaders folder
rm -rf ${TARGET_BUILD_DIR}/${PRODUCT_NAME}${WRAPPER_SUFFIX}/PrivateHeaders
# Remove module.modulemap file
rm ${TARGET_BUILD_DIR}/${PRODUCT_NAME}${WRAPPER_SUFFIX}/Modules/module.modulemap
# Copy public.modulemap file and rename it to module.modulemap
cp ${SRCROOT}/test/public.modulemap ${TARGET_BUILD_DIR}/${PRODUCT_NAME}${WRAPPER_SUFFIX}/Modules/module.modulemap
# Append the Swift module so you can access you Swift code in Objective-C via @import MyFramework.Swift
echo "module ${PRODUCT_NAME}.Swift { header "${PRODUCT_NAME}-Swift.h" }" >> ${TARGET_BUILD_DIR}/${PRODUCT_NAME}${WRAPPER_SUFFIX}/Modules/module.modulemap

希望对您有所帮助!

有一个解决方案,但不推荐。您可以在.Swift文件中使用 @_silgen_name,而不是使用 C 标头。

最新更新