What about some fancier C++ in with the Swift?

This article is part of a series.

The CMake “Hello World” prime number provider code seemed like a fun thing to try to bring into this new Swift/C++ Package. The file loading requirement made it a tricky choice, but it provided good practice importing C++ classes into Swift.

References

Templates

The random-number generator that creates the index number used to go fetch a prime number from the file uses C++ templates. Swift can interact with those a little bit, but not perfectly. A post on the forums provide a good break down of the issues:

It’s not a big barrier to get this example working since the call to fancy_random will be internal to its class in the end (see below), but I tired it out anyway.

A reminder of the C++:

template <typename T> T fancy_random(const T &min, const T &max) {
   std::random_device seed_maker;
   std::mt19937 generator(seed_maker()); // Random-number engine to use (Mersenne-Twister)
   std::uniform_int_distribution<T> distribution(min,
                                                 max); // Guaranteed unbiased
   return distribution(generator);
}

A successful swift call:

    public func randomInt(_ min:CUnsignedChar, _ max:CUnsignedChar) -> CUnsignedChar {
        fancy_random(min, max)
    }

Unsuccessful Swift calls. (causes linker errors)

   public func randomNumber(_ min:UInt8, _ max:UInt8) -> UInt8 {
       fancy_random(min, max)
   }

    public func randomNumber(_ min:CUnsignedInt, _ max:CUnsignedInt) -> CUnsignedInt {
    fancy_random(min, max)
    }

Bug report filed: https://github.com/apple/swift/issues/67901

Accessing the Bundle

In order to test moving files around with CMake, I designed the prime number library code to load and scan a file with prime numbers in it. Packages with resource files need to access those resources via a Bundle type.

There MAY be a way to massage the C++ to import either NSBundle or the Swift type Bundle, but it’s way above my current pay grade. I haven’t even been able to successfully get my hands on swift::String yet.

A Bundle related side bar: It looks like the Swift 5.9 Package Manager Tools now has an automated .embedInCode option? It looks the accessor is a dictionary? I wonder how that will work from the C++? To. Do. Very handy, even just for Swift.

Creating a placeholder class in C++ called BundleManger provided an immediate work around for not being able to get my hands on Bundle. A BundleManger gets initialized with a path string generated by the Swift target’s Bundle reference. In this case, the Swift library was given a mock resource file so Bundle.module would exist. This code relies on the Swift target’s and the C++ target’s resource bundles having the same root path. Very fragile.

It’s my understanding from the WWDC video that BundleManager should be passed into Swift as a value type by default, even though it’s a class. I did not confirm it.

ThingCatalog.swift

import Foundation //for Bundle
import cxxLibrary  // for my class

public struct ThingCatalog {
    var bundleManager:BundleManager //SO! EASY!!!

    public init() {
        let strippedPath = String(URL(filePath: Bundle.module.bundlePath)
            .deletingLastPathComponent()
            .path())
        self.bundleManager = BundleManager(
            std.string(strippedPath), 
            "CxxInteropLibrary_cxxLibrary.bundle"
            )
    }

    //...
}

In the call to initialize a BundleManager note the std.string() casting of a Swift string into the C++ standard library string the initializer requires. The ability to draw upon C++ standard library types gets enabled in the package when swiftSettings: [.interoperabilityMode(.Cxx)]), gets added to the target’s definition in Package.swift. In order to refer to stdlib types one must import at least one of the following

The “overlay implementations” are still being actively worked on. String actually seems the most fleshed out.

BundleManager.hpp

#ifndef BundleManager_hpp
#define BundleManager_hpp

#include <stdio.h>
#include <string>
#include <swift/bridging> //allows for annotating

class BundleManager {
public:
    BundleManager(std::string path, std::string name) {
        bundle_path = path;
        this_bundle_name = name;
    }
    
    std::string path() const SWIFT_COMPUTED_PROPERTY {
        return bundle_path;
    }
    
    std::string name() const SWIFT_COMPUTED_PROPERTY {
        return this_bundle_name;
    }
    
    std::string path_for_resource(std::string name, std::string extension);
    
private:
    std::string bundle_path;
    std::string this_bundle_name;
};

#endif /* BundleManager_hpp */

BundleManager.cpp

