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:

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

In XCode

Make the project

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 *
}

The documentation says

“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.

This article is part of a series.