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.
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/.
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.)