std::string BundleManager::path_for_resource(std::string name, std::string extension) {
    return bundle_path + this_bundle_name + "/" + name + "." + extension;
}

This whole approach worked to get a valid path into C++, but without additional structure every prime number retrieving function call would of had to collect that information as a parameter. Yuck.

Making PrimeNumberGenerator class

Instead of rewriting all the functions, I made a PrimeNumberGenerator object that holds a BundleManager privately.

I could have made PrimeNumberGenerator a subclass of BundleManager, but the nuances of how the many shades of class inheritance in C++ maps into Swift requires more study on my part. I’m just going to avoid that for the moment.

I did, however, want to try out making an explicit reference type.

PrimeNumberGenerator.hpp

#ifndef PrimeNumberGenerator_hpp
#define PrimeNumberGenerator_hpp

#include <stdio.h>
#include <string>
#include <swift/bridging> //allows for annotating

class BundleManager {
    //this lives here now
};

class PrimeNumberGenerator {
public:
    PrimeNumberGenerator(std::string bundle_path)
    :bundle(bundle_path, "CxxInteropLibrary_cxxLibrary.bundle"), 
    max_line_length(16), // Max line is 12 characters long
    max_prime_length(6)  //// 10,000th prime is 104729
    {
    }
    
    //prime in first 10000 primes at index
    int prime_at(int index);
    
    //random prime within first 10000 primes
    int random_prime();
    
private:
    //All for file loading. 
    BundleManager bundle;
    const int max_line_length;     
    const int max_prime_length;     
    int readNthLine(std::ifstream &in, int index);
    
    template <typename T> T fancy_random(const T &min, const T &max);
    unsigned char random_uint8(const unsigned char &min, const unsigned char &max);
    
}; SWIFT_SHARED_REFERENCE(pngRetain,pngRelease); 
//At the bottom or top? Seen both. Which style is preferred? 

void pngRetain(PrimeNumberGenerator *);
void pngRelease(PrimeNumberGenerator *);

#endif /* PrimeNumberGenerator_hpp */

PrimeNumberGenerator.cpp

This will look pretty similar to PrimeLib.cpp, just with class namespace prefixes.

//
//  PrimeNumberGenerator.cpp
//  
//
//  Created by Carlyn Maw on 8/13/23.
//

#include "PrimeNumberGenerator.hpp"

#include <fstream>
#include <random>
#include <string>


//Same as before.
std::string BundleManager::path_for_resource(std::string name, std::string extension) {
    return bundle_path + this_bundle_name + "/" + name + "." + extension;
}

int PrimeNumberGenerator::readNthLine(std::ifstream &in, int index) {
    for (int i = 0; i < index - 1; ++i) {
        in.ignore(max_line_length, '\n');
    }
    std::string s;
    s.reserve(max_prime_length);
    std::getline(in.ignore(max_line_length, ' '), s);
    return std::stoi(s);
}

uint8_t PrimeNumberGenerator::random_uint8(const uint8_t &min, const uint8_t &max) {
    return fancy_random(min, max);
}

template <typename T> T PrimeNumberGenerator::fancy_random(const T &min, const T &max) {
    std::random_device seed_maker;
    std::mt19937 generator(
        seed_maker()); // Random-number engine to use (Mersenne-Twister)
    std::uniform_int_distribution<T> distribution(min,
                                                  max); // Guaranteed unbiased
    return distribution(generator);
}

// random prime within first 10000 primes
int PrimeNumberGenerator::random_prime() { return prime_at(fancy_random(0, 10000)); }

// prime in first 10000 primes at index
int PrimeNumberGenerator::prime_at(int index) {
    //assumes package uses .process for resources
    std::ifstream primesFile(bundle.path_for_resource("b000040", "txt"));
    if (primesFile.is_open()) {
        return readNthLine(primesFile, index);
    } else {
        // handle error
        return -1;
    }
}

Add the Test, and run

import XCTest
@testable import InteropLibrary

final class CxxInteropLibraryTests: XCTestCase {
   
    //...

    //MARK: PrimeNumberGenerator Related

    func testChosenPrime() throws {
        var things = ThingCatalog()
        XCTAssertEqual(things.nthPrime(n: 998), 7901);
    }

    //...
}

Once that’s in, a quick pop over to Terminal and a swift test shows that it all works! Whew!

This article is part of a series.