如何将 C 代码编译成 Swift 框架
How to compile C code into Swift Framework
我有一个 Swift 框架,我们编译并分发给第 3 方开发人员。我想在项目中包含一些 C 代码,但我能够使其工作的唯一方法是将 C header 导入我的框架的 header 并设置 C header 为 public。这将所有 C 代码暴露给第 3 方开发人员,这不是我们想要的。
我在网上可以找到的关于如何执行此操作的所有内容都是此方法的变体:https://spin.atomicobject.com/2015/02/23/c-libraries-swift/
但是这个方法的警告是 "Note that consumers of any frameworks you build using this technique are also going to have to add the module to their Swift search paths." 我们已经尝试过这个并且我们的第 3 方开发人员确实得到了一个错误 Missing required module 'CGeodesic'
这是我们定义的指向 C 的模块header.
我们如何才能将 C 代码直接编译到我们的框架中,同时保持代码的私密性,而不要求第 3 方开发人员更改他们的构建设置以使其工作?我不介意在我们的项目中设置这些子模块,但最终我希望它直接编译成一个单一的平面框架二进制文件,没有动态链接或搜索路径或类似的东西。
编辑
该项目是 C 代码 side-by-side 和 Swift 代码。我的项目中有一个子目录,如下所示:
- 测地线/
- Geodesic.h
- Geodesic.c
- Geodesic.swift
Swift 文件包装了 C 代码,使其更易于使用。我在该目录中也有一个 module.modulemap 文件,但正如我之前所说,它不起作用。我宁愿保留 C 代码 side-by-side 和我的 Swift 代码,但我愿意放弃它以解决此问题。
虽然不推荐,但有一个解决方案。您可以在 .Swift
文件中 use @_silgen_name
而不是使用 C header.
只是为了重新表述您的问题:您基本上想将 C 代码包装在 Swift 中并防止直接访问封装的 C 代码。这意味着要阻止 Swift 和 Objective-C.
的访问
这是可能的,但需要对您的构建过程和一些 post 处理进行一些调整。
C 和 Swift 框架中的互操作
任何你想包装在 Swift 中的 C 代码都需要在编译期间 public 通过伞 header 或使用模块映射。这里的重要部分:编译期间。框架没有完整性检查,这意味着您可以对它们应用 post 处理(例如,使用 lipo
创建一个胖二进制框架)。这就是我们需要在这里做的。
准备构建
除了将所有 C header 依赖项放入伞 header 之外,您还可以将它们放入 module.modulemap
文件。这有一个好处:您可以在 Headers
构建阶段将 header 设为私有。
因此,在您的项目中创建一个 module.modulemap
文件,并在 Module Map File
构建设置中的 Packaging
部分(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 header 文件
后处理
如果您查看最终的框架包,您会注意到所有私人 header 文件都已复制到 MyFramework.framework/PrivateHeaders
文件夹中。这意味着您仍然可以通过 Objective-C 中的 #import <MyFramework/A.h>
访问它们。
在 Swift 中您仍然可以访问 C 代码,因为我们将 header 文件放在已复制到 MyFramework.framework/Modules/module.modulemap
的 module.modulemap
文件中。
幸运的是,我们可以通过一些 post 处理来解决这两个问题:
- 从框架包中删除
PrivateHeaders
文件夹。
- 创建一个单独的模块映射并删除所有
header "XYZ.h"
语句,例如,将其命名为 public.modulemap
.
- 将所有这些放在
Run Script
构建阶段
这是 public 模块图:
# 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 框架,我们编译并分发给第 3 方开发人员。我想在项目中包含一些 C 代码,但我能够使其工作的唯一方法是将 C header 导入我的框架的 header 并设置 C header 为 public。这将所有 C 代码暴露给第 3 方开发人员,这不是我们想要的。
我在网上可以找到的关于如何执行此操作的所有内容都是此方法的变体:https://spin.atomicobject.com/2015/02/23/c-libraries-swift/
但是这个方法的警告是 "Note that consumers of any frameworks you build using this technique are also going to have to add the module to their Swift search paths." 我们已经尝试过这个并且我们的第 3 方开发人员确实得到了一个错误 Missing required module 'CGeodesic'
这是我们定义的指向 C 的模块header.
我们如何才能将 C 代码直接编译到我们的框架中,同时保持代码的私密性,而不要求第 3 方开发人员更改他们的构建设置以使其工作?我不介意在我们的项目中设置这些子模块,但最终我希望它直接编译成一个单一的平面框架二进制文件,没有动态链接或搜索路径或类似的东西。
编辑
该项目是 C 代码 side-by-side 和 Swift 代码。我的项目中有一个子目录,如下所示:
- 测地线/
- Geodesic.h
- Geodesic.c
- Geodesic.swift
Swift 文件包装了 C 代码,使其更易于使用。我在该目录中也有一个 module.modulemap 文件,但正如我之前所说,它不起作用。我宁愿保留 C 代码 side-by-side 和我的 Swift 代码,但我愿意放弃它以解决此问题。
虽然不推荐,但有一个解决方案。您可以在 .Swift
文件中 use @_silgen_name
而不是使用 C header.
只是为了重新表述您的问题:您基本上想将 C 代码包装在 Swift 中并防止直接访问封装的 C 代码。这意味着要阻止 Swift 和 Objective-C.
的访问这是可能的,但需要对您的构建过程和一些 post 处理进行一些调整。
C 和 Swift 框架中的互操作
任何你想包装在 Swift 中的 C 代码都需要在编译期间 public 通过伞 header 或使用模块映射。这里的重要部分:编译期间。框架没有完整性检查,这意味着您可以对它们应用 post 处理(例如,使用 lipo
创建一个胖二进制框架)。这就是我们需要在这里做的。
准备构建
除了将所有 C header 依赖项放入伞 header 之外,您还可以将它们放入 module.modulemap
文件。这有一个好处:您可以在 Headers
构建阶段将 header 设为私有。
因此,在您的项目中创建一个 module.modulemap
文件,并在 Module Map File
构建设置中的 Packaging
部分(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 header 文件后处理
如果您查看最终的框架包,您会注意到所有私人 header 文件都已复制到 MyFramework.framework/PrivateHeaders
文件夹中。这意味着您仍然可以通过 Objective-C 中的 #import <MyFramework/A.h>
访问它们。
在 Swift 中您仍然可以访问 C 代码,因为我们将 header 文件放在已复制到 MyFramework.framework/Modules/module.modulemap
的 module.modulemap
文件中。
幸运的是,我们可以通过一些 post 处理来解决这两个问题:
- 从框架包中删除
PrivateHeaders
文件夹。 - 创建一个单独的模块映射并删除所有
header "XYZ.h"
语句,例如,将其命名为public.modulemap
. - 将所有这些放在
Run Script
构建阶段
这是 public 模块图:
# 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
希望对您有所帮助!