设置混合语言 Swift 和 C++ 项目

Swift 支持 与 C++ 的双向互操作性。本页介绍如何使用受支持的 IDE 或构建系统之一来设置混合语言 Swift 和 C++ 项目

它还介绍了如何通过描述直接调用 Swift 编译器时如何使用 C++ 互操作性,来使其他构建系统能够实现 C++ 互操作性。

使用 Swift 包管理器混合 Swift 和 C++

Swift 包管理器 允许 Swift 代码在 Swift 中使用 C++ API。

Swift 包管理器尚不支持在 C++ 中使用 Swift API。

在包目标中启用 C++ 互操作性

Swift 包中的特定目标必须启用 C++ 互操作性,才能在 Swift 中导入和使用 C++ API。interoperabilityMode Swift 构建设置用于为目标启用 C++ 互操作性。例如,以下包清单展示了如何为库目标启用 C++ 互操作性

let package = Package(
    name: "LibraryThatUsesCxx",
    products: [
        .library(
            name: "libraryUsesCxx",
            targets: ["libraryUsesCxx"])
    ],
    targets: [
        .target(
            name: "libraryUsesCxx",
            swiftSettings: [.interoperabilityMode(.Cxx)])
    ]
)

从 C++ 包目标导入头文件

Swift 使用 Clang 模块 导入 C++ 头文件。Swift 包管理器可以为一个包含总括头文件的 C++ 目标自动生成一个 模块映射文件。生成的模块映射文件允许依赖于此类 C++ 目标的 Swift 目标从此类目标导入 C++ 头文件。

总括头文件必须包含 #include 指令列表,其中包含目标的其它公共 C++ 头文件。一旦将由生成的模块映射表示的 Clang 模块导入到 Swift 中,就可以在 Swift 中使用总括头文件中列出的头文件中声明的受支持的 C++ 类型和函数。

C++ 目标使用的总括头文件必须

include 子目录是目标的默认公共头文件目录。publicHeadersPath 属性可用于为目标的公共头文件指定备用路径。

例如,以下 Swift 包构建了一个使用 C++ 库的 Swift 命令行工具

let package = Package(
    name: "CommandLineSwiftToolUsesCxx",
    products: [
        .library(
            name: "cxxLibrary",
            targets: ["cxxLibrary"]),
        .executable(
            name: "swiftCLITool",
            targets: ["swiftCLITool"])
    ],
    targets: [
        .target(
            name: "cxxLibrary"),
        .executableTarget(
            name: "swiftCLITool",
            dependencies: ["cxxLibrary"],
            swiftSettings: [.interoperabilityMode(.Cxx)])
    ]
)

Swift 包管理器将自动为此包中的 C++ 库生成模块映射,因为它可以在源文件中找到总括头文件

Sources
├── swiftCLITool
└── cxxLibrary
    ├── include
    │   ├── cxxLibrary.h   [This is the umbrella header]
    │   └── classImpl.h
    ├── cxxLibrary.cpp
    └── classImpl.cpp

总括头文件 cxxLibrary.h 包含一些声明,并且还包含 C++ 目标中的其他头文件

// Header file `cxxLibrary.h`
#pragma once

#include <classImpl.h>

swiftCLITool 中的 Swift 代码可以直接导入 cxxLibrary

import cxxLibrary

然后,classImpl.h 头文件中声明的所有受支持的 C++ API 都将在 Swift 中可用。

销售启用 C++ 互操作性的包

为一个 Swift 包管理器目标启用 C++ 互操作性将需要依赖于该目标的其他目标也启用 C++ 互操作性。

启用 C++ 互操作性对于现有包来说是一项破坏性更改,因此必须仅在新的主要 semver 版本中完成。当您启用 C++ 互操作性时,请务必提高包的主要版本!

如果您想销售一个包含启用 C++ 互操作性的目标的包,我们建议您

使用 Xcode 混合 Swift 和 C++

Xcode 15 支持混合语言 Swift 和 C++ 项目。本节介绍如何在 Xcode 中从 Swift 使用 C++ API 以及从 C++ 使用 Swift API。

请查看 “混合使用 Swift 和 C++” WWDC 会议,以获取有关如何在 Xcode 中使用 C++ 互操作性的更多详细信息。以下两个示例 Xcode 项目也可供下载

在 Xcode 中启用 C++ 互操作性

“C++ 和 Objective-C 互操作性”Xcode 构建设置可以设置为“C++ / Objective-C++”,以为特定构建目标启用 C++ 互操作性。启用 C++ 互操作性允许您

