Search code examples
c++swiftswift-package-manager

Can I mix C++ and Swift in the same swift package, using the Swift Package Manager?


I would like to write one module in C++, and make some functions in C that are accessible in Swift.

I am somewhat baffled, because no matter what I do, the SPM insists on attempting to compile the C++ code as if it were Objective-C, and of course it cannot find the right headers.

Here is my attempt.

Source directory structure:

Sources
|
+-CxxModule
| |
| +-include
| | |
| | +-CxxModule.hpp
| |
| +-CxxModule.cpp
|
+-SwiftModule
  |
  +-SwiftModule.swift

The manifest Package.swift is as follows:

// swift-tools-version: 5.6

import PackageDescription

let package = Package(
    name: "CxxLibrary",
    products: [
        .library(
            name: "CxxLibrary",
            targets: ["SwiftModule"]),
    ],
    dependencies: [
    ],
    targets: [
        .target(
            name: "CxxModule",
            dependencies: []),
        .target(
            name: "SwiftModule",
            dependencies: ["CxxModule"],
            path: "Sources/SwiftModule"
        ),
    ]
)

CxxModule.hpp is as follows:

#ifndef CxxModule_hpp
#define CxxModule_hpp

#include <iostream>

extern "C" void printHello();

#endif /* CxxModule_hpp */

CxxModule.cpp is as follows:

#include "CxxModule.hpp"

void printHello() {
    // use something from the standard library to make sure
    // c++ is really being used
    std::cout << "Hello, world!" << std::endl;
}

Finally, SwiftModule.swift:

import CxxModule

What am I missing? Is there a way to tell SPM that the module is supposed to be in C++? Or is this simply unsupported at the moment?

Note: the C++ compiles just fine if I eliminate the Swift target.


Solution

  • Updated answer for Swift 5.9+

    Swift 5.9 supports interoperability with C++. Therefore, there is no longer any need to craft a special header that compiles both in C and C++. Instead, C++ interoperability mode should be enabled by adding .interoperabilityMode(.Cxx) to each target’s swiftSettings in the Package.swift manifest. Here is the updated manifest:

    // swift-tools-version: 5.9
    
    import PackageDescription
    
    let package = Package(
        name: "CxxLibrary",
        // To avoid linker warnings in macOS, make version 13 (or later, 
        // according to your needs), the minimum supported version:
        platforms: [.macOS(.v13)],
        products: [
            .library(
                name: "CxxLibrary",
                targets: ["SwiftModule"]
            ),
        ],
        targets: [
            .target(
                name: "CxxModule"
            ),
            .target(
                name: "SwiftModule",
                dependencies: ["CxxModule"],
                // Specifying the path is superfluous as long as
                // the corresponding folder is called 'SwiftModule'
                swiftSettings: [.interoperabilityMode(.Cxx)]
            ),
        ]
    )
    

    The file structure remains unchanged. However, I modified CxxModule.hpp and CxxModule.cpp as follows:

    CxxModule.hpp:

    #ifndef CxxModule_hpp
    #define CxxModule_hpp
    
    void printHello();
    
    #endif /* CxxModule_hpp */
    

    CxxModule.cpp:

    #include <iostream>
    
    void printHello() {
        // use something from the standard library to make sure
        // c++ is really being used
        std::cout << "Hello, world!" << std::endl;
    }
    

    With C++ interoperability, there is now no problem importing functions and classes directly from C++, with some restrictions. The complete guide can be found at https://www.swift.org/documentation/cxx-interop/.

    Old answer for Swift < 5.9

    I was able to resolve the problem. The answer is “Yes” so long as the header exposed to Swift is readable in pure C.

    First, I moved all C++-specific code (especially headers referencing the standard library) into the source cpp file:

    #include <iostream>
    #include "CxxModule.hpp"
    
    void printHello() {
        // use something from the standard library to make sure
        // c++ is really being used
        std::cout << "Hello, world!" << std::endl;
    }
    

    Second, I added preprocessor directives to make the header readable in both C and C++:

    #ifndef CxxModule_hpp
    #define CxxModule_hpp
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    void printHello();
    
    #ifdef __cplusplus
    }
    #endif
    
    #endif /* CxxModule_hpp */
    

    The file structure and Swift module remain unchanged.

    The lesson: any header that is going to be read by both C++ and Swift must be readable by both. (Swift is able to understand C headers, but not C++ headers, at least in its present state.)