Time for C++ and Swift to make first Contact
This article is part of a series.
As mentioned in a post a few days a go, I’m walking through the same process with C++ interop as I did with C.
Yesterday I hit two of the bases:
- A project for testing C++ and Swift in the same source base in the context of a an XCode project
- A project with C++ and Swift in the same Library.
All this required the XCode 15 beta (No.6) and Swift version 5.9 (swiftlang-5.9.0.128.2 clang-1500.0.40.1)
I’m going to flesh the library one out a bit more, I think, but they both got to hello world!
References
- https://developer.apple.com/videos/play/wwdc2023/10172/
- https://developer.apple.com/documentation/Swift/MixingLanguagesInAnXcodeProject
- https://www.swift.org/documentation/cxx-interop/
- https://www.swift.org/documentation/cxx-interop/project-build-setup/
- https://developer.apple.com/documentation/swift/callingapisacrosslanguageboundaries
Related Repos
- https://github.com/carlynorama/CppInteropSimplestXCode/
- https://github.com/carlynorama/CxxInteropLibrary/tree/simple
In XCode
Make the project
- Selected multi-target project
- Updated
Swift Compiler - Language
build settings for project toC++/ObjectiveC++
(filter on “interop”, its at the bottom) Time Code 2:56
Add C++ files
Choose the options to create header AND bridging header.
//
// CxxExampleCode.hpp
// CppInteropInlineExample
//
// Created by Carlyn Maw on 8/9/23.
//
#ifndef CxxExampleCode_hpp
#define CxxExampleCode_hpp
#include <stdio.h>
int myFavoriteNumber();
#endif /* CxxExampleCode_hpp */
//
// CxxExampleCode.cpp
// CppInteropInlineExample
//
// Created by Carlyn Maw on 8/9/23.
//
#include "CxxExampleCode.hpp"
int myFavoriteNumber() {
return 5;
}
//
// CppInteropInlineExample-Bridging-Header.h
// CppInteropInlineExample
//
// Created by Carlyn Maw on 8/9/23.
//
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#include "SimpleCxxFunctions.hpp"
Add call to C++ in ContentView.swift
struct ContentView: View {
let number = myFavoriteNumber()
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, No. \(number)!")
}
.padding()
}
}
Done! Pretty identical to the process with C.
Swift Calling C++ In a Library
Begin Project
cd $PROJECT_DIR
swift package init --type library
touch README.md
git init
Update Package File
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "CxxInteropLibrary",
products: [
.library(
name: "cxxLibrary",
targets: ["cxxLibrary"]),
.library(
name: "InteropLibrary",
targets: ["InteropLibrary"]),
],
targets: [
.target(
name: "cxxLibrary"),
.target(
name: "InteropLibrary",
dependencies: ["cxxLibrary"],
//IMPORTANT include for _any_ target that uses Swift calling C++, not just executables.
swiftSettings: [.interoperabilityMode(.Cxx)]),
.testTarget(
name: "CxxInteropLibraryTests",
dependencies: ["InteropLibrary"],
swiftSettings: [.interoperabilityMode(.Cxx)])
]
)
Create Directory Structure
The general outline for the project. The key is that the sub folders in the Sources directory are named the same as the products/targets and that all the cpp header files end up in the include folder. The source folder names are easy to change via the package file. Having the header files somewhere other than the include folder requires more doing.
└── Project
└── Sources
│ └── cxxLibrary
| | |── include
| | | └── module.modulemap
| | | └── SimpleCxxFunctions.hpp
| | └── SimpleCxxFunctions.cpp
| └── InteropLibrary
| └── SomeSwiftFile.swift
└── Tests
└── ...
The module map looks like:
module cxxLibrary {
header "SimpleCxxFunctions.hpp"
export *
}
“Swift Package Manager automatically generates a module map file when it finds an umbrella header in a C++ target. Xcode automatically generates a module map file for a framework target, with the module map referencing framework’s public headers. In other cases you might be required to create a module map manually.”
I tried making an umbrella header file in include called cxxLibrary.h, but the SPM did not in fact seem able to pick up on it in either Xcode or VScode so I went with the modulemap, which I had used before when bringing in a system installed C library.
#incude <cxxLibrary/someCPPFile.hpp>
Add the C++
This project used the same C++ files as the Xcode project to get started, minus the bridging header. Also myFavoriteNumber
returns “5”.
Add the Swift
//
// /InteropLibrary/ThingCatalog.swift
// CxxInteropLibrary
//
// Created by Carlyn Maw on 8/9/23.
//
import cxxLibrary
public struct ThingCatalog {
public init() {}
public func currentNumber() -> Int {
return Int(myFavoriteNumber())
}
}
Add the Test
As a way of getting out of building a CLI to test the framework, I just built actual tests. It can then be tested in Xcode or with swift test
from the command line (in the project directory).
import XCTest
@testable import InteropLibrary
final class CxxInteropLibraryTests: XCTestCase {
func testCurrentNumber() throws {
XCTAssert(ThingCatalog().currentNumber() == 5)
}
}
This test passes!
Up next, try different C++ functions with different types.