在同一 Xcode 目标中混合 Swift 和 C++

Xcode 允许您在同一框架或 App 目标中混合 Swift 和 C++ 或 Objective-C++。

框架目标的 public 头文件中声明的受支持 C++ 类型和函数可以从同一目标中的 Swift 代码中使用。App 目标必须使用桥接头文件,以使目标的 C++ 类型和函数可用于同一 App 目标中的 Swift 代码。一旦将包含声明这些 API 的 App 目标的 C++ 头文件的 #include 指令添加到桥接头文件中,App 目标中的 Swift 代码就可以使用此类 C++ API。

框架或 App 目标的已公开 public Swift API 也可以从同一目标中的 C++ 或 Objective-C++ 实现文件中使用,方法是包含 Xcode 自动生成的生成头文件。例如,C++ 源文件可以通过包含以下生成的头文件来访问来自名为 Fibonacci 的同一框架或 App 目标的 Swift API

// FibonacciCxx.cpp [Fibonacci target]

#include <Fibonacci/Fibonacci-Swift.h>

// You can now use the exposed Swift APIs from Fibonacci.

使用导入的框架目标的 C++ API

Xcode 框架目标的 public C++ 头文件中声明的受支持 C++ 函数和类型可以从其他目标中的 Swift 代码中使用。想要从另一个框架目标使用 C++ API 的目标必须将该框架添加到其依赖项列表中。然后,此类目标中的 Swift 源文件可以导入该框架并在 Swift 中使用 C++ API。

例如,ForestLib 框架的公共 C++ API 可以从 ForestViewer App 目标中的 Swift 源文件使用,一旦 Swift 文件导入 ForestLib 框架

// TreeView.swift [ForestViewer App target]

import ForestLib

// You can now use the supported C++ APIs from ForestLib.

使用导入的框架目标的 Swift API

Xcode 框架目标的受支持和公开的 public Swift API 可以从其他目标中的 C++ 和 Objective-C++ 代码中使用。想要从另一个框架目标使用 Swift API 的目标必须将该框架添加到其依赖项列表中。然后,此类目标中的 C++ 或 Objective-C++ 源文件可以包含该框架的生成头文件,并在 C++ 或 Objective-C++ 中使用 Swift API。

例如,StorageProvider 框架的公共公开 Swift API 可以从 SafeStorage App 目标中的 Objective-C++ 源文件使用,一旦 Objective-C++ 源文件包含以下生成的头文件

// StorageAccess.mm [SafeStorage App target]

#include <StorageProvider/StorageProvider-Swift.h>

// You can now use the exposed Swift APIs from StorageProvider.

使用其他构建系统混合 Swift 和 C++

本节介绍如何在直接调用 Swift 编译器时启用和使用 C++ 互操作性。这允许其他构建系统配置混合语言 Swift 和 C++ 项目。

在 Swift 编译器中启用 C++ 互操作性

-cxx-interoperability-mode= 构建标志用于在 Swift 编译器中启用 C++ 互操作性。它接收互操作性兼容性版本作为其值。目前唯一支持的值是 defaultdefault 值表示 Swift 使用的互操作性兼容性版本与 Swift 语言版本匹配。

在直接调用编译器时导入 C++ Clang 模块

以下构建标志允许 Swift 查找 C++ 头文件

-Xcc 标志用于将额外的 C++ 构建设置传递给 Swift 编译器中嵌入的 C++ Clang 编译器。例如,您可以使用 Clang 的 -std= 标志将需要 C++20 的 C++ 头文件导入 Swift

swiftc ... -Xcc -std=c++20 ...

综上所述,以下 Swift 编译器调用允许您编译一个 Swift 文件,该文件导入一个 Clang 模块,该模块的模块映射文件位于 include 目录中

swiftc main.swift -cxx-interoperability-mode=default -I include -o main

生成带有暴露的 Swift API 的 C++ 头文件

-emit-clang-header-path Swift 前端标志可用于在构建系统中构建 Swift 代码时,当向 C++ 公开 Swift API 时,发出生成的头文件,该构建系统不提供对生成带有暴露的 API 的头文件的自动支持。

以下 Swift 编译器调用为由两个源文件 a.swiftb.swift 组成的 SwiftModule 模块发出一个生成的头文件。

swiftc -frontend -typecheck \
       /sources/a.swift /sources/b.swift -module-name SwiftModule \
       -cxx-interoperability-mode=default \
       -emit-clang-header-path SwiftModule-Swift.h