swift和c语⾔,Swift与C混编的正确姿势
⾃从笔者第⼀次尝试 Swift 到现在已经过去 5 年多了,从Swift 的第⼀个版本到现在的 Swift 5.2,Swift 语⾔发⽣了天翻地覆的变化。Swift ⽣态也已经很完善,⽇常开发中⽤到的各种库基本都⽀持了 Swift。那些现在还在纠结要不要使⽤ Swift 的同学可以看看这篇⽂章 ,⽂章中提到的⼏个问题⼏乎涵盖了 OC 与 Swift 混编时会遇到的⼀些问题,⽂章中都给出了相应的解决⽅案。
Swift 和 Objective-C 以及 C、C++(Swift 不能直接调⽤ C++,必须通过 OC进⾏调⽤) 混编的阻⼒⾮常⼩。它可以⾃动桥接 objective-C 的类型,甚⾄可以桥接很多 C 的类型。这就可以让我们在原有库的基础上,使⽤ Swift 开发出简洁易⽤的 API。Swift 和 Objective-C 混编的⽂章不少,在这篇⽂章中,我们将学习如何让 C 与 Swift 进⾏交互。
Bridging Header学swift语言能干什么
当我们在⼀个 Swift 项⽬中添加 C 源⽂件时,Xcode 会询问是否添加 Objective-C 桥接头⽂件,这跟我们在 Swift 项⽬中添加 OC ⽂件⼀样。接着我们只需要在 Bridging Header 中添加需要暴露给 Swift 代码的头⽂件:
#include "test.h"
在 test.h 中声明了⼀个 hello 函数:
#ifndef test_h
#define test_h
#include
void hello(void);
#endif/* test_h */
然后在 tesh.c 中实现了它:
#include "test.h"
void hello() {
printf("Hello World");
}
现在我们就可以在 Swift 代码中调⽤ hello() 了。
Swift Package Manager
上⾯使⽤ Bridging header 的⽅式主要适⽤于 C 源代码跟 Swift 代码处于同⼀个 App target 下,对于那些独⽴的 Swift Framework 就不适⽤了,在这种情况下就需要使⽤ Swift 包管理器(Swift Package Manager , 下⽂简称SPM)了。从 Swift 3.0 开始我们就可以使⽤SPM 来构建 C 语⾔的⽬标 (target)了。
下⾯我们将⽤ Swift 封装⼀个易⽤的 OpenGL 程序库。通过这个例⼦,我们基本上可以掌握如何在⼀个 Swif 库中与 C 进⾏交互了。
设置 SPM
为导⼊ C 程序库设置⼀个 Swift 包管理器项⽬并不是什么难事,不过还是有不少的步骤要完成。
现在让我们开始创建⼀个新的 SPM 项⽬吧。切换要保存代码的⽬录,执⾏下⾯的命令创建⼀个 SPM 包:
$mkdir OpenGLApp
$cdOpenGLApp
$swift package init --type library
我们通过 swift package init --type library 命令创建了⼀个名为 OpenGLApp 的 Swift 库。我们可以打开 Package.swift ⽂件看看⾥⾯的内容(删除了⽆关内容):
// swift-tools-version:5.2
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "OpenGLAPP",
products: [
.library(name: "OpenGLApp", targets: ["OpenGLApp"])
],
dependencies: [],
targets: [
.target(
name: "OpenGLApp",
dependencies: [],
]
)
为了完成⼀个可以运⾏的 OpenGL 程序,我们需要依赖
将 C 程序库导出为模块
由于 GLFW 和 GLEW 都是由 C 编写的库,所以我们先要解决如何让 Swift 到这些 C 语⾔库,这样,才能在 Swift 调⽤它们。在 C ⾥,可以通过 #include ⼀个或多个库的头⽂件的⽅式来访问它们。但是 Swift ⽆法直接处理 C 的头⽂件,它依赖的是模块 (Module)。为了让⼀个⽤ C 和 Objective-C 编
写的库对 Swift 编译器可见,它们必须安照 Clang Module 的格式提供⼀份模块地图 (Module map)。它的主要作⽤就是列出构成模块的头⽂件。
因为 GLFW 和 GLEW 并没有提供模块地图,所以我们需要在 SPM ⾥定义⼀个专门⽣成模块地图的⽬标。它的作⽤就是把以上的 C 语⾔库封装成模块,这样就可以在另⼀个 Swift 模块中调⽤它们了。
⾸先,我们需要安装 glew 和 glfw,如果是 macOS 系统可以使⽤ homebrew 来安装。其他的系统就使⽤相关的包管理器安装就可以了。
接着打开 Package.swift, 在 targets 中增加如下内容:
...
targets: [
....
.systemLibrary(
name: "Cglew",
pkgConfig: "glew",
providers: [
.brew(["glew"])
]),
.systemLibrary(
name: "Cglfw",
pkgConfig: "glfw3",
providers: [
.brew(["glfw"])
]),
]
在上⾯的 Package.swift 中,我们新添加了两个系统程序库⽬标(system library target)。所谓的系统程序库⽬标是指那些由系统级别的包管理器安装的程序库,例如我们使⽤ homebrew 安装的⼀些程序库。Sample ⽬标是最终的可执⾏程序,OpenGLApp 是我们将要使⽤Swift 封装的 OpenGL 库,Cglew 和 Cglfw 两个系统程序⽬标就是我们制作的可以在 Swift 中调⽤的模块。
在系统程序库⽬标中 pkConfig 和 providers 两个参数需要说明⼀下:
providers 指令是可选的,在⽬标库没有被安装时,它为 SPM 提供了⽤于安装库的⽅式的提⽰。
pkConfig 指定了pk g-config ⽂件的名称,Swift 包管理器可以通过它到要导⼊的库的头⽂件和库搜索路径。pkConfig 的名称我们可以在库的安装路径的 lib/pkconfig/xxx.pc 中到,以我电脑中安装的 glew 为例,它的位置是
/usr/local/Cellar/glew/2.1.0/lib/pkgconfig/glew.pc,所以上⾯ pkConfig 中设置的就是 glew。
接下来我们需要在 Sources ⽬录下为系统程序库⽬标创建⼀个保存⽂件的⽬录,该⽬录名称必须跟上⾯ Package.swift 中定义的⽬标的name 属性⼀致。这⾥我以 Cglfw 为例:
$cdSources && mkdir Cglfw
在 Cglfw ⽬录中添加⼀个 glfw.h ⽂件,并添加如下内容:
#include
接着添加⼀个 dulemap ⽂件,它应该是下⾯的样⼦:
module Cglfw [system] {
header "glfw.h"
export *
}
我们添加 glfw.h (名称可以⾃⼰定义)⽂件的⽬的是绕过模块地图中必须包含绝对路径的限制,否则的话,我们就必须在 modulemap ⽂件中的 header 中指定 glfw3.h 头⽂件的绝对路径,在我的电脑上就是 /usr/local/Cellar/glfw/3.3.2/include/GLFW/glfw3.h,这样就将GLFW 的路径硬编码到模块地图中了。使⽤了我们添加的 glfw.h ⽂件,SPM 就会从 pkg-config ⽂件中读取正确的头⽂件搜索路径,并将它添加到编译器的调⽤中。
我们可以按照同样的⽅式将 GLEW 导出为模块,这⾥我就不演⽰了。上⾯是将安装在系统中的 C 程序库导出为模块,不过有些情况下我们只有 C 程序库的源代码,这个时候我们仍然可以使⽤ SPM 将 C 程序源码导出为模块。
C 源码导出为模块
将 C 源代码导出为模块也⾮常简单,其实也是编写模块地图的过程,不过这个过程我们可以借助 SPM ⾃动帮我们完成。
我们可以从这⾥下载 GLEW 的源码。跟上⾯的步骤⼀样,在 Sources ⽬录下创建⼀个 Cglew ⼦⽬录,并将解压后的 GLEW 源代码中include 和 src ⽬录拷贝到 Cglew ⽬录下。然后我们在 Package.swift 中添加如下内容:
.target(name: "Cglew")
在上⾯的过程中我们并没有编写模块地图,并不是说通过这种⽅式不需要模块地图,⽽是 SPM ⾃动帮我们完成的。我们将需要暴露给外部的头⽂件放到 include ⽬录下,编译时 SPM 就会⾃动⽣成模块地图。当然我们也可以通过 publicHeadersPath 参数来指定需要暴露给外部头⽂件的路径。
接着我们可以来完成 OpenGLApp 这个⽬标了。在 OpenGLApp ⽬录中添加⼀个 GLApp.swift ⽂件。现在,我们就可以在 Swift ⽂件中使⽤ import Cglew , import Cglfw,并调⽤ GLFW 和 GLEW 中提供的 API 了。有⼀点不要忘记,我们需要在 Package.swift ⽂件中OpenGLApp 这个⽬标的 dependencies 添加我们都依赖:
.
target(
name: "OpenGLApp",
dependencies: ["Cglfw", "Cglew"],
linkerSettings: [
.linkedFramework("OpenGL")
]),
为了⽅便在 Xcode 中编写并调试程序,可以使⽤ swift package generate-xcodeproj 命令来⽣成⼀个 Xcode ⼯程。
在通过 import Cglew 引⼊ Cglew 模块并构建项⽬,你会发现 Xcode 报了⼤量错,这个时候可以在 Cglew ⽬标中的 glew.h ⽂件最上⾯添加 #define GLEW_NO_GLU。
后⾯的主要⼯作就是编写 OpenGL 代码了,这⾥就不展开了,毕竟不是本⽂的重点。
接着我们可以添加⼀个⽤于运⾏该库的可执⾏程序的⽬标。我们在 Sources ⽬录下添加 Sample ⼦⽬
录,并添加⼀个 main.siwft ⽂件,并在 Package.swift 中的 targets 添加⼀个 Sample ⽬标:
.target(
name: "Sample",
dependencies: ["OpenGLApp"]),
我在 main.siwft 中调⽤了⾃⼰封装的 OpenGLApp 的 Swift 库:
import OpenGLApp
let app = GLApp(title: "Hello, OpenGL", width: 600, height: 600)
app.run()
SPM 会将包含有 main.swift ⽂件的⽬标作为可执⾏⽂件⽬标。所以我们在⽤ SPM 开发库时,库⽂件中不要有 main.swift ⽂件,否则的话,SPM 会将该⽬标作为可执⾏⽂件⽽不是⼀个库,这样就⽆法正确地和其他库或可执⾏⽂件进⾏链接了。
如果我们继续在终端中执⾏ swift run 命令,这时 SPM 就会构建并执⾏这个应⽤程序(你可以从这⾥
到着⾊器的代码,这⾥到初始化顶点数据的代码)。
下⾯是完整的 Package.swift:
// swift-tools-version:5.2
// The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription
let package = Package(
name: "GLAPP",
products: [
.library(name: "OpenGLApp", targets: ["OpenGLApp"])
],
dependencies: [],
targets: [
.target(
name: "Sample",
dependencies: ["OpenGLApp"]),
.target(
name: "OpenGLApp",
dependencies: ["Cglew", "Cglfw"],

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。