如何将C代码编译成Swift框架

时间:2016-07-08 19:57:51

标签: c xcode swift llvm ios-frameworks

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

我在网上可以找到的有关如何执行此操作的所有内容都是此方法的变体:https://spin.atomicobject.com/2015/02/23/c-libraries-swift/

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

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

修改

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

  • 测地线/
    • Geodesic.h
    • Geodesic.c
    • Geodesic.swift

Swift文件包装C代码,使其更清晰。我在该目录中也有一个module.modulemap文件,但就像我之前说过的那样没有用。我更愿意将C代码与我的Swift代码并排放在一起,但我愿意放弃它来解决这个问题。

2 个答案:

答案 0 :(得分:17)

只是为了重新解释你的问题:你基本上想要在Swift中包装C代码并阻止直接访问封装的C代码。这意味着要阻止Swift和Objective-C的访问。

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

框架中的C和Swift Interop

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

准备构建

您可以将它们放入module.modulemap文件中,而不是将所有C头依赖项放入伞标题中。这有一个好处:您可以在Headers构建阶段将标头设为私有。

因此,在项目中创建一个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头文件。

<强>后处理

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

在Swift中,您仍然可以访问C代码,因为我们将头文件放在已复制到module.modulemap的{​​{1}}文件中。

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

  1. 从框架包中删除MyFramework.framework/Modules/module.modulemap文件夹。
  2. 创建单独的模块地图并删除所有PrivateHeaders语句,例如,将其命名为header "XYZ.h"
  3. 将所有内容放入public.modulemap构建阶段
  4. 这是公共模块映射:

    Run Script

    您应该添加到框架构建阶段结束的运行脚本:

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

    希望有所帮助!

答案 1 :(得分:1)

有一个解决方案,但不建议这样做。您可以在.Swift文件中use @_silgen_name而不是使用C标头。